50
WEB-SOVELLUKSEN TEKO DJANGOLLA Ammattikorkeakoulututkinnon opinnäytetyö Riihimäki, Tietotekniikan koulutusohjelma Kevät, 2018 Tommi Vuori

WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

  • Upload
    others

  • View
    0

  • Download
    0

Embed Size (px)

Citation preview

Page 1: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

WEB-SOVELLUKSEN TEKO DJANGOLLA

Ammattikorkeakoulututkinnon opinnaumlytetyouml

Riihimaumlki Tietotekniikan koulutusohjelma

Kevaumlt 2018

Tommi Vuori

TIIVISTELMAuml Tietotekniikan koulutusohjelma Riihimaumlki Tekijauml Tommi Vuori Vuosi 2018 Tyoumln nimi Web-sovelluksen teko Djangolla Tyoumln ohjaaja t Petri Kuittinen

TIIVISTELMAuml

Taumlssauml opinnaumlytetyoumlssauml tutustuttiin web-sovelluksien kehittaumlmiseen tarkoi-tettuun Django-sovelluskehykseen ja sen kaumlyttaumlmaumlaumln Python-ohjelmointi-kieleen Naumliden lisaumlksi tyoumlssauml kaumlytiin laumlpi yleisesti mitauml web-sovellukset ovat ja mitauml ominaisuuksia web-sovelluskehykset tavallisesti sisaumlltaumlvaumlt Projektina toteutettiin esimerkkisovellus Djangolla Sovelluksena toimi ominaisuuksiltaan melko rajattu linkin lyhentaumlmiseen tarkoitettu web-so-vellus Sovelluksen visuaalisen ilmeen toteutukseen kaumlytettiin Bootstrap-kirjastoa Ennen opinnaumlytetyoumltauml olin kaumlynyt laumlpi muutamia tutoriaaleja Djangolla ja tutustunut myoumls muihin sovelluskehyksiin Minulla oli myoumls hieman kokemusta Bootstrap-kirjastosta Opinnaumlytetyoumln tuloksena saatiin tehtyauml responsiivinen linkin lyhentaumlmi-seen tarkoitettu web-sovellus Valmis sovellus oli ominaisuuksiltaan melko rajattu sisaumlltaumlen vain sovelluksen toiminnan kannalta oleellisimmat omi-naisuudet Linkin lyhentaumlmisen lisaumlksi sovelluksesta loumlytyi tuki kaumlyttaumljaumlti-leille ja linkkien hallintaan tarkoitettu hallintapaneeli Jatkokehitystauml aja-tellen sovellus oli helposti laajennettavissa isommaksi kokonaisuudeksi

Avainsanat Django Python web-sovellus web-sovelluskehys Sivut 44 sivua

ABSTRACT Information Technology Riihimaumlki Author Tommi Vuori Year 2018 Subject Creating a web application with Django Supervisors Petri Kuittinen ABSTRACT

The focus of this thesis was on the Django web framework and the Python programming language it uses In addition to these the thesis takes a gen-eral look at what web applications are and what features are usually in-cluded in the current popular web frameworks As the practical part of the thesis a link shortening application was created using Django Bootstrap was used for the visual interface of the applica-tion Prior to the thesis I had gone through a couple of Django tutorials and introduced myself to other popular frameworks I also had some experi-ence with Bootstrap The result of the thesis was a working and responsive link shortening ap-plication The application was quite limited in functionality including only the most important features In addition to link shortening the application has support for user accounts and contains a link management dashboard The current application would be easy to extend into a more complete and bigger application with further development

Keywords Django Python web application web framework Pages 44 pages

SANASTO

CSS Cascading Style Sheets Web-sivujen ulkoasun ja visuaalisen il-meen maumlaumlrittaumlmiseen tarkoitettu kieli

DOM Document Object Model Malli jonka avulla HTML-sivua on mah-dollista muokata JavaScriptilla

HTML Hypertext Markup Langu-age

Merkintaumlkieli jolla maumlaumlritetaumlaumln sivus-ton rakenne ja sisaumlltouml

HTTP Hypertext Transfer Protocol Protokolla jota selaimet ja palvelimet kaumlyttaumlvaumlt tiedonsiirtoon

JSX JavaScript XML Laajennus tavallisen JavaScript-ohjel-mointikielen syntaksiin

MVC Model-View-Controller Ohjelmistoarkkitehtuuri jonka tarkoi-tuksena on erottaa malli naumlkymauml ja kauml-sittelijauml toisistaan

MVT Model-View-Template Djangon versio suositusta MVC-arkkitehtuurista

ORM Object-relational mapping Tekniikka jonka avulla tietokantoja voi kaumlyttaumlauml oliomaisesti

REST Representational State Transfer

HTTP-protokollaan perustuva tapa raja-pintojen toteuttamiseen

SPA Single-Page Application Sovellus joka toimii usein vain yhdellauml sivun latauksella Data ja sisaumlltouml lada-taan ja esitetaumlaumln dynaamisesti

SQL Structured Query Language Kyselykieli jota kaumlytaumlnnoumlssauml kaikki re-laatiotietokannat kaumlyttaumlvaumlt

XML Extensible Markup Langu-age

Tietynlaisten merkintaumlkielten standardi

SISAumlLLYS

1 JOHDANTO 1

2 WEB-SOVELLUKSET 1

3 WEB-SOVELLUSKEHYKSET 2

31 Mikrosovelluskehykset 2

32 Suosituimmat web-sovelluskehykset 3

33 Asiakaspuolen web-sovelluskehykset 3

331 AngularJS ja Angular 3

332 React 4

333 Vue 4

34 Palvelinpuolen web-sovelluskehykset 5

341 ASPNET Core 5

342 Express 5

343 Flask 6

344 Laravel 6

345 Ruby on Rails 6

4 PYTHON-OHJELMOINTIKIELI 7

41 Pythonin suosio 7

42 Tulkattavuus 8

43 Tyypitys 8

44 Syntaksi 9

441 Funktiot 9

442 Silmukat 9

443 Luokat 10

45 PEP 8 10

5 DJANGO 11

51 Historia 11

52 MVT-arkkitehtuuri 12

53 Mallit 12

531 Mallien maumlaumlritys 13

532 Tietokantakyselyiden tekeminen 13

54 Naumlkymaumlt 13

541 Funktiopohjaiset naumlkymaumlt 14

542 Luokkapohjaiset naumlkymaumlt 14

55 URL-maumlaumlritys 14

56 Sivupohjat 15

561 Sivupohjien syntaksi 15

562 Periytyminen 16

563 Include 17

57 Middleware 17

58 Admin-sivusto 18

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT 20

61 Cmder 20

62 Git ja GitHub 20

63 Virtualenv ja virtualenvwrapper 21

64 Visual Studio Code 21

7 PROJEKTIN TOTEUTUS 21

71 Gitin kaumlyttoumloumlnotto 22

72 Virtualenvwrapperin kaumlyttoumloumlnotto 23

73 Djangon asennus ja uuden projektin luominen 24

74 Templates- ja static-kansioiden luonti 25

75 Basehtml-sivupohja 26

76 Accounts-sovellus 26

77 Auth-sovelluksen kaumlyttoumloumlnotto 27

78 Sivupohjat auth-sovelluksen naumlkymille 27

79 Rekisteroumlinti 28

791 Lomake 28

792 Naumlkymauml 29

793 Sivupohja 30

710 Profiili 31

711 Kaumlyttaumljaumltilin muokkaus 31

712 Shortener-sovellus 33

713 ShortenedURL-malli 33

714 Osoitteen lyhennys 34

7141 Algoritmi 34

7142 Naumlkymauml 34

715 Detail-naumlkymauml 36

716 Lyhyiden osoitteiden uudelleenohjaus 37

717 Lyhennettyjen osoitteiden hallintapaneeli 37

718 Lyhennettyjen osoitteiden poistaminen 38

719 Linkin kopioiminen 40

8 YHTEENVETO 40

81 Haasteet 41

82 Jatkokehitys 41

LAumlHTEET 42

1

1 JOHDANTO

Taumlssauml opinnaumlytetyoumlssauml tutustutaan web-sovelluksen rakentamiseen Pyt-hon-ohjelmointikieltauml kaumlyttaumlvaumlllauml Django-sovelluskehyksellauml Tyoumln alussa kaumlydaumlaumln laumlpi yleisesti mitauml web-sovelluksella ja sovelluskehyksellauml tarkoi-tetaan ja mitauml ominaisuuksia eri sovelluskehykset tavallisesti sisaumlltaumlvaumlt Taumlmaumln jaumllkeen tutustutaan taumlmaumln hetken suosituimpiin web-sovelluske-hyksiin Sitten tyoumlssauml perehdytaumlaumln Python-ohjelmointikieleen ja tarkem-min Djangon ominaisuuksiin ja toimintaperiaatteisiin Projektiosuuden alussa kaumlydaumlaumln laumlpi projektissa kaumlytettaumlvaumlt tyoumlkalut ja kehitysympaumlristouml Opinnaumlytetyoumln tavoitteena oli luoda kaumlyttoumlvalmis pitkien linkkien lyhentauml-miseen keskittynyt web-sovellus Djangolla Sovelluksen vaatimuksina oli linkkien lyhentaumlmisen lisaumlksi tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnoin-tiin Opinnaumlytetyoumlssauml dokumentoitiin sovelluksen keskeisimmaumlt ominai-suudet Tyoumln lopussa pohditaan mahdollisia jatkokehitykseen liittyviauml ide-oita ja kaumlydaumlaumln laumlpi tyoumlssauml ilmenneitauml ongelmia Opinnaumlytetyouml on suunnattu henkiloumlille jotka ovat kiinnostuneita web-so-vellusten kehityksestauml tai yleisesti web-sovelluskehyksistauml Opinnaumlytetyouml olettaa lukijalta jonkin tasoista ymmaumlrrystauml ohjelmoinnin perusteista

2 WEB-SOVELLUKSET

Web-sovelluksella tarkoitetaan sovellusta joka suoritetaan kaumlyttaumlen web-selainta Tavallisesta www-sivusta web-sovellus eroaa siten ettauml se sisaumll-taumlauml dynaamista ja kaumlyttaumljaumln syoumltteen perusteella muuttuvaa dataa Web-sovellus koostuu tavallisesti kaumlyttaumljaumln selaimessa suoritettavasta HTML CSS ndash ja JavaScript-koodista ja se keskustelee palvelimella olevan koodin kanssa Web-sovellus voi toimia myoumls kokonaan ilman palvelinta Esi-merkki taumlllaisesta sovelluksesta olisi JavaScriptilla tehty laskin jonka omi-naisuudet eivaumlt tarvitse palvelimella olevaa tietokantaa tai laskentatehoa (Ndegwa 2016) Web-sovelluksen HTML-rakenne ja sisaumlltouml voidaan renderoumlidauml joko palve-limella tai vasta kaumlyttaumljaumln selaimessa Renderoumlinnillauml tarkoitetaan kaumlyttauml-jaumln selaimessa lopulta naumlkyvaumln HTML-sivuston rakentamista yhdestauml tai useammasta osasta Kummallakin tavalla on omat hyoumldyt ja haitat ja so-velluksen kaumlyttoumltarkoitus maumlaumlrittaumlauml yleensauml sen kumpaa tapaa kannattaa kaumlyttaumlauml (Ndegwa 2016) Yleisin tapa on rakentaa valmis HTML-dokumentti kokonaan palvelimella jonka jaumllkeen se laumlhetetaumlaumln sellaisenaan kaumlyttaumljaumllle Taumlmauml tapa soveltuu

2

hyvin sivustoille ja sovelluksille jotka eivaumlt sisaumlllauml isoja maumlaumlriauml dynaamista sisaumlltoumlauml Taumlllaiset sivustot on myoumls helpompi hakukoneoptimoida koska tavallisimmat hakukoneiden kaumlyttaumlmaumlt botit eivaumlt pysty renderoumlimaumlaumln si-saumlltoumlauml paikallisesti (Vega 2017) Kun sivun HTML on tarkoitus renderoumlidauml paumlaumlasiassa selaimella palvelin lauml-hettaumlauml vain pienen paumltkaumln HTML-koodia JavaScript-koodin lisaumlksi Loput HTMLstauml rakennetaan vasta kaumlyttaumljaumln selaimessa JavaScript-koodin avulla JavaScript-koodin ja sen laumlhettaumlmien palvelinkutsujen ajamisessa kestaumlauml hetken jonka takia dynaamisen sisaumllloumln tilalla naumlkyy usein placehol-der-sisaumlltoumlauml (Vega 2017)

3 WEB-SOVELLUSKEHYKSET

Sovelluskehyksellauml tarkoitetaan ohjelmistoa ja kirjastoja jotka muodosta-vat rungon sen paumlaumllle rakennettavalle sovellukselle Sovelluskehys ei siis ole valmis kaumlytettaumlvissauml oleva ohjelma vaan toimii ainoastaan pohjana si-saumlltaumlen yleisimmin tarvittavat ominaisuudet Sovelluskehyksen taumlrkeim-paumlnauml tehtaumlvaumlnauml on vaumlhentaumlauml usein kaumlytettyjen ominaisuuksien ohjelmoin-tia uudelleen eli ne sisaumlltaumlvaumlt valmiiksi perustoiminnallisuudet ja naumlin ly-hentaumlvaumlt sovelluksen kehitykseen menevaumlauml aikaa merkittaumlvaumlsti (Niemi 2015)

Web-sovelluskehykset ovat nimensauml mukaisesti web-sovelluksien tekoon tarkoitettuja sovelluskehyksiauml Web-sovelluskehyksiauml on tehty laumlhes jokai-selle suositulle ohjelmointikielelle matalan tason C++-kielestauml korkean ta-son C-kieleen Taumlllauml hetkellauml yleisimmaumlt web-sovelluksien tekoon kaumlytet-taumlvaumlt kielet ovat C PHP Java JavaScript Python ja Ruby Web-sovellus-kehykset voidaan jakaa kahteen eri kategoriaan sen perusteella toimivatko ne asiakaspuolella vai palvelinpuolella (Niemi 2015)

31 Mikrosovelluskehykset

Massiivisten sovelluskehysten lisaumlksi on olemassa myoumls pienempiauml sovel-luskehyksiauml jotka sisaumlltaumlvaumlt hyvin vaumlhaumln sisaumlaumlnrakennettuja toimintoja ja ominaisuuksia Naumlitauml kaumlyttaumlen myoumls projektin rakenne on vapaampi jonka ansiosta ohjelmoijalla on vapaammat kaumldet sovelluksen suhteen Yksi esi-merkki taumlllaisesta sovelluskehyksestauml on Python-pohjainen Flask jossa lauml-hes jokainen ominaisuus taumlytyy tehdauml itse tai asentaa erikseen laajennuk-sena Myoumls erittaumlin suosittu Nodejs-pohjainen Expressjs lukeutuu samaan kategoriaan (Ronacher 2017)

3

32 Suosituimmat web-sovelluskehykset

Perehdyin eri web-sovelluskehysten suosioon HotFrameworks on sivusto joka mittaa eri sovelluskehysten suosiota niiden GitHub- ja Stack Overflow -aktiivisuuden perusteella Microsoftin sovelluskehysten GitHub-pisteet kuitenkin puuttuivat kokonaan jonka takia niiden todellinen suosio on to-dennaumlkoumlisesti HotFrameworks-sivuston tuloksia pienempi (Kuva 1) Muilta osin pidin HotFrameworks-sivuston tuloksia luotettavina (HotFrameworks 2018)

Kuva 1 Suosituimmat web-sovelluskehykset HotFrameworks-sivuston mukaan

33 Asiakaspuolen web-sovelluskehykset

Asiakaspuolen web-sovelluskehyksillauml rakennetaan paumlaumlasiassa selaimessa toimivia JavaScript-pohjaisia sovelluksia Ne tavallisesti keskittyvaumlt dynaa-misen datan kaumlsittelyn helpottamiseen ja sen naumlyttaumlmiseen HTML-sivulla Palvelimien ja tietokantojen kanssa keskustelemiseen asiakaspuolen sovel-lukset kaumlyttaumlvaumlt REST-rajapintoja Kaumlytaumlnnoumlssauml kaikki asiakaspuolen web-sovelluskehykset kaumlyttaumlvaumlt JavaScriptia tai jotain siihen pohjautuvaa kieltauml

331 AngularJS ja Angular

AngularJS ja Angular ovat Googlen yllaumlpitaumlmiauml avointa laumlhdekoodia olevia asiakaspuolen sovelluksien rakentamiseen tarkoitettuja sovelluskehyksiauml

4

AngularJS-nimellauml tarkoitetaan vanhempaa 1x-versiota ja pelkaumlllauml Angu-lar-nimellauml tarkoitetaan uudelleenkirjoituksen jaumllkeisiauml versioita (Angular nd) AngularJS on Miško Heveryn kehittaumlmauml sovelluskehys joka oli aluksi vain apuna Googlen sisaumlisissauml projekteissa Sovelluskehyksen ensimmaumlinen jul-kinen versio julkaistiin vuonna 2010 avoimen laumlhdekoodin projektina An-gularJS oli kuitenkin ominaisuuksiltaan rajattu eikauml se soveltunut kunnolla isojen sovellusten rakentamiseen (Gavigan 2018) Sovelluskehykselle tehtiin kokonainen uudelleenkirjoitus josta vastasi Google Uusi versio julkaistiin vuonna 2016 versionumerolla 20 Uudel-leenkirjoituksesta johtuen syntaksi ja sovellusten rakenne oli erilainen Se ei myoumlskaumlaumln ollut taaksepaumlin yhteensopiva vanhempien AngularJS-sovel-lusten kanssa Uudelleenkirjoitettu versio kaumlytti oletuksena JavaScriptin si-jasta TypeScriptia joka lisaumlauml JavaScriptiin uusia ominaisuuksia (Gavigan 2018)

332 React

React on Facebookin yllaumlpitaumlmauml JavaScript-pohjainen kaumlyttoumlliittymien te-koon tarkoitettu sovelluskehys Ensimmaumlinen julkinen versio Reactista jul-kaistiin vuonna 2013 Web-sovellusten lisaumlksi Reactia voi kaumlyttaumlauml mobiiliso-vellusten tekoon Mobiilisovellusten tekoon tarkoitettu kirjasto on nimel-taumlaumln React Native Virtuaalisen DOMin ansiosta dynaamisen ja muuttuvan datan esittaumlminen on erittaumlin nopeaa Kun ainoastaan yhtauml sivun elementtiauml muutetaan riit-taumlauml ettauml ainoastaan kyseinen elementti renderoumlidaumlaumln uudelleen Tavallista DOMia kaumlytettaumlessauml koko sivu pitaumlisi renderoumlidauml uudelleen (Reactjs nd) React-sovellukset rakentuvat komponenteista Komponentit kaumlyttaumlvaumlt JSX-syntaksia jonka avulla voi sekoittaa JavaScript- ja HTML-koodia keske-naumlaumln Taumlmaumln avulla voi esimerkiksi asettaa funktion palauttamaan HTML-elementtejauml mikauml helpottaa ja nopeuttaa kehitystauml (Reactjs nd)

333 Vue

Vue on avointa laumlhdekoodia oleva asiakaspuolen sovelluksien kehittaumlmi-seen tarkoitettu sovelluskehys Vuen mukana tuleva ydinkirjasto keskittyy paumlaumlasiassa naumlkymien rakentamiseen jonka takia sitauml on helppo kaumlyttaumlauml myoumls muiden sovelluskehysten ja tekniikoiden kanssa Pelkaumlllauml Vuella voi myoumls tehdauml kokonaisia SPA-sovelluksia mutta se vaatii ydinkirjaston lisaumlksi muita kirjastoja Muut kirjastot hoitavat esimerkiksi URL-reititykset (Vuejs nd a)

5

Ominaisuuksiltaan Vue on samankaltainen Reactin kanssa Molemmat hyoumldyntaumlvaumlt virtuaalista DOMia ja sovelluksen eri osat rakentuvat kom-ponenteista Vue tukee myoumls tarvittaessa Reactista tuttua JSX-syntaksia vaikkakin oletuksena kaumlytoumlssauml on yksinkertaisempi HTML-pohjainen sivu-pohja-jaumlrjestelmauml (Vuejs nd b)

34 Palvelinpuolen web-sovelluskehykset

Palvelinpuolen web-sovelluskehykset ovat tavallisesti asiakaspuolen web-sovelluskehyksiauml isompia koska ne tarvitsevat enemmaumln eri ominaisuuk-sia Palvelimella on tavallisesti yhteys tietokantaan joka jo sinaumlllaumlaumln aset-taa haasteita muun muassa tietoturvan suhteen Palvelimen taumlytyy myoumls pystyauml palvelemaan useaa kaumlyttaumljaumlauml samanaikaisesti Palvelinpuolen web-sovelluskehykset sisaumlltaumlvaumlt yleensauml vaumlhintaumlaumln seuraa-vat ominaisuudet sisaumlaumlnrakennettuina - tuki HTTP-pyyntoumljen vastaanottamiseen ja laumlhettaumlmiseen - tuki REST-rajapintojen tekoon - kaumlyttaumljaumltilit - lomakkeet ja niiden validointi - vaumllimuisti ja istunnot - sivupohjamoottori - tuki monelle eri tietokannalle - ORM-jaumlrjestelmauml - MVC-arkkitehtuuri tai vastaava - testaustyoumlkalut - suojaus yleisiauml haavoittuvuuksia vastaan (Niemi 2015)

341 ASPNET Core

ASPNET Core on Microsoftin kehittaumlmauml ja markkinoima web-sovelluske-hys Se on ASPNET-sovelluskehysten uusin ja modernein iteraatio ja se jul-kaistiin vuonna 2016 ASPNET Core on avointa laumlhdekoodia ja toi muka-naan huomattavasti aiempaa paremman tuen eri kaumlyttoumljaumlrjestelmille Mic-rosoftin lisaumlksi myoumls kaumlyttaumljaumlt paumlaumlsevaumlt osallistumaan tuotteen jatkokehi-tykseen ASPNET Core on rakennettu Common Language Runtimen paumlaumllle joten se tukee kaikkia NET kieliauml joista yleisimmaumlt ovat C ja Visual Basic NET (Microsoft 2018)

342 Express

Express on Nodejs-pohjainen mikrosovelluskehys Nodejs-pohjan ansi-osta se toimii asynkronisesti joka tekee siitauml toiminnaltaan varsin erilaisen muihin taumlssauml tyoumlssauml mainittuihin web-sovelluskehyksiin verrattuna Ex-press on projektin arkkitehtuurin kannalta hyvin avoin eikauml tee juurikaan paumlaumltoumlksiauml ohjelmoijan puolesta

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 2: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

TIIVISTELMAuml Tietotekniikan koulutusohjelma Riihimaumlki Tekijauml Tommi Vuori Vuosi 2018 Tyoumln nimi Web-sovelluksen teko Djangolla Tyoumln ohjaaja t Petri Kuittinen

TIIVISTELMAuml

Taumlssauml opinnaumlytetyoumlssauml tutustuttiin web-sovelluksien kehittaumlmiseen tarkoi-tettuun Django-sovelluskehykseen ja sen kaumlyttaumlmaumlaumln Python-ohjelmointi-kieleen Naumliden lisaumlksi tyoumlssauml kaumlytiin laumlpi yleisesti mitauml web-sovellukset ovat ja mitauml ominaisuuksia web-sovelluskehykset tavallisesti sisaumlltaumlvaumlt Projektina toteutettiin esimerkkisovellus Djangolla Sovelluksena toimi ominaisuuksiltaan melko rajattu linkin lyhentaumlmiseen tarkoitettu web-so-vellus Sovelluksen visuaalisen ilmeen toteutukseen kaumlytettiin Bootstrap-kirjastoa Ennen opinnaumlytetyoumltauml olin kaumlynyt laumlpi muutamia tutoriaaleja Djangolla ja tutustunut myoumls muihin sovelluskehyksiin Minulla oli myoumls hieman kokemusta Bootstrap-kirjastosta Opinnaumlytetyoumln tuloksena saatiin tehtyauml responsiivinen linkin lyhentaumlmi-seen tarkoitettu web-sovellus Valmis sovellus oli ominaisuuksiltaan melko rajattu sisaumlltaumlen vain sovelluksen toiminnan kannalta oleellisimmat omi-naisuudet Linkin lyhentaumlmisen lisaumlksi sovelluksesta loumlytyi tuki kaumlyttaumljaumlti-leille ja linkkien hallintaan tarkoitettu hallintapaneeli Jatkokehitystauml aja-tellen sovellus oli helposti laajennettavissa isommaksi kokonaisuudeksi

Avainsanat Django Python web-sovellus web-sovelluskehys Sivut 44 sivua

ABSTRACT Information Technology Riihimaumlki Author Tommi Vuori Year 2018 Subject Creating a web application with Django Supervisors Petri Kuittinen ABSTRACT

The focus of this thesis was on the Django web framework and the Python programming language it uses In addition to these the thesis takes a gen-eral look at what web applications are and what features are usually in-cluded in the current popular web frameworks As the practical part of the thesis a link shortening application was created using Django Bootstrap was used for the visual interface of the applica-tion Prior to the thesis I had gone through a couple of Django tutorials and introduced myself to other popular frameworks I also had some experi-ence with Bootstrap The result of the thesis was a working and responsive link shortening ap-plication The application was quite limited in functionality including only the most important features In addition to link shortening the application has support for user accounts and contains a link management dashboard The current application would be easy to extend into a more complete and bigger application with further development

Keywords Django Python web application web framework Pages 44 pages

SANASTO

CSS Cascading Style Sheets Web-sivujen ulkoasun ja visuaalisen il-meen maumlaumlrittaumlmiseen tarkoitettu kieli

DOM Document Object Model Malli jonka avulla HTML-sivua on mah-dollista muokata JavaScriptilla

HTML Hypertext Markup Langu-age

Merkintaumlkieli jolla maumlaumlritetaumlaumln sivus-ton rakenne ja sisaumlltouml

HTTP Hypertext Transfer Protocol Protokolla jota selaimet ja palvelimet kaumlyttaumlvaumlt tiedonsiirtoon

JSX JavaScript XML Laajennus tavallisen JavaScript-ohjel-mointikielen syntaksiin

MVC Model-View-Controller Ohjelmistoarkkitehtuuri jonka tarkoi-tuksena on erottaa malli naumlkymauml ja kauml-sittelijauml toisistaan

MVT Model-View-Template Djangon versio suositusta MVC-arkkitehtuurista

ORM Object-relational mapping Tekniikka jonka avulla tietokantoja voi kaumlyttaumlauml oliomaisesti

REST Representational State Transfer

HTTP-protokollaan perustuva tapa raja-pintojen toteuttamiseen

SPA Single-Page Application Sovellus joka toimii usein vain yhdellauml sivun latauksella Data ja sisaumlltouml lada-taan ja esitetaumlaumln dynaamisesti

SQL Structured Query Language Kyselykieli jota kaumlytaumlnnoumlssauml kaikki re-laatiotietokannat kaumlyttaumlvaumlt

XML Extensible Markup Langu-age

Tietynlaisten merkintaumlkielten standardi

SISAumlLLYS

1 JOHDANTO 1

2 WEB-SOVELLUKSET 1

3 WEB-SOVELLUSKEHYKSET 2

31 Mikrosovelluskehykset 2

32 Suosituimmat web-sovelluskehykset 3

33 Asiakaspuolen web-sovelluskehykset 3

331 AngularJS ja Angular 3

332 React 4

333 Vue 4

34 Palvelinpuolen web-sovelluskehykset 5

341 ASPNET Core 5

342 Express 5

343 Flask 6

344 Laravel 6

345 Ruby on Rails 6

4 PYTHON-OHJELMOINTIKIELI 7

41 Pythonin suosio 7

42 Tulkattavuus 8

43 Tyypitys 8

44 Syntaksi 9

441 Funktiot 9

442 Silmukat 9

443 Luokat 10

45 PEP 8 10

5 DJANGO 11

51 Historia 11

52 MVT-arkkitehtuuri 12

53 Mallit 12

531 Mallien maumlaumlritys 13

532 Tietokantakyselyiden tekeminen 13

54 Naumlkymaumlt 13

541 Funktiopohjaiset naumlkymaumlt 14

542 Luokkapohjaiset naumlkymaumlt 14

55 URL-maumlaumlritys 14

56 Sivupohjat 15

561 Sivupohjien syntaksi 15

562 Periytyminen 16

563 Include 17

57 Middleware 17

58 Admin-sivusto 18

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT 20

61 Cmder 20

62 Git ja GitHub 20

63 Virtualenv ja virtualenvwrapper 21

64 Visual Studio Code 21

7 PROJEKTIN TOTEUTUS 21

71 Gitin kaumlyttoumloumlnotto 22

72 Virtualenvwrapperin kaumlyttoumloumlnotto 23

73 Djangon asennus ja uuden projektin luominen 24

74 Templates- ja static-kansioiden luonti 25

75 Basehtml-sivupohja 26

76 Accounts-sovellus 26

77 Auth-sovelluksen kaumlyttoumloumlnotto 27

78 Sivupohjat auth-sovelluksen naumlkymille 27

79 Rekisteroumlinti 28

791 Lomake 28

792 Naumlkymauml 29

793 Sivupohja 30

710 Profiili 31

711 Kaumlyttaumljaumltilin muokkaus 31

712 Shortener-sovellus 33

713 ShortenedURL-malli 33

714 Osoitteen lyhennys 34

7141 Algoritmi 34

7142 Naumlkymauml 34

715 Detail-naumlkymauml 36

716 Lyhyiden osoitteiden uudelleenohjaus 37

717 Lyhennettyjen osoitteiden hallintapaneeli 37

718 Lyhennettyjen osoitteiden poistaminen 38

719 Linkin kopioiminen 40

8 YHTEENVETO 40

81 Haasteet 41

82 Jatkokehitys 41

LAumlHTEET 42

1

1 JOHDANTO

Taumlssauml opinnaumlytetyoumlssauml tutustutaan web-sovelluksen rakentamiseen Pyt-hon-ohjelmointikieltauml kaumlyttaumlvaumlllauml Django-sovelluskehyksellauml Tyoumln alussa kaumlydaumlaumln laumlpi yleisesti mitauml web-sovelluksella ja sovelluskehyksellauml tarkoi-tetaan ja mitauml ominaisuuksia eri sovelluskehykset tavallisesti sisaumlltaumlvaumlt Taumlmaumln jaumllkeen tutustutaan taumlmaumln hetken suosituimpiin web-sovelluske-hyksiin Sitten tyoumlssauml perehdytaumlaumln Python-ohjelmointikieleen ja tarkem-min Djangon ominaisuuksiin ja toimintaperiaatteisiin Projektiosuuden alussa kaumlydaumlaumln laumlpi projektissa kaumlytettaumlvaumlt tyoumlkalut ja kehitysympaumlristouml Opinnaumlytetyoumln tavoitteena oli luoda kaumlyttoumlvalmis pitkien linkkien lyhentauml-miseen keskittynyt web-sovellus Djangolla Sovelluksen vaatimuksina oli linkkien lyhentaumlmisen lisaumlksi tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnoin-tiin Opinnaumlytetyoumlssauml dokumentoitiin sovelluksen keskeisimmaumlt ominai-suudet Tyoumln lopussa pohditaan mahdollisia jatkokehitykseen liittyviauml ide-oita ja kaumlydaumlaumln laumlpi tyoumlssauml ilmenneitauml ongelmia Opinnaumlytetyouml on suunnattu henkiloumlille jotka ovat kiinnostuneita web-so-vellusten kehityksestauml tai yleisesti web-sovelluskehyksistauml Opinnaumlytetyouml olettaa lukijalta jonkin tasoista ymmaumlrrystauml ohjelmoinnin perusteista

2 WEB-SOVELLUKSET

Web-sovelluksella tarkoitetaan sovellusta joka suoritetaan kaumlyttaumlen web-selainta Tavallisesta www-sivusta web-sovellus eroaa siten ettauml se sisaumll-taumlauml dynaamista ja kaumlyttaumljaumln syoumltteen perusteella muuttuvaa dataa Web-sovellus koostuu tavallisesti kaumlyttaumljaumln selaimessa suoritettavasta HTML CSS ndash ja JavaScript-koodista ja se keskustelee palvelimella olevan koodin kanssa Web-sovellus voi toimia myoumls kokonaan ilman palvelinta Esi-merkki taumlllaisesta sovelluksesta olisi JavaScriptilla tehty laskin jonka omi-naisuudet eivaumlt tarvitse palvelimella olevaa tietokantaa tai laskentatehoa (Ndegwa 2016) Web-sovelluksen HTML-rakenne ja sisaumlltouml voidaan renderoumlidauml joko palve-limella tai vasta kaumlyttaumljaumln selaimessa Renderoumlinnillauml tarkoitetaan kaumlyttauml-jaumln selaimessa lopulta naumlkyvaumln HTML-sivuston rakentamista yhdestauml tai useammasta osasta Kummallakin tavalla on omat hyoumldyt ja haitat ja so-velluksen kaumlyttoumltarkoitus maumlaumlrittaumlauml yleensauml sen kumpaa tapaa kannattaa kaumlyttaumlauml (Ndegwa 2016) Yleisin tapa on rakentaa valmis HTML-dokumentti kokonaan palvelimella jonka jaumllkeen se laumlhetetaumlaumln sellaisenaan kaumlyttaumljaumllle Taumlmauml tapa soveltuu

2

hyvin sivustoille ja sovelluksille jotka eivaumlt sisaumlllauml isoja maumlaumlriauml dynaamista sisaumlltoumlauml Taumlllaiset sivustot on myoumls helpompi hakukoneoptimoida koska tavallisimmat hakukoneiden kaumlyttaumlmaumlt botit eivaumlt pysty renderoumlimaumlaumln si-saumlltoumlauml paikallisesti (Vega 2017) Kun sivun HTML on tarkoitus renderoumlidauml paumlaumlasiassa selaimella palvelin lauml-hettaumlauml vain pienen paumltkaumln HTML-koodia JavaScript-koodin lisaumlksi Loput HTMLstauml rakennetaan vasta kaumlyttaumljaumln selaimessa JavaScript-koodin avulla JavaScript-koodin ja sen laumlhettaumlmien palvelinkutsujen ajamisessa kestaumlauml hetken jonka takia dynaamisen sisaumllloumln tilalla naumlkyy usein placehol-der-sisaumlltoumlauml (Vega 2017)

3 WEB-SOVELLUSKEHYKSET

Sovelluskehyksellauml tarkoitetaan ohjelmistoa ja kirjastoja jotka muodosta-vat rungon sen paumlaumllle rakennettavalle sovellukselle Sovelluskehys ei siis ole valmis kaumlytettaumlvissauml oleva ohjelma vaan toimii ainoastaan pohjana si-saumlltaumlen yleisimmin tarvittavat ominaisuudet Sovelluskehyksen taumlrkeim-paumlnauml tehtaumlvaumlnauml on vaumlhentaumlauml usein kaumlytettyjen ominaisuuksien ohjelmoin-tia uudelleen eli ne sisaumlltaumlvaumlt valmiiksi perustoiminnallisuudet ja naumlin ly-hentaumlvaumlt sovelluksen kehitykseen menevaumlauml aikaa merkittaumlvaumlsti (Niemi 2015)

Web-sovelluskehykset ovat nimensauml mukaisesti web-sovelluksien tekoon tarkoitettuja sovelluskehyksiauml Web-sovelluskehyksiauml on tehty laumlhes jokai-selle suositulle ohjelmointikielelle matalan tason C++-kielestauml korkean ta-son C-kieleen Taumlllauml hetkellauml yleisimmaumlt web-sovelluksien tekoon kaumlytet-taumlvaumlt kielet ovat C PHP Java JavaScript Python ja Ruby Web-sovellus-kehykset voidaan jakaa kahteen eri kategoriaan sen perusteella toimivatko ne asiakaspuolella vai palvelinpuolella (Niemi 2015)

31 Mikrosovelluskehykset

Massiivisten sovelluskehysten lisaumlksi on olemassa myoumls pienempiauml sovel-luskehyksiauml jotka sisaumlltaumlvaumlt hyvin vaumlhaumln sisaumlaumlnrakennettuja toimintoja ja ominaisuuksia Naumlitauml kaumlyttaumlen myoumls projektin rakenne on vapaampi jonka ansiosta ohjelmoijalla on vapaammat kaumldet sovelluksen suhteen Yksi esi-merkki taumlllaisesta sovelluskehyksestauml on Python-pohjainen Flask jossa lauml-hes jokainen ominaisuus taumlytyy tehdauml itse tai asentaa erikseen laajennuk-sena Myoumls erittaumlin suosittu Nodejs-pohjainen Expressjs lukeutuu samaan kategoriaan (Ronacher 2017)

3

32 Suosituimmat web-sovelluskehykset

Perehdyin eri web-sovelluskehysten suosioon HotFrameworks on sivusto joka mittaa eri sovelluskehysten suosiota niiden GitHub- ja Stack Overflow -aktiivisuuden perusteella Microsoftin sovelluskehysten GitHub-pisteet kuitenkin puuttuivat kokonaan jonka takia niiden todellinen suosio on to-dennaumlkoumlisesti HotFrameworks-sivuston tuloksia pienempi (Kuva 1) Muilta osin pidin HotFrameworks-sivuston tuloksia luotettavina (HotFrameworks 2018)

Kuva 1 Suosituimmat web-sovelluskehykset HotFrameworks-sivuston mukaan

33 Asiakaspuolen web-sovelluskehykset

Asiakaspuolen web-sovelluskehyksillauml rakennetaan paumlaumlasiassa selaimessa toimivia JavaScript-pohjaisia sovelluksia Ne tavallisesti keskittyvaumlt dynaa-misen datan kaumlsittelyn helpottamiseen ja sen naumlyttaumlmiseen HTML-sivulla Palvelimien ja tietokantojen kanssa keskustelemiseen asiakaspuolen sovel-lukset kaumlyttaumlvaumlt REST-rajapintoja Kaumlytaumlnnoumlssauml kaikki asiakaspuolen web-sovelluskehykset kaumlyttaumlvaumlt JavaScriptia tai jotain siihen pohjautuvaa kieltauml

331 AngularJS ja Angular

AngularJS ja Angular ovat Googlen yllaumlpitaumlmiauml avointa laumlhdekoodia olevia asiakaspuolen sovelluksien rakentamiseen tarkoitettuja sovelluskehyksiauml

4

AngularJS-nimellauml tarkoitetaan vanhempaa 1x-versiota ja pelkaumlllauml Angu-lar-nimellauml tarkoitetaan uudelleenkirjoituksen jaumllkeisiauml versioita (Angular nd) AngularJS on Miško Heveryn kehittaumlmauml sovelluskehys joka oli aluksi vain apuna Googlen sisaumlisissauml projekteissa Sovelluskehyksen ensimmaumlinen jul-kinen versio julkaistiin vuonna 2010 avoimen laumlhdekoodin projektina An-gularJS oli kuitenkin ominaisuuksiltaan rajattu eikauml se soveltunut kunnolla isojen sovellusten rakentamiseen (Gavigan 2018) Sovelluskehykselle tehtiin kokonainen uudelleenkirjoitus josta vastasi Google Uusi versio julkaistiin vuonna 2016 versionumerolla 20 Uudel-leenkirjoituksesta johtuen syntaksi ja sovellusten rakenne oli erilainen Se ei myoumlskaumlaumln ollut taaksepaumlin yhteensopiva vanhempien AngularJS-sovel-lusten kanssa Uudelleenkirjoitettu versio kaumlytti oletuksena JavaScriptin si-jasta TypeScriptia joka lisaumlauml JavaScriptiin uusia ominaisuuksia (Gavigan 2018)

332 React

React on Facebookin yllaumlpitaumlmauml JavaScript-pohjainen kaumlyttoumlliittymien te-koon tarkoitettu sovelluskehys Ensimmaumlinen julkinen versio Reactista jul-kaistiin vuonna 2013 Web-sovellusten lisaumlksi Reactia voi kaumlyttaumlauml mobiiliso-vellusten tekoon Mobiilisovellusten tekoon tarkoitettu kirjasto on nimel-taumlaumln React Native Virtuaalisen DOMin ansiosta dynaamisen ja muuttuvan datan esittaumlminen on erittaumlin nopeaa Kun ainoastaan yhtauml sivun elementtiauml muutetaan riit-taumlauml ettauml ainoastaan kyseinen elementti renderoumlidaumlaumln uudelleen Tavallista DOMia kaumlytettaumlessauml koko sivu pitaumlisi renderoumlidauml uudelleen (Reactjs nd) React-sovellukset rakentuvat komponenteista Komponentit kaumlyttaumlvaumlt JSX-syntaksia jonka avulla voi sekoittaa JavaScript- ja HTML-koodia keske-naumlaumln Taumlmaumln avulla voi esimerkiksi asettaa funktion palauttamaan HTML-elementtejauml mikauml helpottaa ja nopeuttaa kehitystauml (Reactjs nd)

333 Vue

Vue on avointa laumlhdekoodia oleva asiakaspuolen sovelluksien kehittaumlmi-seen tarkoitettu sovelluskehys Vuen mukana tuleva ydinkirjasto keskittyy paumlaumlasiassa naumlkymien rakentamiseen jonka takia sitauml on helppo kaumlyttaumlauml myoumls muiden sovelluskehysten ja tekniikoiden kanssa Pelkaumlllauml Vuella voi myoumls tehdauml kokonaisia SPA-sovelluksia mutta se vaatii ydinkirjaston lisaumlksi muita kirjastoja Muut kirjastot hoitavat esimerkiksi URL-reititykset (Vuejs nd a)

5

Ominaisuuksiltaan Vue on samankaltainen Reactin kanssa Molemmat hyoumldyntaumlvaumlt virtuaalista DOMia ja sovelluksen eri osat rakentuvat kom-ponenteista Vue tukee myoumls tarvittaessa Reactista tuttua JSX-syntaksia vaikkakin oletuksena kaumlytoumlssauml on yksinkertaisempi HTML-pohjainen sivu-pohja-jaumlrjestelmauml (Vuejs nd b)

34 Palvelinpuolen web-sovelluskehykset

Palvelinpuolen web-sovelluskehykset ovat tavallisesti asiakaspuolen web-sovelluskehyksiauml isompia koska ne tarvitsevat enemmaumln eri ominaisuuk-sia Palvelimella on tavallisesti yhteys tietokantaan joka jo sinaumlllaumlaumln aset-taa haasteita muun muassa tietoturvan suhteen Palvelimen taumlytyy myoumls pystyauml palvelemaan useaa kaumlyttaumljaumlauml samanaikaisesti Palvelinpuolen web-sovelluskehykset sisaumlltaumlvaumlt yleensauml vaumlhintaumlaumln seuraa-vat ominaisuudet sisaumlaumlnrakennettuina - tuki HTTP-pyyntoumljen vastaanottamiseen ja laumlhettaumlmiseen - tuki REST-rajapintojen tekoon - kaumlyttaumljaumltilit - lomakkeet ja niiden validointi - vaumllimuisti ja istunnot - sivupohjamoottori - tuki monelle eri tietokannalle - ORM-jaumlrjestelmauml - MVC-arkkitehtuuri tai vastaava - testaustyoumlkalut - suojaus yleisiauml haavoittuvuuksia vastaan (Niemi 2015)

341 ASPNET Core

ASPNET Core on Microsoftin kehittaumlmauml ja markkinoima web-sovelluske-hys Se on ASPNET-sovelluskehysten uusin ja modernein iteraatio ja se jul-kaistiin vuonna 2016 ASPNET Core on avointa laumlhdekoodia ja toi muka-naan huomattavasti aiempaa paremman tuen eri kaumlyttoumljaumlrjestelmille Mic-rosoftin lisaumlksi myoumls kaumlyttaumljaumlt paumlaumlsevaumlt osallistumaan tuotteen jatkokehi-tykseen ASPNET Core on rakennettu Common Language Runtimen paumlaumllle joten se tukee kaikkia NET kieliauml joista yleisimmaumlt ovat C ja Visual Basic NET (Microsoft 2018)

342 Express

Express on Nodejs-pohjainen mikrosovelluskehys Nodejs-pohjan ansi-osta se toimii asynkronisesti joka tekee siitauml toiminnaltaan varsin erilaisen muihin taumlssauml tyoumlssauml mainittuihin web-sovelluskehyksiin verrattuna Ex-press on projektin arkkitehtuurin kannalta hyvin avoin eikauml tee juurikaan paumlaumltoumlksiauml ohjelmoijan puolesta

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 3: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

ABSTRACT Information Technology Riihimaumlki Author Tommi Vuori Year 2018 Subject Creating a web application with Django Supervisors Petri Kuittinen ABSTRACT

The focus of this thesis was on the Django web framework and the Python programming language it uses In addition to these the thesis takes a gen-eral look at what web applications are and what features are usually in-cluded in the current popular web frameworks As the practical part of the thesis a link shortening application was created using Django Bootstrap was used for the visual interface of the applica-tion Prior to the thesis I had gone through a couple of Django tutorials and introduced myself to other popular frameworks I also had some experi-ence with Bootstrap The result of the thesis was a working and responsive link shortening ap-plication The application was quite limited in functionality including only the most important features In addition to link shortening the application has support for user accounts and contains a link management dashboard The current application would be easy to extend into a more complete and bigger application with further development

Keywords Django Python web application web framework Pages 44 pages

SANASTO

CSS Cascading Style Sheets Web-sivujen ulkoasun ja visuaalisen il-meen maumlaumlrittaumlmiseen tarkoitettu kieli

DOM Document Object Model Malli jonka avulla HTML-sivua on mah-dollista muokata JavaScriptilla

HTML Hypertext Markup Langu-age

Merkintaumlkieli jolla maumlaumlritetaumlaumln sivus-ton rakenne ja sisaumlltouml

HTTP Hypertext Transfer Protocol Protokolla jota selaimet ja palvelimet kaumlyttaumlvaumlt tiedonsiirtoon

JSX JavaScript XML Laajennus tavallisen JavaScript-ohjel-mointikielen syntaksiin

MVC Model-View-Controller Ohjelmistoarkkitehtuuri jonka tarkoi-tuksena on erottaa malli naumlkymauml ja kauml-sittelijauml toisistaan

MVT Model-View-Template Djangon versio suositusta MVC-arkkitehtuurista

ORM Object-relational mapping Tekniikka jonka avulla tietokantoja voi kaumlyttaumlauml oliomaisesti

REST Representational State Transfer

HTTP-protokollaan perustuva tapa raja-pintojen toteuttamiseen

SPA Single-Page Application Sovellus joka toimii usein vain yhdellauml sivun latauksella Data ja sisaumlltouml lada-taan ja esitetaumlaumln dynaamisesti

SQL Structured Query Language Kyselykieli jota kaumlytaumlnnoumlssauml kaikki re-laatiotietokannat kaumlyttaumlvaumlt

XML Extensible Markup Langu-age

Tietynlaisten merkintaumlkielten standardi

SISAumlLLYS

1 JOHDANTO 1

2 WEB-SOVELLUKSET 1

3 WEB-SOVELLUSKEHYKSET 2

31 Mikrosovelluskehykset 2

32 Suosituimmat web-sovelluskehykset 3

33 Asiakaspuolen web-sovelluskehykset 3

331 AngularJS ja Angular 3

332 React 4

333 Vue 4

34 Palvelinpuolen web-sovelluskehykset 5

341 ASPNET Core 5

342 Express 5

343 Flask 6

344 Laravel 6

345 Ruby on Rails 6

4 PYTHON-OHJELMOINTIKIELI 7

41 Pythonin suosio 7

42 Tulkattavuus 8

43 Tyypitys 8

44 Syntaksi 9

441 Funktiot 9

442 Silmukat 9

443 Luokat 10

45 PEP 8 10

5 DJANGO 11

51 Historia 11

52 MVT-arkkitehtuuri 12

53 Mallit 12

531 Mallien maumlaumlritys 13

532 Tietokantakyselyiden tekeminen 13

54 Naumlkymaumlt 13

541 Funktiopohjaiset naumlkymaumlt 14

542 Luokkapohjaiset naumlkymaumlt 14

55 URL-maumlaumlritys 14

56 Sivupohjat 15

561 Sivupohjien syntaksi 15

562 Periytyminen 16

563 Include 17

57 Middleware 17

58 Admin-sivusto 18

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT 20

61 Cmder 20

62 Git ja GitHub 20

63 Virtualenv ja virtualenvwrapper 21

64 Visual Studio Code 21

7 PROJEKTIN TOTEUTUS 21

71 Gitin kaumlyttoumloumlnotto 22

72 Virtualenvwrapperin kaumlyttoumloumlnotto 23

73 Djangon asennus ja uuden projektin luominen 24

74 Templates- ja static-kansioiden luonti 25

75 Basehtml-sivupohja 26

76 Accounts-sovellus 26

77 Auth-sovelluksen kaumlyttoumloumlnotto 27

78 Sivupohjat auth-sovelluksen naumlkymille 27

79 Rekisteroumlinti 28

791 Lomake 28

792 Naumlkymauml 29

793 Sivupohja 30

710 Profiili 31

711 Kaumlyttaumljaumltilin muokkaus 31

712 Shortener-sovellus 33

713 ShortenedURL-malli 33

714 Osoitteen lyhennys 34

7141 Algoritmi 34

7142 Naumlkymauml 34

715 Detail-naumlkymauml 36

716 Lyhyiden osoitteiden uudelleenohjaus 37

717 Lyhennettyjen osoitteiden hallintapaneeli 37

718 Lyhennettyjen osoitteiden poistaminen 38

719 Linkin kopioiminen 40

8 YHTEENVETO 40

81 Haasteet 41

82 Jatkokehitys 41

LAumlHTEET 42

1

1 JOHDANTO

Taumlssauml opinnaumlytetyoumlssauml tutustutaan web-sovelluksen rakentamiseen Pyt-hon-ohjelmointikieltauml kaumlyttaumlvaumlllauml Django-sovelluskehyksellauml Tyoumln alussa kaumlydaumlaumln laumlpi yleisesti mitauml web-sovelluksella ja sovelluskehyksellauml tarkoi-tetaan ja mitauml ominaisuuksia eri sovelluskehykset tavallisesti sisaumlltaumlvaumlt Taumlmaumln jaumllkeen tutustutaan taumlmaumln hetken suosituimpiin web-sovelluske-hyksiin Sitten tyoumlssauml perehdytaumlaumln Python-ohjelmointikieleen ja tarkem-min Djangon ominaisuuksiin ja toimintaperiaatteisiin Projektiosuuden alussa kaumlydaumlaumln laumlpi projektissa kaumlytettaumlvaumlt tyoumlkalut ja kehitysympaumlristouml Opinnaumlytetyoumln tavoitteena oli luoda kaumlyttoumlvalmis pitkien linkkien lyhentauml-miseen keskittynyt web-sovellus Djangolla Sovelluksen vaatimuksina oli linkkien lyhentaumlmisen lisaumlksi tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnoin-tiin Opinnaumlytetyoumlssauml dokumentoitiin sovelluksen keskeisimmaumlt ominai-suudet Tyoumln lopussa pohditaan mahdollisia jatkokehitykseen liittyviauml ide-oita ja kaumlydaumlaumln laumlpi tyoumlssauml ilmenneitauml ongelmia Opinnaumlytetyouml on suunnattu henkiloumlille jotka ovat kiinnostuneita web-so-vellusten kehityksestauml tai yleisesti web-sovelluskehyksistauml Opinnaumlytetyouml olettaa lukijalta jonkin tasoista ymmaumlrrystauml ohjelmoinnin perusteista

2 WEB-SOVELLUKSET

Web-sovelluksella tarkoitetaan sovellusta joka suoritetaan kaumlyttaumlen web-selainta Tavallisesta www-sivusta web-sovellus eroaa siten ettauml se sisaumll-taumlauml dynaamista ja kaumlyttaumljaumln syoumltteen perusteella muuttuvaa dataa Web-sovellus koostuu tavallisesti kaumlyttaumljaumln selaimessa suoritettavasta HTML CSS ndash ja JavaScript-koodista ja se keskustelee palvelimella olevan koodin kanssa Web-sovellus voi toimia myoumls kokonaan ilman palvelinta Esi-merkki taumlllaisesta sovelluksesta olisi JavaScriptilla tehty laskin jonka omi-naisuudet eivaumlt tarvitse palvelimella olevaa tietokantaa tai laskentatehoa (Ndegwa 2016) Web-sovelluksen HTML-rakenne ja sisaumlltouml voidaan renderoumlidauml joko palve-limella tai vasta kaumlyttaumljaumln selaimessa Renderoumlinnillauml tarkoitetaan kaumlyttauml-jaumln selaimessa lopulta naumlkyvaumln HTML-sivuston rakentamista yhdestauml tai useammasta osasta Kummallakin tavalla on omat hyoumldyt ja haitat ja so-velluksen kaumlyttoumltarkoitus maumlaumlrittaumlauml yleensauml sen kumpaa tapaa kannattaa kaumlyttaumlauml (Ndegwa 2016) Yleisin tapa on rakentaa valmis HTML-dokumentti kokonaan palvelimella jonka jaumllkeen se laumlhetetaumlaumln sellaisenaan kaumlyttaumljaumllle Taumlmauml tapa soveltuu

2

hyvin sivustoille ja sovelluksille jotka eivaumlt sisaumlllauml isoja maumlaumlriauml dynaamista sisaumlltoumlauml Taumlllaiset sivustot on myoumls helpompi hakukoneoptimoida koska tavallisimmat hakukoneiden kaumlyttaumlmaumlt botit eivaumlt pysty renderoumlimaumlaumln si-saumlltoumlauml paikallisesti (Vega 2017) Kun sivun HTML on tarkoitus renderoumlidauml paumlaumlasiassa selaimella palvelin lauml-hettaumlauml vain pienen paumltkaumln HTML-koodia JavaScript-koodin lisaumlksi Loput HTMLstauml rakennetaan vasta kaumlyttaumljaumln selaimessa JavaScript-koodin avulla JavaScript-koodin ja sen laumlhettaumlmien palvelinkutsujen ajamisessa kestaumlauml hetken jonka takia dynaamisen sisaumllloumln tilalla naumlkyy usein placehol-der-sisaumlltoumlauml (Vega 2017)

3 WEB-SOVELLUSKEHYKSET

Sovelluskehyksellauml tarkoitetaan ohjelmistoa ja kirjastoja jotka muodosta-vat rungon sen paumlaumllle rakennettavalle sovellukselle Sovelluskehys ei siis ole valmis kaumlytettaumlvissauml oleva ohjelma vaan toimii ainoastaan pohjana si-saumlltaumlen yleisimmin tarvittavat ominaisuudet Sovelluskehyksen taumlrkeim-paumlnauml tehtaumlvaumlnauml on vaumlhentaumlauml usein kaumlytettyjen ominaisuuksien ohjelmoin-tia uudelleen eli ne sisaumlltaumlvaumlt valmiiksi perustoiminnallisuudet ja naumlin ly-hentaumlvaumlt sovelluksen kehitykseen menevaumlauml aikaa merkittaumlvaumlsti (Niemi 2015)

Web-sovelluskehykset ovat nimensauml mukaisesti web-sovelluksien tekoon tarkoitettuja sovelluskehyksiauml Web-sovelluskehyksiauml on tehty laumlhes jokai-selle suositulle ohjelmointikielelle matalan tason C++-kielestauml korkean ta-son C-kieleen Taumlllauml hetkellauml yleisimmaumlt web-sovelluksien tekoon kaumlytet-taumlvaumlt kielet ovat C PHP Java JavaScript Python ja Ruby Web-sovellus-kehykset voidaan jakaa kahteen eri kategoriaan sen perusteella toimivatko ne asiakaspuolella vai palvelinpuolella (Niemi 2015)

31 Mikrosovelluskehykset

Massiivisten sovelluskehysten lisaumlksi on olemassa myoumls pienempiauml sovel-luskehyksiauml jotka sisaumlltaumlvaumlt hyvin vaumlhaumln sisaumlaumlnrakennettuja toimintoja ja ominaisuuksia Naumlitauml kaumlyttaumlen myoumls projektin rakenne on vapaampi jonka ansiosta ohjelmoijalla on vapaammat kaumldet sovelluksen suhteen Yksi esi-merkki taumlllaisesta sovelluskehyksestauml on Python-pohjainen Flask jossa lauml-hes jokainen ominaisuus taumlytyy tehdauml itse tai asentaa erikseen laajennuk-sena Myoumls erittaumlin suosittu Nodejs-pohjainen Expressjs lukeutuu samaan kategoriaan (Ronacher 2017)

3

32 Suosituimmat web-sovelluskehykset

Perehdyin eri web-sovelluskehysten suosioon HotFrameworks on sivusto joka mittaa eri sovelluskehysten suosiota niiden GitHub- ja Stack Overflow -aktiivisuuden perusteella Microsoftin sovelluskehysten GitHub-pisteet kuitenkin puuttuivat kokonaan jonka takia niiden todellinen suosio on to-dennaumlkoumlisesti HotFrameworks-sivuston tuloksia pienempi (Kuva 1) Muilta osin pidin HotFrameworks-sivuston tuloksia luotettavina (HotFrameworks 2018)

Kuva 1 Suosituimmat web-sovelluskehykset HotFrameworks-sivuston mukaan

33 Asiakaspuolen web-sovelluskehykset

Asiakaspuolen web-sovelluskehyksillauml rakennetaan paumlaumlasiassa selaimessa toimivia JavaScript-pohjaisia sovelluksia Ne tavallisesti keskittyvaumlt dynaa-misen datan kaumlsittelyn helpottamiseen ja sen naumlyttaumlmiseen HTML-sivulla Palvelimien ja tietokantojen kanssa keskustelemiseen asiakaspuolen sovel-lukset kaumlyttaumlvaumlt REST-rajapintoja Kaumlytaumlnnoumlssauml kaikki asiakaspuolen web-sovelluskehykset kaumlyttaumlvaumlt JavaScriptia tai jotain siihen pohjautuvaa kieltauml

331 AngularJS ja Angular

AngularJS ja Angular ovat Googlen yllaumlpitaumlmiauml avointa laumlhdekoodia olevia asiakaspuolen sovelluksien rakentamiseen tarkoitettuja sovelluskehyksiauml

4

AngularJS-nimellauml tarkoitetaan vanhempaa 1x-versiota ja pelkaumlllauml Angu-lar-nimellauml tarkoitetaan uudelleenkirjoituksen jaumllkeisiauml versioita (Angular nd) AngularJS on Miško Heveryn kehittaumlmauml sovelluskehys joka oli aluksi vain apuna Googlen sisaumlisissauml projekteissa Sovelluskehyksen ensimmaumlinen jul-kinen versio julkaistiin vuonna 2010 avoimen laumlhdekoodin projektina An-gularJS oli kuitenkin ominaisuuksiltaan rajattu eikauml se soveltunut kunnolla isojen sovellusten rakentamiseen (Gavigan 2018) Sovelluskehykselle tehtiin kokonainen uudelleenkirjoitus josta vastasi Google Uusi versio julkaistiin vuonna 2016 versionumerolla 20 Uudel-leenkirjoituksesta johtuen syntaksi ja sovellusten rakenne oli erilainen Se ei myoumlskaumlaumln ollut taaksepaumlin yhteensopiva vanhempien AngularJS-sovel-lusten kanssa Uudelleenkirjoitettu versio kaumlytti oletuksena JavaScriptin si-jasta TypeScriptia joka lisaumlauml JavaScriptiin uusia ominaisuuksia (Gavigan 2018)

332 React

React on Facebookin yllaumlpitaumlmauml JavaScript-pohjainen kaumlyttoumlliittymien te-koon tarkoitettu sovelluskehys Ensimmaumlinen julkinen versio Reactista jul-kaistiin vuonna 2013 Web-sovellusten lisaumlksi Reactia voi kaumlyttaumlauml mobiiliso-vellusten tekoon Mobiilisovellusten tekoon tarkoitettu kirjasto on nimel-taumlaumln React Native Virtuaalisen DOMin ansiosta dynaamisen ja muuttuvan datan esittaumlminen on erittaumlin nopeaa Kun ainoastaan yhtauml sivun elementtiauml muutetaan riit-taumlauml ettauml ainoastaan kyseinen elementti renderoumlidaumlaumln uudelleen Tavallista DOMia kaumlytettaumlessauml koko sivu pitaumlisi renderoumlidauml uudelleen (Reactjs nd) React-sovellukset rakentuvat komponenteista Komponentit kaumlyttaumlvaumlt JSX-syntaksia jonka avulla voi sekoittaa JavaScript- ja HTML-koodia keske-naumlaumln Taumlmaumln avulla voi esimerkiksi asettaa funktion palauttamaan HTML-elementtejauml mikauml helpottaa ja nopeuttaa kehitystauml (Reactjs nd)

333 Vue

Vue on avointa laumlhdekoodia oleva asiakaspuolen sovelluksien kehittaumlmi-seen tarkoitettu sovelluskehys Vuen mukana tuleva ydinkirjasto keskittyy paumlaumlasiassa naumlkymien rakentamiseen jonka takia sitauml on helppo kaumlyttaumlauml myoumls muiden sovelluskehysten ja tekniikoiden kanssa Pelkaumlllauml Vuella voi myoumls tehdauml kokonaisia SPA-sovelluksia mutta se vaatii ydinkirjaston lisaumlksi muita kirjastoja Muut kirjastot hoitavat esimerkiksi URL-reititykset (Vuejs nd a)

5

Ominaisuuksiltaan Vue on samankaltainen Reactin kanssa Molemmat hyoumldyntaumlvaumlt virtuaalista DOMia ja sovelluksen eri osat rakentuvat kom-ponenteista Vue tukee myoumls tarvittaessa Reactista tuttua JSX-syntaksia vaikkakin oletuksena kaumlytoumlssauml on yksinkertaisempi HTML-pohjainen sivu-pohja-jaumlrjestelmauml (Vuejs nd b)

34 Palvelinpuolen web-sovelluskehykset

Palvelinpuolen web-sovelluskehykset ovat tavallisesti asiakaspuolen web-sovelluskehyksiauml isompia koska ne tarvitsevat enemmaumln eri ominaisuuk-sia Palvelimella on tavallisesti yhteys tietokantaan joka jo sinaumlllaumlaumln aset-taa haasteita muun muassa tietoturvan suhteen Palvelimen taumlytyy myoumls pystyauml palvelemaan useaa kaumlyttaumljaumlauml samanaikaisesti Palvelinpuolen web-sovelluskehykset sisaumlltaumlvaumlt yleensauml vaumlhintaumlaumln seuraa-vat ominaisuudet sisaumlaumlnrakennettuina - tuki HTTP-pyyntoumljen vastaanottamiseen ja laumlhettaumlmiseen - tuki REST-rajapintojen tekoon - kaumlyttaumljaumltilit - lomakkeet ja niiden validointi - vaumllimuisti ja istunnot - sivupohjamoottori - tuki monelle eri tietokannalle - ORM-jaumlrjestelmauml - MVC-arkkitehtuuri tai vastaava - testaustyoumlkalut - suojaus yleisiauml haavoittuvuuksia vastaan (Niemi 2015)

341 ASPNET Core

ASPNET Core on Microsoftin kehittaumlmauml ja markkinoima web-sovelluske-hys Se on ASPNET-sovelluskehysten uusin ja modernein iteraatio ja se jul-kaistiin vuonna 2016 ASPNET Core on avointa laumlhdekoodia ja toi muka-naan huomattavasti aiempaa paremman tuen eri kaumlyttoumljaumlrjestelmille Mic-rosoftin lisaumlksi myoumls kaumlyttaumljaumlt paumlaumlsevaumlt osallistumaan tuotteen jatkokehi-tykseen ASPNET Core on rakennettu Common Language Runtimen paumlaumllle joten se tukee kaikkia NET kieliauml joista yleisimmaumlt ovat C ja Visual Basic NET (Microsoft 2018)

342 Express

Express on Nodejs-pohjainen mikrosovelluskehys Nodejs-pohjan ansi-osta se toimii asynkronisesti joka tekee siitauml toiminnaltaan varsin erilaisen muihin taumlssauml tyoumlssauml mainittuihin web-sovelluskehyksiin verrattuna Ex-press on projektin arkkitehtuurin kannalta hyvin avoin eikauml tee juurikaan paumlaumltoumlksiauml ohjelmoijan puolesta

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 4: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

SANASTO

CSS Cascading Style Sheets Web-sivujen ulkoasun ja visuaalisen il-meen maumlaumlrittaumlmiseen tarkoitettu kieli

DOM Document Object Model Malli jonka avulla HTML-sivua on mah-dollista muokata JavaScriptilla

HTML Hypertext Markup Langu-age

Merkintaumlkieli jolla maumlaumlritetaumlaumln sivus-ton rakenne ja sisaumlltouml

HTTP Hypertext Transfer Protocol Protokolla jota selaimet ja palvelimet kaumlyttaumlvaumlt tiedonsiirtoon

JSX JavaScript XML Laajennus tavallisen JavaScript-ohjel-mointikielen syntaksiin

MVC Model-View-Controller Ohjelmistoarkkitehtuuri jonka tarkoi-tuksena on erottaa malli naumlkymauml ja kauml-sittelijauml toisistaan

MVT Model-View-Template Djangon versio suositusta MVC-arkkitehtuurista

ORM Object-relational mapping Tekniikka jonka avulla tietokantoja voi kaumlyttaumlauml oliomaisesti

REST Representational State Transfer

HTTP-protokollaan perustuva tapa raja-pintojen toteuttamiseen

SPA Single-Page Application Sovellus joka toimii usein vain yhdellauml sivun latauksella Data ja sisaumlltouml lada-taan ja esitetaumlaumln dynaamisesti

SQL Structured Query Language Kyselykieli jota kaumlytaumlnnoumlssauml kaikki re-laatiotietokannat kaumlyttaumlvaumlt

XML Extensible Markup Langu-age

Tietynlaisten merkintaumlkielten standardi

SISAumlLLYS

1 JOHDANTO 1

2 WEB-SOVELLUKSET 1

3 WEB-SOVELLUSKEHYKSET 2

31 Mikrosovelluskehykset 2

32 Suosituimmat web-sovelluskehykset 3

33 Asiakaspuolen web-sovelluskehykset 3

331 AngularJS ja Angular 3

332 React 4

333 Vue 4

34 Palvelinpuolen web-sovelluskehykset 5

341 ASPNET Core 5

342 Express 5

343 Flask 6

344 Laravel 6

345 Ruby on Rails 6

4 PYTHON-OHJELMOINTIKIELI 7

41 Pythonin suosio 7

42 Tulkattavuus 8

43 Tyypitys 8

44 Syntaksi 9

441 Funktiot 9

442 Silmukat 9

443 Luokat 10

45 PEP 8 10

5 DJANGO 11

51 Historia 11

52 MVT-arkkitehtuuri 12

53 Mallit 12

531 Mallien maumlaumlritys 13

532 Tietokantakyselyiden tekeminen 13

54 Naumlkymaumlt 13

541 Funktiopohjaiset naumlkymaumlt 14

542 Luokkapohjaiset naumlkymaumlt 14

55 URL-maumlaumlritys 14

56 Sivupohjat 15

561 Sivupohjien syntaksi 15

562 Periytyminen 16

563 Include 17

57 Middleware 17

58 Admin-sivusto 18

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT 20

61 Cmder 20

62 Git ja GitHub 20

63 Virtualenv ja virtualenvwrapper 21

64 Visual Studio Code 21

7 PROJEKTIN TOTEUTUS 21

71 Gitin kaumlyttoumloumlnotto 22

72 Virtualenvwrapperin kaumlyttoumloumlnotto 23

73 Djangon asennus ja uuden projektin luominen 24

74 Templates- ja static-kansioiden luonti 25

75 Basehtml-sivupohja 26

76 Accounts-sovellus 26

77 Auth-sovelluksen kaumlyttoumloumlnotto 27

78 Sivupohjat auth-sovelluksen naumlkymille 27

79 Rekisteroumlinti 28

791 Lomake 28

792 Naumlkymauml 29

793 Sivupohja 30

710 Profiili 31

711 Kaumlyttaumljaumltilin muokkaus 31

712 Shortener-sovellus 33

713 ShortenedURL-malli 33

714 Osoitteen lyhennys 34

7141 Algoritmi 34

7142 Naumlkymauml 34

715 Detail-naumlkymauml 36

716 Lyhyiden osoitteiden uudelleenohjaus 37

717 Lyhennettyjen osoitteiden hallintapaneeli 37

718 Lyhennettyjen osoitteiden poistaminen 38

719 Linkin kopioiminen 40

8 YHTEENVETO 40

81 Haasteet 41

82 Jatkokehitys 41

LAumlHTEET 42

1

1 JOHDANTO

Taumlssauml opinnaumlytetyoumlssauml tutustutaan web-sovelluksen rakentamiseen Pyt-hon-ohjelmointikieltauml kaumlyttaumlvaumlllauml Django-sovelluskehyksellauml Tyoumln alussa kaumlydaumlaumln laumlpi yleisesti mitauml web-sovelluksella ja sovelluskehyksellauml tarkoi-tetaan ja mitauml ominaisuuksia eri sovelluskehykset tavallisesti sisaumlltaumlvaumlt Taumlmaumln jaumllkeen tutustutaan taumlmaumln hetken suosituimpiin web-sovelluske-hyksiin Sitten tyoumlssauml perehdytaumlaumln Python-ohjelmointikieleen ja tarkem-min Djangon ominaisuuksiin ja toimintaperiaatteisiin Projektiosuuden alussa kaumlydaumlaumln laumlpi projektissa kaumlytettaumlvaumlt tyoumlkalut ja kehitysympaumlristouml Opinnaumlytetyoumln tavoitteena oli luoda kaumlyttoumlvalmis pitkien linkkien lyhentauml-miseen keskittynyt web-sovellus Djangolla Sovelluksen vaatimuksina oli linkkien lyhentaumlmisen lisaumlksi tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnoin-tiin Opinnaumlytetyoumlssauml dokumentoitiin sovelluksen keskeisimmaumlt ominai-suudet Tyoumln lopussa pohditaan mahdollisia jatkokehitykseen liittyviauml ide-oita ja kaumlydaumlaumln laumlpi tyoumlssauml ilmenneitauml ongelmia Opinnaumlytetyouml on suunnattu henkiloumlille jotka ovat kiinnostuneita web-so-vellusten kehityksestauml tai yleisesti web-sovelluskehyksistauml Opinnaumlytetyouml olettaa lukijalta jonkin tasoista ymmaumlrrystauml ohjelmoinnin perusteista

2 WEB-SOVELLUKSET

Web-sovelluksella tarkoitetaan sovellusta joka suoritetaan kaumlyttaumlen web-selainta Tavallisesta www-sivusta web-sovellus eroaa siten ettauml se sisaumll-taumlauml dynaamista ja kaumlyttaumljaumln syoumltteen perusteella muuttuvaa dataa Web-sovellus koostuu tavallisesti kaumlyttaumljaumln selaimessa suoritettavasta HTML CSS ndash ja JavaScript-koodista ja se keskustelee palvelimella olevan koodin kanssa Web-sovellus voi toimia myoumls kokonaan ilman palvelinta Esi-merkki taumlllaisesta sovelluksesta olisi JavaScriptilla tehty laskin jonka omi-naisuudet eivaumlt tarvitse palvelimella olevaa tietokantaa tai laskentatehoa (Ndegwa 2016) Web-sovelluksen HTML-rakenne ja sisaumlltouml voidaan renderoumlidauml joko palve-limella tai vasta kaumlyttaumljaumln selaimessa Renderoumlinnillauml tarkoitetaan kaumlyttauml-jaumln selaimessa lopulta naumlkyvaumln HTML-sivuston rakentamista yhdestauml tai useammasta osasta Kummallakin tavalla on omat hyoumldyt ja haitat ja so-velluksen kaumlyttoumltarkoitus maumlaumlrittaumlauml yleensauml sen kumpaa tapaa kannattaa kaumlyttaumlauml (Ndegwa 2016) Yleisin tapa on rakentaa valmis HTML-dokumentti kokonaan palvelimella jonka jaumllkeen se laumlhetetaumlaumln sellaisenaan kaumlyttaumljaumllle Taumlmauml tapa soveltuu

2

hyvin sivustoille ja sovelluksille jotka eivaumlt sisaumlllauml isoja maumlaumlriauml dynaamista sisaumlltoumlauml Taumlllaiset sivustot on myoumls helpompi hakukoneoptimoida koska tavallisimmat hakukoneiden kaumlyttaumlmaumlt botit eivaumlt pysty renderoumlimaumlaumln si-saumlltoumlauml paikallisesti (Vega 2017) Kun sivun HTML on tarkoitus renderoumlidauml paumlaumlasiassa selaimella palvelin lauml-hettaumlauml vain pienen paumltkaumln HTML-koodia JavaScript-koodin lisaumlksi Loput HTMLstauml rakennetaan vasta kaumlyttaumljaumln selaimessa JavaScript-koodin avulla JavaScript-koodin ja sen laumlhettaumlmien palvelinkutsujen ajamisessa kestaumlauml hetken jonka takia dynaamisen sisaumllloumln tilalla naumlkyy usein placehol-der-sisaumlltoumlauml (Vega 2017)

3 WEB-SOVELLUSKEHYKSET

Sovelluskehyksellauml tarkoitetaan ohjelmistoa ja kirjastoja jotka muodosta-vat rungon sen paumlaumllle rakennettavalle sovellukselle Sovelluskehys ei siis ole valmis kaumlytettaumlvissauml oleva ohjelma vaan toimii ainoastaan pohjana si-saumlltaumlen yleisimmin tarvittavat ominaisuudet Sovelluskehyksen taumlrkeim-paumlnauml tehtaumlvaumlnauml on vaumlhentaumlauml usein kaumlytettyjen ominaisuuksien ohjelmoin-tia uudelleen eli ne sisaumlltaumlvaumlt valmiiksi perustoiminnallisuudet ja naumlin ly-hentaumlvaumlt sovelluksen kehitykseen menevaumlauml aikaa merkittaumlvaumlsti (Niemi 2015)

Web-sovelluskehykset ovat nimensauml mukaisesti web-sovelluksien tekoon tarkoitettuja sovelluskehyksiauml Web-sovelluskehyksiauml on tehty laumlhes jokai-selle suositulle ohjelmointikielelle matalan tason C++-kielestauml korkean ta-son C-kieleen Taumlllauml hetkellauml yleisimmaumlt web-sovelluksien tekoon kaumlytet-taumlvaumlt kielet ovat C PHP Java JavaScript Python ja Ruby Web-sovellus-kehykset voidaan jakaa kahteen eri kategoriaan sen perusteella toimivatko ne asiakaspuolella vai palvelinpuolella (Niemi 2015)

31 Mikrosovelluskehykset

Massiivisten sovelluskehysten lisaumlksi on olemassa myoumls pienempiauml sovel-luskehyksiauml jotka sisaumlltaumlvaumlt hyvin vaumlhaumln sisaumlaumlnrakennettuja toimintoja ja ominaisuuksia Naumlitauml kaumlyttaumlen myoumls projektin rakenne on vapaampi jonka ansiosta ohjelmoijalla on vapaammat kaumldet sovelluksen suhteen Yksi esi-merkki taumlllaisesta sovelluskehyksestauml on Python-pohjainen Flask jossa lauml-hes jokainen ominaisuus taumlytyy tehdauml itse tai asentaa erikseen laajennuk-sena Myoumls erittaumlin suosittu Nodejs-pohjainen Expressjs lukeutuu samaan kategoriaan (Ronacher 2017)

3

32 Suosituimmat web-sovelluskehykset

Perehdyin eri web-sovelluskehysten suosioon HotFrameworks on sivusto joka mittaa eri sovelluskehysten suosiota niiden GitHub- ja Stack Overflow -aktiivisuuden perusteella Microsoftin sovelluskehysten GitHub-pisteet kuitenkin puuttuivat kokonaan jonka takia niiden todellinen suosio on to-dennaumlkoumlisesti HotFrameworks-sivuston tuloksia pienempi (Kuva 1) Muilta osin pidin HotFrameworks-sivuston tuloksia luotettavina (HotFrameworks 2018)

Kuva 1 Suosituimmat web-sovelluskehykset HotFrameworks-sivuston mukaan

33 Asiakaspuolen web-sovelluskehykset

Asiakaspuolen web-sovelluskehyksillauml rakennetaan paumlaumlasiassa selaimessa toimivia JavaScript-pohjaisia sovelluksia Ne tavallisesti keskittyvaumlt dynaa-misen datan kaumlsittelyn helpottamiseen ja sen naumlyttaumlmiseen HTML-sivulla Palvelimien ja tietokantojen kanssa keskustelemiseen asiakaspuolen sovel-lukset kaumlyttaumlvaumlt REST-rajapintoja Kaumlytaumlnnoumlssauml kaikki asiakaspuolen web-sovelluskehykset kaumlyttaumlvaumlt JavaScriptia tai jotain siihen pohjautuvaa kieltauml

331 AngularJS ja Angular

AngularJS ja Angular ovat Googlen yllaumlpitaumlmiauml avointa laumlhdekoodia olevia asiakaspuolen sovelluksien rakentamiseen tarkoitettuja sovelluskehyksiauml

4

AngularJS-nimellauml tarkoitetaan vanhempaa 1x-versiota ja pelkaumlllauml Angu-lar-nimellauml tarkoitetaan uudelleenkirjoituksen jaumllkeisiauml versioita (Angular nd) AngularJS on Miško Heveryn kehittaumlmauml sovelluskehys joka oli aluksi vain apuna Googlen sisaumlisissauml projekteissa Sovelluskehyksen ensimmaumlinen jul-kinen versio julkaistiin vuonna 2010 avoimen laumlhdekoodin projektina An-gularJS oli kuitenkin ominaisuuksiltaan rajattu eikauml se soveltunut kunnolla isojen sovellusten rakentamiseen (Gavigan 2018) Sovelluskehykselle tehtiin kokonainen uudelleenkirjoitus josta vastasi Google Uusi versio julkaistiin vuonna 2016 versionumerolla 20 Uudel-leenkirjoituksesta johtuen syntaksi ja sovellusten rakenne oli erilainen Se ei myoumlskaumlaumln ollut taaksepaumlin yhteensopiva vanhempien AngularJS-sovel-lusten kanssa Uudelleenkirjoitettu versio kaumlytti oletuksena JavaScriptin si-jasta TypeScriptia joka lisaumlauml JavaScriptiin uusia ominaisuuksia (Gavigan 2018)

332 React

React on Facebookin yllaumlpitaumlmauml JavaScript-pohjainen kaumlyttoumlliittymien te-koon tarkoitettu sovelluskehys Ensimmaumlinen julkinen versio Reactista jul-kaistiin vuonna 2013 Web-sovellusten lisaumlksi Reactia voi kaumlyttaumlauml mobiiliso-vellusten tekoon Mobiilisovellusten tekoon tarkoitettu kirjasto on nimel-taumlaumln React Native Virtuaalisen DOMin ansiosta dynaamisen ja muuttuvan datan esittaumlminen on erittaumlin nopeaa Kun ainoastaan yhtauml sivun elementtiauml muutetaan riit-taumlauml ettauml ainoastaan kyseinen elementti renderoumlidaumlaumln uudelleen Tavallista DOMia kaumlytettaumlessauml koko sivu pitaumlisi renderoumlidauml uudelleen (Reactjs nd) React-sovellukset rakentuvat komponenteista Komponentit kaumlyttaumlvaumlt JSX-syntaksia jonka avulla voi sekoittaa JavaScript- ja HTML-koodia keske-naumlaumln Taumlmaumln avulla voi esimerkiksi asettaa funktion palauttamaan HTML-elementtejauml mikauml helpottaa ja nopeuttaa kehitystauml (Reactjs nd)

333 Vue

Vue on avointa laumlhdekoodia oleva asiakaspuolen sovelluksien kehittaumlmi-seen tarkoitettu sovelluskehys Vuen mukana tuleva ydinkirjasto keskittyy paumlaumlasiassa naumlkymien rakentamiseen jonka takia sitauml on helppo kaumlyttaumlauml myoumls muiden sovelluskehysten ja tekniikoiden kanssa Pelkaumlllauml Vuella voi myoumls tehdauml kokonaisia SPA-sovelluksia mutta se vaatii ydinkirjaston lisaumlksi muita kirjastoja Muut kirjastot hoitavat esimerkiksi URL-reititykset (Vuejs nd a)

5

Ominaisuuksiltaan Vue on samankaltainen Reactin kanssa Molemmat hyoumldyntaumlvaumlt virtuaalista DOMia ja sovelluksen eri osat rakentuvat kom-ponenteista Vue tukee myoumls tarvittaessa Reactista tuttua JSX-syntaksia vaikkakin oletuksena kaumlytoumlssauml on yksinkertaisempi HTML-pohjainen sivu-pohja-jaumlrjestelmauml (Vuejs nd b)

34 Palvelinpuolen web-sovelluskehykset

Palvelinpuolen web-sovelluskehykset ovat tavallisesti asiakaspuolen web-sovelluskehyksiauml isompia koska ne tarvitsevat enemmaumln eri ominaisuuk-sia Palvelimella on tavallisesti yhteys tietokantaan joka jo sinaumlllaumlaumln aset-taa haasteita muun muassa tietoturvan suhteen Palvelimen taumlytyy myoumls pystyauml palvelemaan useaa kaumlyttaumljaumlauml samanaikaisesti Palvelinpuolen web-sovelluskehykset sisaumlltaumlvaumlt yleensauml vaumlhintaumlaumln seuraa-vat ominaisuudet sisaumlaumlnrakennettuina - tuki HTTP-pyyntoumljen vastaanottamiseen ja laumlhettaumlmiseen - tuki REST-rajapintojen tekoon - kaumlyttaumljaumltilit - lomakkeet ja niiden validointi - vaumllimuisti ja istunnot - sivupohjamoottori - tuki monelle eri tietokannalle - ORM-jaumlrjestelmauml - MVC-arkkitehtuuri tai vastaava - testaustyoumlkalut - suojaus yleisiauml haavoittuvuuksia vastaan (Niemi 2015)

341 ASPNET Core

ASPNET Core on Microsoftin kehittaumlmauml ja markkinoima web-sovelluske-hys Se on ASPNET-sovelluskehysten uusin ja modernein iteraatio ja se jul-kaistiin vuonna 2016 ASPNET Core on avointa laumlhdekoodia ja toi muka-naan huomattavasti aiempaa paremman tuen eri kaumlyttoumljaumlrjestelmille Mic-rosoftin lisaumlksi myoumls kaumlyttaumljaumlt paumlaumlsevaumlt osallistumaan tuotteen jatkokehi-tykseen ASPNET Core on rakennettu Common Language Runtimen paumlaumllle joten se tukee kaikkia NET kieliauml joista yleisimmaumlt ovat C ja Visual Basic NET (Microsoft 2018)

342 Express

Express on Nodejs-pohjainen mikrosovelluskehys Nodejs-pohjan ansi-osta se toimii asynkronisesti joka tekee siitauml toiminnaltaan varsin erilaisen muihin taumlssauml tyoumlssauml mainittuihin web-sovelluskehyksiin verrattuna Ex-press on projektin arkkitehtuurin kannalta hyvin avoin eikauml tee juurikaan paumlaumltoumlksiauml ohjelmoijan puolesta

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 5: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

SISAumlLLYS

1 JOHDANTO 1

2 WEB-SOVELLUKSET 1

3 WEB-SOVELLUSKEHYKSET 2

31 Mikrosovelluskehykset 2

32 Suosituimmat web-sovelluskehykset 3

33 Asiakaspuolen web-sovelluskehykset 3

331 AngularJS ja Angular 3

332 React 4

333 Vue 4

34 Palvelinpuolen web-sovelluskehykset 5

341 ASPNET Core 5

342 Express 5

343 Flask 6

344 Laravel 6

345 Ruby on Rails 6

4 PYTHON-OHJELMOINTIKIELI 7

41 Pythonin suosio 7

42 Tulkattavuus 8

43 Tyypitys 8

44 Syntaksi 9

441 Funktiot 9

442 Silmukat 9

443 Luokat 10

45 PEP 8 10

5 DJANGO 11

51 Historia 11

52 MVT-arkkitehtuuri 12

53 Mallit 12

531 Mallien maumlaumlritys 13

532 Tietokantakyselyiden tekeminen 13

54 Naumlkymaumlt 13

541 Funktiopohjaiset naumlkymaumlt 14

542 Luokkapohjaiset naumlkymaumlt 14

55 URL-maumlaumlritys 14

56 Sivupohjat 15

561 Sivupohjien syntaksi 15

562 Periytyminen 16

563 Include 17

57 Middleware 17

58 Admin-sivusto 18

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT 20

61 Cmder 20

62 Git ja GitHub 20

63 Virtualenv ja virtualenvwrapper 21

64 Visual Studio Code 21

7 PROJEKTIN TOTEUTUS 21

71 Gitin kaumlyttoumloumlnotto 22

72 Virtualenvwrapperin kaumlyttoumloumlnotto 23

73 Djangon asennus ja uuden projektin luominen 24

74 Templates- ja static-kansioiden luonti 25

75 Basehtml-sivupohja 26

76 Accounts-sovellus 26

77 Auth-sovelluksen kaumlyttoumloumlnotto 27

78 Sivupohjat auth-sovelluksen naumlkymille 27

79 Rekisteroumlinti 28

791 Lomake 28

792 Naumlkymauml 29

793 Sivupohja 30

710 Profiili 31

711 Kaumlyttaumljaumltilin muokkaus 31

712 Shortener-sovellus 33

713 ShortenedURL-malli 33

714 Osoitteen lyhennys 34

7141 Algoritmi 34

7142 Naumlkymauml 34

715 Detail-naumlkymauml 36

716 Lyhyiden osoitteiden uudelleenohjaus 37

717 Lyhennettyjen osoitteiden hallintapaneeli 37

718 Lyhennettyjen osoitteiden poistaminen 38

719 Linkin kopioiminen 40

8 YHTEENVETO 40

81 Haasteet 41

82 Jatkokehitys 41

LAumlHTEET 42

1

1 JOHDANTO

Taumlssauml opinnaumlytetyoumlssauml tutustutaan web-sovelluksen rakentamiseen Pyt-hon-ohjelmointikieltauml kaumlyttaumlvaumlllauml Django-sovelluskehyksellauml Tyoumln alussa kaumlydaumlaumln laumlpi yleisesti mitauml web-sovelluksella ja sovelluskehyksellauml tarkoi-tetaan ja mitauml ominaisuuksia eri sovelluskehykset tavallisesti sisaumlltaumlvaumlt Taumlmaumln jaumllkeen tutustutaan taumlmaumln hetken suosituimpiin web-sovelluske-hyksiin Sitten tyoumlssauml perehdytaumlaumln Python-ohjelmointikieleen ja tarkem-min Djangon ominaisuuksiin ja toimintaperiaatteisiin Projektiosuuden alussa kaumlydaumlaumln laumlpi projektissa kaumlytettaumlvaumlt tyoumlkalut ja kehitysympaumlristouml Opinnaumlytetyoumln tavoitteena oli luoda kaumlyttoumlvalmis pitkien linkkien lyhentauml-miseen keskittynyt web-sovellus Djangolla Sovelluksen vaatimuksina oli linkkien lyhentaumlmisen lisaumlksi tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnoin-tiin Opinnaumlytetyoumlssauml dokumentoitiin sovelluksen keskeisimmaumlt ominai-suudet Tyoumln lopussa pohditaan mahdollisia jatkokehitykseen liittyviauml ide-oita ja kaumlydaumlaumln laumlpi tyoumlssauml ilmenneitauml ongelmia Opinnaumlytetyouml on suunnattu henkiloumlille jotka ovat kiinnostuneita web-so-vellusten kehityksestauml tai yleisesti web-sovelluskehyksistauml Opinnaumlytetyouml olettaa lukijalta jonkin tasoista ymmaumlrrystauml ohjelmoinnin perusteista

2 WEB-SOVELLUKSET

Web-sovelluksella tarkoitetaan sovellusta joka suoritetaan kaumlyttaumlen web-selainta Tavallisesta www-sivusta web-sovellus eroaa siten ettauml se sisaumll-taumlauml dynaamista ja kaumlyttaumljaumln syoumltteen perusteella muuttuvaa dataa Web-sovellus koostuu tavallisesti kaumlyttaumljaumln selaimessa suoritettavasta HTML CSS ndash ja JavaScript-koodista ja se keskustelee palvelimella olevan koodin kanssa Web-sovellus voi toimia myoumls kokonaan ilman palvelinta Esi-merkki taumlllaisesta sovelluksesta olisi JavaScriptilla tehty laskin jonka omi-naisuudet eivaumlt tarvitse palvelimella olevaa tietokantaa tai laskentatehoa (Ndegwa 2016) Web-sovelluksen HTML-rakenne ja sisaumlltouml voidaan renderoumlidauml joko palve-limella tai vasta kaumlyttaumljaumln selaimessa Renderoumlinnillauml tarkoitetaan kaumlyttauml-jaumln selaimessa lopulta naumlkyvaumln HTML-sivuston rakentamista yhdestauml tai useammasta osasta Kummallakin tavalla on omat hyoumldyt ja haitat ja so-velluksen kaumlyttoumltarkoitus maumlaumlrittaumlauml yleensauml sen kumpaa tapaa kannattaa kaumlyttaumlauml (Ndegwa 2016) Yleisin tapa on rakentaa valmis HTML-dokumentti kokonaan palvelimella jonka jaumllkeen se laumlhetetaumlaumln sellaisenaan kaumlyttaumljaumllle Taumlmauml tapa soveltuu

2

hyvin sivustoille ja sovelluksille jotka eivaumlt sisaumlllauml isoja maumlaumlriauml dynaamista sisaumlltoumlauml Taumlllaiset sivustot on myoumls helpompi hakukoneoptimoida koska tavallisimmat hakukoneiden kaumlyttaumlmaumlt botit eivaumlt pysty renderoumlimaumlaumln si-saumlltoumlauml paikallisesti (Vega 2017) Kun sivun HTML on tarkoitus renderoumlidauml paumlaumlasiassa selaimella palvelin lauml-hettaumlauml vain pienen paumltkaumln HTML-koodia JavaScript-koodin lisaumlksi Loput HTMLstauml rakennetaan vasta kaumlyttaumljaumln selaimessa JavaScript-koodin avulla JavaScript-koodin ja sen laumlhettaumlmien palvelinkutsujen ajamisessa kestaumlauml hetken jonka takia dynaamisen sisaumllloumln tilalla naumlkyy usein placehol-der-sisaumlltoumlauml (Vega 2017)

3 WEB-SOVELLUSKEHYKSET

Sovelluskehyksellauml tarkoitetaan ohjelmistoa ja kirjastoja jotka muodosta-vat rungon sen paumlaumllle rakennettavalle sovellukselle Sovelluskehys ei siis ole valmis kaumlytettaumlvissauml oleva ohjelma vaan toimii ainoastaan pohjana si-saumlltaumlen yleisimmin tarvittavat ominaisuudet Sovelluskehyksen taumlrkeim-paumlnauml tehtaumlvaumlnauml on vaumlhentaumlauml usein kaumlytettyjen ominaisuuksien ohjelmoin-tia uudelleen eli ne sisaumlltaumlvaumlt valmiiksi perustoiminnallisuudet ja naumlin ly-hentaumlvaumlt sovelluksen kehitykseen menevaumlauml aikaa merkittaumlvaumlsti (Niemi 2015)

Web-sovelluskehykset ovat nimensauml mukaisesti web-sovelluksien tekoon tarkoitettuja sovelluskehyksiauml Web-sovelluskehyksiauml on tehty laumlhes jokai-selle suositulle ohjelmointikielelle matalan tason C++-kielestauml korkean ta-son C-kieleen Taumlllauml hetkellauml yleisimmaumlt web-sovelluksien tekoon kaumlytet-taumlvaumlt kielet ovat C PHP Java JavaScript Python ja Ruby Web-sovellus-kehykset voidaan jakaa kahteen eri kategoriaan sen perusteella toimivatko ne asiakaspuolella vai palvelinpuolella (Niemi 2015)

31 Mikrosovelluskehykset

Massiivisten sovelluskehysten lisaumlksi on olemassa myoumls pienempiauml sovel-luskehyksiauml jotka sisaumlltaumlvaumlt hyvin vaumlhaumln sisaumlaumlnrakennettuja toimintoja ja ominaisuuksia Naumlitauml kaumlyttaumlen myoumls projektin rakenne on vapaampi jonka ansiosta ohjelmoijalla on vapaammat kaumldet sovelluksen suhteen Yksi esi-merkki taumlllaisesta sovelluskehyksestauml on Python-pohjainen Flask jossa lauml-hes jokainen ominaisuus taumlytyy tehdauml itse tai asentaa erikseen laajennuk-sena Myoumls erittaumlin suosittu Nodejs-pohjainen Expressjs lukeutuu samaan kategoriaan (Ronacher 2017)

3

32 Suosituimmat web-sovelluskehykset

Perehdyin eri web-sovelluskehysten suosioon HotFrameworks on sivusto joka mittaa eri sovelluskehysten suosiota niiden GitHub- ja Stack Overflow -aktiivisuuden perusteella Microsoftin sovelluskehysten GitHub-pisteet kuitenkin puuttuivat kokonaan jonka takia niiden todellinen suosio on to-dennaumlkoumlisesti HotFrameworks-sivuston tuloksia pienempi (Kuva 1) Muilta osin pidin HotFrameworks-sivuston tuloksia luotettavina (HotFrameworks 2018)

Kuva 1 Suosituimmat web-sovelluskehykset HotFrameworks-sivuston mukaan

33 Asiakaspuolen web-sovelluskehykset

Asiakaspuolen web-sovelluskehyksillauml rakennetaan paumlaumlasiassa selaimessa toimivia JavaScript-pohjaisia sovelluksia Ne tavallisesti keskittyvaumlt dynaa-misen datan kaumlsittelyn helpottamiseen ja sen naumlyttaumlmiseen HTML-sivulla Palvelimien ja tietokantojen kanssa keskustelemiseen asiakaspuolen sovel-lukset kaumlyttaumlvaumlt REST-rajapintoja Kaumlytaumlnnoumlssauml kaikki asiakaspuolen web-sovelluskehykset kaumlyttaumlvaumlt JavaScriptia tai jotain siihen pohjautuvaa kieltauml

331 AngularJS ja Angular

AngularJS ja Angular ovat Googlen yllaumlpitaumlmiauml avointa laumlhdekoodia olevia asiakaspuolen sovelluksien rakentamiseen tarkoitettuja sovelluskehyksiauml

4

AngularJS-nimellauml tarkoitetaan vanhempaa 1x-versiota ja pelkaumlllauml Angu-lar-nimellauml tarkoitetaan uudelleenkirjoituksen jaumllkeisiauml versioita (Angular nd) AngularJS on Miško Heveryn kehittaumlmauml sovelluskehys joka oli aluksi vain apuna Googlen sisaumlisissauml projekteissa Sovelluskehyksen ensimmaumlinen jul-kinen versio julkaistiin vuonna 2010 avoimen laumlhdekoodin projektina An-gularJS oli kuitenkin ominaisuuksiltaan rajattu eikauml se soveltunut kunnolla isojen sovellusten rakentamiseen (Gavigan 2018) Sovelluskehykselle tehtiin kokonainen uudelleenkirjoitus josta vastasi Google Uusi versio julkaistiin vuonna 2016 versionumerolla 20 Uudel-leenkirjoituksesta johtuen syntaksi ja sovellusten rakenne oli erilainen Se ei myoumlskaumlaumln ollut taaksepaumlin yhteensopiva vanhempien AngularJS-sovel-lusten kanssa Uudelleenkirjoitettu versio kaumlytti oletuksena JavaScriptin si-jasta TypeScriptia joka lisaumlauml JavaScriptiin uusia ominaisuuksia (Gavigan 2018)

332 React

React on Facebookin yllaumlpitaumlmauml JavaScript-pohjainen kaumlyttoumlliittymien te-koon tarkoitettu sovelluskehys Ensimmaumlinen julkinen versio Reactista jul-kaistiin vuonna 2013 Web-sovellusten lisaumlksi Reactia voi kaumlyttaumlauml mobiiliso-vellusten tekoon Mobiilisovellusten tekoon tarkoitettu kirjasto on nimel-taumlaumln React Native Virtuaalisen DOMin ansiosta dynaamisen ja muuttuvan datan esittaumlminen on erittaumlin nopeaa Kun ainoastaan yhtauml sivun elementtiauml muutetaan riit-taumlauml ettauml ainoastaan kyseinen elementti renderoumlidaumlaumln uudelleen Tavallista DOMia kaumlytettaumlessauml koko sivu pitaumlisi renderoumlidauml uudelleen (Reactjs nd) React-sovellukset rakentuvat komponenteista Komponentit kaumlyttaumlvaumlt JSX-syntaksia jonka avulla voi sekoittaa JavaScript- ja HTML-koodia keske-naumlaumln Taumlmaumln avulla voi esimerkiksi asettaa funktion palauttamaan HTML-elementtejauml mikauml helpottaa ja nopeuttaa kehitystauml (Reactjs nd)

333 Vue

Vue on avointa laumlhdekoodia oleva asiakaspuolen sovelluksien kehittaumlmi-seen tarkoitettu sovelluskehys Vuen mukana tuleva ydinkirjasto keskittyy paumlaumlasiassa naumlkymien rakentamiseen jonka takia sitauml on helppo kaumlyttaumlauml myoumls muiden sovelluskehysten ja tekniikoiden kanssa Pelkaumlllauml Vuella voi myoumls tehdauml kokonaisia SPA-sovelluksia mutta se vaatii ydinkirjaston lisaumlksi muita kirjastoja Muut kirjastot hoitavat esimerkiksi URL-reititykset (Vuejs nd a)

5

Ominaisuuksiltaan Vue on samankaltainen Reactin kanssa Molemmat hyoumldyntaumlvaumlt virtuaalista DOMia ja sovelluksen eri osat rakentuvat kom-ponenteista Vue tukee myoumls tarvittaessa Reactista tuttua JSX-syntaksia vaikkakin oletuksena kaumlytoumlssauml on yksinkertaisempi HTML-pohjainen sivu-pohja-jaumlrjestelmauml (Vuejs nd b)

34 Palvelinpuolen web-sovelluskehykset

Palvelinpuolen web-sovelluskehykset ovat tavallisesti asiakaspuolen web-sovelluskehyksiauml isompia koska ne tarvitsevat enemmaumln eri ominaisuuk-sia Palvelimella on tavallisesti yhteys tietokantaan joka jo sinaumlllaumlaumln aset-taa haasteita muun muassa tietoturvan suhteen Palvelimen taumlytyy myoumls pystyauml palvelemaan useaa kaumlyttaumljaumlauml samanaikaisesti Palvelinpuolen web-sovelluskehykset sisaumlltaumlvaumlt yleensauml vaumlhintaumlaumln seuraa-vat ominaisuudet sisaumlaumlnrakennettuina - tuki HTTP-pyyntoumljen vastaanottamiseen ja laumlhettaumlmiseen - tuki REST-rajapintojen tekoon - kaumlyttaumljaumltilit - lomakkeet ja niiden validointi - vaumllimuisti ja istunnot - sivupohjamoottori - tuki monelle eri tietokannalle - ORM-jaumlrjestelmauml - MVC-arkkitehtuuri tai vastaava - testaustyoumlkalut - suojaus yleisiauml haavoittuvuuksia vastaan (Niemi 2015)

341 ASPNET Core

ASPNET Core on Microsoftin kehittaumlmauml ja markkinoima web-sovelluske-hys Se on ASPNET-sovelluskehysten uusin ja modernein iteraatio ja se jul-kaistiin vuonna 2016 ASPNET Core on avointa laumlhdekoodia ja toi muka-naan huomattavasti aiempaa paremman tuen eri kaumlyttoumljaumlrjestelmille Mic-rosoftin lisaumlksi myoumls kaumlyttaumljaumlt paumlaumlsevaumlt osallistumaan tuotteen jatkokehi-tykseen ASPNET Core on rakennettu Common Language Runtimen paumlaumllle joten se tukee kaikkia NET kieliauml joista yleisimmaumlt ovat C ja Visual Basic NET (Microsoft 2018)

342 Express

Express on Nodejs-pohjainen mikrosovelluskehys Nodejs-pohjan ansi-osta se toimii asynkronisesti joka tekee siitauml toiminnaltaan varsin erilaisen muihin taumlssauml tyoumlssauml mainittuihin web-sovelluskehyksiin verrattuna Ex-press on projektin arkkitehtuurin kannalta hyvin avoin eikauml tee juurikaan paumlaumltoumlksiauml ohjelmoijan puolesta

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 6: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT 20

61 Cmder 20

62 Git ja GitHub 20

63 Virtualenv ja virtualenvwrapper 21

64 Visual Studio Code 21

7 PROJEKTIN TOTEUTUS 21

71 Gitin kaumlyttoumloumlnotto 22

72 Virtualenvwrapperin kaumlyttoumloumlnotto 23

73 Djangon asennus ja uuden projektin luominen 24

74 Templates- ja static-kansioiden luonti 25

75 Basehtml-sivupohja 26

76 Accounts-sovellus 26

77 Auth-sovelluksen kaumlyttoumloumlnotto 27

78 Sivupohjat auth-sovelluksen naumlkymille 27

79 Rekisteroumlinti 28

791 Lomake 28

792 Naumlkymauml 29

793 Sivupohja 30

710 Profiili 31

711 Kaumlyttaumljaumltilin muokkaus 31

712 Shortener-sovellus 33

713 ShortenedURL-malli 33

714 Osoitteen lyhennys 34

7141 Algoritmi 34

7142 Naumlkymauml 34

715 Detail-naumlkymauml 36

716 Lyhyiden osoitteiden uudelleenohjaus 37

717 Lyhennettyjen osoitteiden hallintapaneeli 37

718 Lyhennettyjen osoitteiden poistaminen 38

719 Linkin kopioiminen 40

8 YHTEENVETO 40

81 Haasteet 41

82 Jatkokehitys 41

LAumlHTEET 42

1

1 JOHDANTO

Taumlssauml opinnaumlytetyoumlssauml tutustutaan web-sovelluksen rakentamiseen Pyt-hon-ohjelmointikieltauml kaumlyttaumlvaumlllauml Django-sovelluskehyksellauml Tyoumln alussa kaumlydaumlaumln laumlpi yleisesti mitauml web-sovelluksella ja sovelluskehyksellauml tarkoi-tetaan ja mitauml ominaisuuksia eri sovelluskehykset tavallisesti sisaumlltaumlvaumlt Taumlmaumln jaumllkeen tutustutaan taumlmaumln hetken suosituimpiin web-sovelluske-hyksiin Sitten tyoumlssauml perehdytaumlaumln Python-ohjelmointikieleen ja tarkem-min Djangon ominaisuuksiin ja toimintaperiaatteisiin Projektiosuuden alussa kaumlydaumlaumln laumlpi projektissa kaumlytettaumlvaumlt tyoumlkalut ja kehitysympaumlristouml Opinnaumlytetyoumln tavoitteena oli luoda kaumlyttoumlvalmis pitkien linkkien lyhentauml-miseen keskittynyt web-sovellus Djangolla Sovelluksen vaatimuksina oli linkkien lyhentaumlmisen lisaumlksi tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnoin-tiin Opinnaumlytetyoumlssauml dokumentoitiin sovelluksen keskeisimmaumlt ominai-suudet Tyoumln lopussa pohditaan mahdollisia jatkokehitykseen liittyviauml ide-oita ja kaumlydaumlaumln laumlpi tyoumlssauml ilmenneitauml ongelmia Opinnaumlytetyouml on suunnattu henkiloumlille jotka ovat kiinnostuneita web-so-vellusten kehityksestauml tai yleisesti web-sovelluskehyksistauml Opinnaumlytetyouml olettaa lukijalta jonkin tasoista ymmaumlrrystauml ohjelmoinnin perusteista

2 WEB-SOVELLUKSET

Web-sovelluksella tarkoitetaan sovellusta joka suoritetaan kaumlyttaumlen web-selainta Tavallisesta www-sivusta web-sovellus eroaa siten ettauml se sisaumll-taumlauml dynaamista ja kaumlyttaumljaumln syoumltteen perusteella muuttuvaa dataa Web-sovellus koostuu tavallisesti kaumlyttaumljaumln selaimessa suoritettavasta HTML CSS ndash ja JavaScript-koodista ja se keskustelee palvelimella olevan koodin kanssa Web-sovellus voi toimia myoumls kokonaan ilman palvelinta Esi-merkki taumlllaisesta sovelluksesta olisi JavaScriptilla tehty laskin jonka omi-naisuudet eivaumlt tarvitse palvelimella olevaa tietokantaa tai laskentatehoa (Ndegwa 2016) Web-sovelluksen HTML-rakenne ja sisaumlltouml voidaan renderoumlidauml joko palve-limella tai vasta kaumlyttaumljaumln selaimessa Renderoumlinnillauml tarkoitetaan kaumlyttauml-jaumln selaimessa lopulta naumlkyvaumln HTML-sivuston rakentamista yhdestauml tai useammasta osasta Kummallakin tavalla on omat hyoumldyt ja haitat ja so-velluksen kaumlyttoumltarkoitus maumlaumlrittaumlauml yleensauml sen kumpaa tapaa kannattaa kaumlyttaumlauml (Ndegwa 2016) Yleisin tapa on rakentaa valmis HTML-dokumentti kokonaan palvelimella jonka jaumllkeen se laumlhetetaumlaumln sellaisenaan kaumlyttaumljaumllle Taumlmauml tapa soveltuu

2

hyvin sivustoille ja sovelluksille jotka eivaumlt sisaumlllauml isoja maumlaumlriauml dynaamista sisaumlltoumlauml Taumlllaiset sivustot on myoumls helpompi hakukoneoptimoida koska tavallisimmat hakukoneiden kaumlyttaumlmaumlt botit eivaumlt pysty renderoumlimaumlaumln si-saumlltoumlauml paikallisesti (Vega 2017) Kun sivun HTML on tarkoitus renderoumlidauml paumlaumlasiassa selaimella palvelin lauml-hettaumlauml vain pienen paumltkaumln HTML-koodia JavaScript-koodin lisaumlksi Loput HTMLstauml rakennetaan vasta kaumlyttaumljaumln selaimessa JavaScript-koodin avulla JavaScript-koodin ja sen laumlhettaumlmien palvelinkutsujen ajamisessa kestaumlauml hetken jonka takia dynaamisen sisaumllloumln tilalla naumlkyy usein placehol-der-sisaumlltoumlauml (Vega 2017)

3 WEB-SOVELLUSKEHYKSET

Sovelluskehyksellauml tarkoitetaan ohjelmistoa ja kirjastoja jotka muodosta-vat rungon sen paumlaumllle rakennettavalle sovellukselle Sovelluskehys ei siis ole valmis kaumlytettaumlvissauml oleva ohjelma vaan toimii ainoastaan pohjana si-saumlltaumlen yleisimmin tarvittavat ominaisuudet Sovelluskehyksen taumlrkeim-paumlnauml tehtaumlvaumlnauml on vaumlhentaumlauml usein kaumlytettyjen ominaisuuksien ohjelmoin-tia uudelleen eli ne sisaumlltaumlvaumlt valmiiksi perustoiminnallisuudet ja naumlin ly-hentaumlvaumlt sovelluksen kehitykseen menevaumlauml aikaa merkittaumlvaumlsti (Niemi 2015)

Web-sovelluskehykset ovat nimensauml mukaisesti web-sovelluksien tekoon tarkoitettuja sovelluskehyksiauml Web-sovelluskehyksiauml on tehty laumlhes jokai-selle suositulle ohjelmointikielelle matalan tason C++-kielestauml korkean ta-son C-kieleen Taumlllauml hetkellauml yleisimmaumlt web-sovelluksien tekoon kaumlytet-taumlvaumlt kielet ovat C PHP Java JavaScript Python ja Ruby Web-sovellus-kehykset voidaan jakaa kahteen eri kategoriaan sen perusteella toimivatko ne asiakaspuolella vai palvelinpuolella (Niemi 2015)

31 Mikrosovelluskehykset

Massiivisten sovelluskehysten lisaumlksi on olemassa myoumls pienempiauml sovel-luskehyksiauml jotka sisaumlltaumlvaumlt hyvin vaumlhaumln sisaumlaumlnrakennettuja toimintoja ja ominaisuuksia Naumlitauml kaumlyttaumlen myoumls projektin rakenne on vapaampi jonka ansiosta ohjelmoijalla on vapaammat kaumldet sovelluksen suhteen Yksi esi-merkki taumlllaisesta sovelluskehyksestauml on Python-pohjainen Flask jossa lauml-hes jokainen ominaisuus taumlytyy tehdauml itse tai asentaa erikseen laajennuk-sena Myoumls erittaumlin suosittu Nodejs-pohjainen Expressjs lukeutuu samaan kategoriaan (Ronacher 2017)

3

32 Suosituimmat web-sovelluskehykset

Perehdyin eri web-sovelluskehysten suosioon HotFrameworks on sivusto joka mittaa eri sovelluskehysten suosiota niiden GitHub- ja Stack Overflow -aktiivisuuden perusteella Microsoftin sovelluskehysten GitHub-pisteet kuitenkin puuttuivat kokonaan jonka takia niiden todellinen suosio on to-dennaumlkoumlisesti HotFrameworks-sivuston tuloksia pienempi (Kuva 1) Muilta osin pidin HotFrameworks-sivuston tuloksia luotettavina (HotFrameworks 2018)

Kuva 1 Suosituimmat web-sovelluskehykset HotFrameworks-sivuston mukaan

33 Asiakaspuolen web-sovelluskehykset

Asiakaspuolen web-sovelluskehyksillauml rakennetaan paumlaumlasiassa selaimessa toimivia JavaScript-pohjaisia sovelluksia Ne tavallisesti keskittyvaumlt dynaa-misen datan kaumlsittelyn helpottamiseen ja sen naumlyttaumlmiseen HTML-sivulla Palvelimien ja tietokantojen kanssa keskustelemiseen asiakaspuolen sovel-lukset kaumlyttaumlvaumlt REST-rajapintoja Kaumlytaumlnnoumlssauml kaikki asiakaspuolen web-sovelluskehykset kaumlyttaumlvaumlt JavaScriptia tai jotain siihen pohjautuvaa kieltauml

331 AngularJS ja Angular

AngularJS ja Angular ovat Googlen yllaumlpitaumlmiauml avointa laumlhdekoodia olevia asiakaspuolen sovelluksien rakentamiseen tarkoitettuja sovelluskehyksiauml

4

AngularJS-nimellauml tarkoitetaan vanhempaa 1x-versiota ja pelkaumlllauml Angu-lar-nimellauml tarkoitetaan uudelleenkirjoituksen jaumllkeisiauml versioita (Angular nd) AngularJS on Miško Heveryn kehittaumlmauml sovelluskehys joka oli aluksi vain apuna Googlen sisaumlisissauml projekteissa Sovelluskehyksen ensimmaumlinen jul-kinen versio julkaistiin vuonna 2010 avoimen laumlhdekoodin projektina An-gularJS oli kuitenkin ominaisuuksiltaan rajattu eikauml se soveltunut kunnolla isojen sovellusten rakentamiseen (Gavigan 2018) Sovelluskehykselle tehtiin kokonainen uudelleenkirjoitus josta vastasi Google Uusi versio julkaistiin vuonna 2016 versionumerolla 20 Uudel-leenkirjoituksesta johtuen syntaksi ja sovellusten rakenne oli erilainen Se ei myoumlskaumlaumln ollut taaksepaumlin yhteensopiva vanhempien AngularJS-sovel-lusten kanssa Uudelleenkirjoitettu versio kaumlytti oletuksena JavaScriptin si-jasta TypeScriptia joka lisaumlauml JavaScriptiin uusia ominaisuuksia (Gavigan 2018)

332 React

React on Facebookin yllaumlpitaumlmauml JavaScript-pohjainen kaumlyttoumlliittymien te-koon tarkoitettu sovelluskehys Ensimmaumlinen julkinen versio Reactista jul-kaistiin vuonna 2013 Web-sovellusten lisaumlksi Reactia voi kaumlyttaumlauml mobiiliso-vellusten tekoon Mobiilisovellusten tekoon tarkoitettu kirjasto on nimel-taumlaumln React Native Virtuaalisen DOMin ansiosta dynaamisen ja muuttuvan datan esittaumlminen on erittaumlin nopeaa Kun ainoastaan yhtauml sivun elementtiauml muutetaan riit-taumlauml ettauml ainoastaan kyseinen elementti renderoumlidaumlaumln uudelleen Tavallista DOMia kaumlytettaumlessauml koko sivu pitaumlisi renderoumlidauml uudelleen (Reactjs nd) React-sovellukset rakentuvat komponenteista Komponentit kaumlyttaumlvaumlt JSX-syntaksia jonka avulla voi sekoittaa JavaScript- ja HTML-koodia keske-naumlaumln Taumlmaumln avulla voi esimerkiksi asettaa funktion palauttamaan HTML-elementtejauml mikauml helpottaa ja nopeuttaa kehitystauml (Reactjs nd)

333 Vue

Vue on avointa laumlhdekoodia oleva asiakaspuolen sovelluksien kehittaumlmi-seen tarkoitettu sovelluskehys Vuen mukana tuleva ydinkirjasto keskittyy paumlaumlasiassa naumlkymien rakentamiseen jonka takia sitauml on helppo kaumlyttaumlauml myoumls muiden sovelluskehysten ja tekniikoiden kanssa Pelkaumlllauml Vuella voi myoumls tehdauml kokonaisia SPA-sovelluksia mutta se vaatii ydinkirjaston lisaumlksi muita kirjastoja Muut kirjastot hoitavat esimerkiksi URL-reititykset (Vuejs nd a)

5

Ominaisuuksiltaan Vue on samankaltainen Reactin kanssa Molemmat hyoumldyntaumlvaumlt virtuaalista DOMia ja sovelluksen eri osat rakentuvat kom-ponenteista Vue tukee myoumls tarvittaessa Reactista tuttua JSX-syntaksia vaikkakin oletuksena kaumlytoumlssauml on yksinkertaisempi HTML-pohjainen sivu-pohja-jaumlrjestelmauml (Vuejs nd b)

34 Palvelinpuolen web-sovelluskehykset

Palvelinpuolen web-sovelluskehykset ovat tavallisesti asiakaspuolen web-sovelluskehyksiauml isompia koska ne tarvitsevat enemmaumln eri ominaisuuk-sia Palvelimella on tavallisesti yhteys tietokantaan joka jo sinaumlllaumlaumln aset-taa haasteita muun muassa tietoturvan suhteen Palvelimen taumlytyy myoumls pystyauml palvelemaan useaa kaumlyttaumljaumlauml samanaikaisesti Palvelinpuolen web-sovelluskehykset sisaumlltaumlvaumlt yleensauml vaumlhintaumlaumln seuraa-vat ominaisuudet sisaumlaumlnrakennettuina - tuki HTTP-pyyntoumljen vastaanottamiseen ja laumlhettaumlmiseen - tuki REST-rajapintojen tekoon - kaumlyttaumljaumltilit - lomakkeet ja niiden validointi - vaumllimuisti ja istunnot - sivupohjamoottori - tuki monelle eri tietokannalle - ORM-jaumlrjestelmauml - MVC-arkkitehtuuri tai vastaava - testaustyoumlkalut - suojaus yleisiauml haavoittuvuuksia vastaan (Niemi 2015)

341 ASPNET Core

ASPNET Core on Microsoftin kehittaumlmauml ja markkinoima web-sovelluske-hys Se on ASPNET-sovelluskehysten uusin ja modernein iteraatio ja se jul-kaistiin vuonna 2016 ASPNET Core on avointa laumlhdekoodia ja toi muka-naan huomattavasti aiempaa paremman tuen eri kaumlyttoumljaumlrjestelmille Mic-rosoftin lisaumlksi myoumls kaumlyttaumljaumlt paumlaumlsevaumlt osallistumaan tuotteen jatkokehi-tykseen ASPNET Core on rakennettu Common Language Runtimen paumlaumllle joten se tukee kaikkia NET kieliauml joista yleisimmaumlt ovat C ja Visual Basic NET (Microsoft 2018)

342 Express

Express on Nodejs-pohjainen mikrosovelluskehys Nodejs-pohjan ansi-osta se toimii asynkronisesti joka tekee siitauml toiminnaltaan varsin erilaisen muihin taumlssauml tyoumlssauml mainittuihin web-sovelluskehyksiin verrattuna Ex-press on projektin arkkitehtuurin kannalta hyvin avoin eikauml tee juurikaan paumlaumltoumlksiauml ohjelmoijan puolesta

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 7: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

1

1 JOHDANTO

Taumlssauml opinnaumlytetyoumlssauml tutustutaan web-sovelluksen rakentamiseen Pyt-hon-ohjelmointikieltauml kaumlyttaumlvaumlllauml Django-sovelluskehyksellauml Tyoumln alussa kaumlydaumlaumln laumlpi yleisesti mitauml web-sovelluksella ja sovelluskehyksellauml tarkoi-tetaan ja mitauml ominaisuuksia eri sovelluskehykset tavallisesti sisaumlltaumlvaumlt Taumlmaumln jaumllkeen tutustutaan taumlmaumln hetken suosituimpiin web-sovelluske-hyksiin Sitten tyoumlssauml perehdytaumlaumln Python-ohjelmointikieleen ja tarkem-min Djangon ominaisuuksiin ja toimintaperiaatteisiin Projektiosuuden alussa kaumlydaumlaumln laumlpi projektissa kaumlytettaumlvaumlt tyoumlkalut ja kehitysympaumlristouml Opinnaumlytetyoumln tavoitteena oli luoda kaumlyttoumlvalmis pitkien linkkien lyhentauml-miseen keskittynyt web-sovellus Djangolla Sovelluksen vaatimuksina oli linkkien lyhentaumlmisen lisaumlksi tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnoin-tiin Opinnaumlytetyoumlssauml dokumentoitiin sovelluksen keskeisimmaumlt ominai-suudet Tyoumln lopussa pohditaan mahdollisia jatkokehitykseen liittyviauml ide-oita ja kaumlydaumlaumln laumlpi tyoumlssauml ilmenneitauml ongelmia Opinnaumlytetyouml on suunnattu henkiloumlille jotka ovat kiinnostuneita web-so-vellusten kehityksestauml tai yleisesti web-sovelluskehyksistauml Opinnaumlytetyouml olettaa lukijalta jonkin tasoista ymmaumlrrystauml ohjelmoinnin perusteista

2 WEB-SOVELLUKSET

Web-sovelluksella tarkoitetaan sovellusta joka suoritetaan kaumlyttaumlen web-selainta Tavallisesta www-sivusta web-sovellus eroaa siten ettauml se sisaumll-taumlauml dynaamista ja kaumlyttaumljaumln syoumltteen perusteella muuttuvaa dataa Web-sovellus koostuu tavallisesti kaumlyttaumljaumln selaimessa suoritettavasta HTML CSS ndash ja JavaScript-koodista ja se keskustelee palvelimella olevan koodin kanssa Web-sovellus voi toimia myoumls kokonaan ilman palvelinta Esi-merkki taumlllaisesta sovelluksesta olisi JavaScriptilla tehty laskin jonka omi-naisuudet eivaumlt tarvitse palvelimella olevaa tietokantaa tai laskentatehoa (Ndegwa 2016) Web-sovelluksen HTML-rakenne ja sisaumlltouml voidaan renderoumlidauml joko palve-limella tai vasta kaumlyttaumljaumln selaimessa Renderoumlinnillauml tarkoitetaan kaumlyttauml-jaumln selaimessa lopulta naumlkyvaumln HTML-sivuston rakentamista yhdestauml tai useammasta osasta Kummallakin tavalla on omat hyoumldyt ja haitat ja so-velluksen kaumlyttoumltarkoitus maumlaumlrittaumlauml yleensauml sen kumpaa tapaa kannattaa kaumlyttaumlauml (Ndegwa 2016) Yleisin tapa on rakentaa valmis HTML-dokumentti kokonaan palvelimella jonka jaumllkeen se laumlhetetaumlaumln sellaisenaan kaumlyttaumljaumllle Taumlmauml tapa soveltuu

2

hyvin sivustoille ja sovelluksille jotka eivaumlt sisaumlllauml isoja maumlaumlriauml dynaamista sisaumlltoumlauml Taumlllaiset sivustot on myoumls helpompi hakukoneoptimoida koska tavallisimmat hakukoneiden kaumlyttaumlmaumlt botit eivaumlt pysty renderoumlimaumlaumln si-saumlltoumlauml paikallisesti (Vega 2017) Kun sivun HTML on tarkoitus renderoumlidauml paumlaumlasiassa selaimella palvelin lauml-hettaumlauml vain pienen paumltkaumln HTML-koodia JavaScript-koodin lisaumlksi Loput HTMLstauml rakennetaan vasta kaumlyttaumljaumln selaimessa JavaScript-koodin avulla JavaScript-koodin ja sen laumlhettaumlmien palvelinkutsujen ajamisessa kestaumlauml hetken jonka takia dynaamisen sisaumllloumln tilalla naumlkyy usein placehol-der-sisaumlltoumlauml (Vega 2017)

3 WEB-SOVELLUSKEHYKSET

Sovelluskehyksellauml tarkoitetaan ohjelmistoa ja kirjastoja jotka muodosta-vat rungon sen paumlaumllle rakennettavalle sovellukselle Sovelluskehys ei siis ole valmis kaumlytettaumlvissauml oleva ohjelma vaan toimii ainoastaan pohjana si-saumlltaumlen yleisimmin tarvittavat ominaisuudet Sovelluskehyksen taumlrkeim-paumlnauml tehtaumlvaumlnauml on vaumlhentaumlauml usein kaumlytettyjen ominaisuuksien ohjelmoin-tia uudelleen eli ne sisaumlltaumlvaumlt valmiiksi perustoiminnallisuudet ja naumlin ly-hentaumlvaumlt sovelluksen kehitykseen menevaumlauml aikaa merkittaumlvaumlsti (Niemi 2015)

Web-sovelluskehykset ovat nimensauml mukaisesti web-sovelluksien tekoon tarkoitettuja sovelluskehyksiauml Web-sovelluskehyksiauml on tehty laumlhes jokai-selle suositulle ohjelmointikielelle matalan tason C++-kielestauml korkean ta-son C-kieleen Taumlllauml hetkellauml yleisimmaumlt web-sovelluksien tekoon kaumlytet-taumlvaumlt kielet ovat C PHP Java JavaScript Python ja Ruby Web-sovellus-kehykset voidaan jakaa kahteen eri kategoriaan sen perusteella toimivatko ne asiakaspuolella vai palvelinpuolella (Niemi 2015)

31 Mikrosovelluskehykset

Massiivisten sovelluskehysten lisaumlksi on olemassa myoumls pienempiauml sovel-luskehyksiauml jotka sisaumlltaumlvaumlt hyvin vaumlhaumln sisaumlaumlnrakennettuja toimintoja ja ominaisuuksia Naumlitauml kaumlyttaumlen myoumls projektin rakenne on vapaampi jonka ansiosta ohjelmoijalla on vapaammat kaumldet sovelluksen suhteen Yksi esi-merkki taumlllaisesta sovelluskehyksestauml on Python-pohjainen Flask jossa lauml-hes jokainen ominaisuus taumlytyy tehdauml itse tai asentaa erikseen laajennuk-sena Myoumls erittaumlin suosittu Nodejs-pohjainen Expressjs lukeutuu samaan kategoriaan (Ronacher 2017)

3

32 Suosituimmat web-sovelluskehykset

Perehdyin eri web-sovelluskehysten suosioon HotFrameworks on sivusto joka mittaa eri sovelluskehysten suosiota niiden GitHub- ja Stack Overflow -aktiivisuuden perusteella Microsoftin sovelluskehysten GitHub-pisteet kuitenkin puuttuivat kokonaan jonka takia niiden todellinen suosio on to-dennaumlkoumlisesti HotFrameworks-sivuston tuloksia pienempi (Kuva 1) Muilta osin pidin HotFrameworks-sivuston tuloksia luotettavina (HotFrameworks 2018)

Kuva 1 Suosituimmat web-sovelluskehykset HotFrameworks-sivuston mukaan

33 Asiakaspuolen web-sovelluskehykset

Asiakaspuolen web-sovelluskehyksillauml rakennetaan paumlaumlasiassa selaimessa toimivia JavaScript-pohjaisia sovelluksia Ne tavallisesti keskittyvaumlt dynaa-misen datan kaumlsittelyn helpottamiseen ja sen naumlyttaumlmiseen HTML-sivulla Palvelimien ja tietokantojen kanssa keskustelemiseen asiakaspuolen sovel-lukset kaumlyttaumlvaumlt REST-rajapintoja Kaumlytaumlnnoumlssauml kaikki asiakaspuolen web-sovelluskehykset kaumlyttaumlvaumlt JavaScriptia tai jotain siihen pohjautuvaa kieltauml

331 AngularJS ja Angular

AngularJS ja Angular ovat Googlen yllaumlpitaumlmiauml avointa laumlhdekoodia olevia asiakaspuolen sovelluksien rakentamiseen tarkoitettuja sovelluskehyksiauml

4

AngularJS-nimellauml tarkoitetaan vanhempaa 1x-versiota ja pelkaumlllauml Angu-lar-nimellauml tarkoitetaan uudelleenkirjoituksen jaumllkeisiauml versioita (Angular nd) AngularJS on Miško Heveryn kehittaumlmauml sovelluskehys joka oli aluksi vain apuna Googlen sisaumlisissauml projekteissa Sovelluskehyksen ensimmaumlinen jul-kinen versio julkaistiin vuonna 2010 avoimen laumlhdekoodin projektina An-gularJS oli kuitenkin ominaisuuksiltaan rajattu eikauml se soveltunut kunnolla isojen sovellusten rakentamiseen (Gavigan 2018) Sovelluskehykselle tehtiin kokonainen uudelleenkirjoitus josta vastasi Google Uusi versio julkaistiin vuonna 2016 versionumerolla 20 Uudel-leenkirjoituksesta johtuen syntaksi ja sovellusten rakenne oli erilainen Se ei myoumlskaumlaumln ollut taaksepaumlin yhteensopiva vanhempien AngularJS-sovel-lusten kanssa Uudelleenkirjoitettu versio kaumlytti oletuksena JavaScriptin si-jasta TypeScriptia joka lisaumlauml JavaScriptiin uusia ominaisuuksia (Gavigan 2018)

332 React

React on Facebookin yllaumlpitaumlmauml JavaScript-pohjainen kaumlyttoumlliittymien te-koon tarkoitettu sovelluskehys Ensimmaumlinen julkinen versio Reactista jul-kaistiin vuonna 2013 Web-sovellusten lisaumlksi Reactia voi kaumlyttaumlauml mobiiliso-vellusten tekoon Mobiilisovellusten tekoon tarkoitettu kirjasto on nimel-taumlaumln React Native Virtuaalisen DOMin ansiosta dynaamisen ja muuttuvan datan esittaumlminen on erittaumlin nopeaa Kun ainoastaan yhtauml sivun elementtiauml muutetaan riit-taumlauml ettauml ainoastaan kyseinen elementti renderoumlidaumlaumln uudelleen Tavallista DOMia kaumlytettaumlessauml koko sivu pitaumlisi renderoumlidauml uudelleen (Reactjs nd) React-sovellukset rakentuvat komponenteista Komponentit kaumlyttaumlvaumlt JSX-syntaksia jonka avulla voi sekoittaa JavaScript- ja HTML-koodia keske-naumlaumln Taumlmaumln avulla voi esimerkiksi asettaa funktion palauttamaan HTML-elementtejauml mikauml helpottaa ja nopeuttaa kehitystauml (Reactjs nd)

333 Vue

Vue on avointa laumlhdekoodia oleva asiakaspuolen sovelluksien kehittaumlmi-seen tarkoitettu sovelluskehys Vuen mukana tuleva ydinkirjasto keskittyy paumlaumlasiassa naumlkymien rakentamiseen jonka takia sitauml on helppo kaumlyttaumlauml myoumls muiden sovelluskehysten ja tekniikoiden kanssa Pelkaumlllauml Vuella voi myoumls tehdauml kokonaisia SPA-sovelluksia mutta se vaatii ydinkirjaston lisaumlksi muita kirjastoja Muut kirjastot hoitavat esimerkiksi URL-reititykset (Vuejs nd a)

5

Ominaisuuksiltaan Vue on samankaltainen Reactin kanssa Molemmat hyoumldyntaumlvaumlt virtuaalista DOMia ja sovelluksen eri osat rakentuvat kom-ponenteista Vue tukee myoumls tarvittaessa Reactista tuttua JSX-syntaksia vaikkakin oletuksena kaumlytoumlssauml on yksinkertaisempi HTML-pohjainen sivu-pohja-jaumlrjestelmauml (Vuejs nd b)

34 Palvelinpuolen web-sovelluskehykset

Palvelinpuolen web-sovelluskehykset ovat tavallisesti asiakaspuolen web-sovelluskehyksiauml isompia koska ne tarvitsevat enemmaumln eri ominaisuuk-sia Palvelimella on tavallisesti yhteys tietokantaan joka jo sinaumlllaumlaumln aset-taa haasteita muun muassa tietoturvan suhteen Palvelimen taumlytyy myoumls pystyauml palvelemaan useaa kaumlyttaumljaumlauml samanaikaisesti Palvelinpuolen web-sovelluskehykset sisaumlltaumlvaumlt yleensauml vaumlhintaumlaumln seuraa-vat ominaisuudet sisaumlaumlnrakennettuina - tuki HTTP-pyyntoumljen vastaanottamiseen ja laumlhettaumlmiseen - tuki REST-rajapintojen tekoon - kaumlyttaumljaumltilit - lomakkeet ja niiden validointi - vaumllimuisti ja istunnot - sivupohjamoottori - tuki monelle eri tietokannalle - ORM-jaumlrjestelmauml - MVC-arkkitehtuuri tai vastaava - testaustyoumlkalut - suojaus yleisiauml haavoittuvuuksia vastaan (Niemi 2015)

341 ASPNET Core

ASPNET Core on Microsoftin kehittaumlmauml ja markkinoima web-sovelluske-hys Se on ASPNET-sovelluskehysten uusin ja modernein iteraatio ja se jul-kaistiin vuonna 2016 ASPNET Core on avointa laumlhdekoodia ja toi muka-naan huomattavasti aiempaa paremman tuen eri kaumlyttoumljaumlrjestelmille Mic-rosoftin lisaumlksi myoumls kaumlyttaumljaumlt paumlaumlsevaumlt osallistumaan tuotteen jatkokehi-tykseen ASPNET Core on rakennettu Common Language Runtimen paumlaumllle joten se tukee kaikkia NET kieliauml joista yleisimmaumlt ovat C ja Visual Basic NET (Microsoft 2018)

342 Express

Express on Nodejs-pohjainen mikrosovelluskehys Nodejs-pohjan ansi-osta se toimii asynkronisesti joka tekee siitauml toiminnaltaan varsin erilaisen muihin taumlssauml tyoumlssauml mainittuihin web-sovelluskehyksiin verrattuna Ex-press on projektin arkkitehtuurin kannalta hyvin avoin eikauml tee juurikaan paumlaumltoumlksiauml ohjelmoijan puolesta

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 8: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

2

hyvin sivustoille ja sovelluksille jotka eivaumlt sisaumlllauml isoja maumlaumlriauml dynaamista sisaumlltoumlauml Taumlllaiset sivustot on myoumls helpompi hakukoneoptimoida koska tavallisimmat hakukoneiden kaumlyttaumlmaumlt botit eivaumlt pysty renderoumlimaumlaumln si-saumlltoumlauml paikallisesti (Vega 2017) Kun sivun HTML on tarkoitus renderoumlidauml paumlaumlasiassa selaimella palvelin lauml-hettaumlauml vain pienen paumltkaumln HTML-koodia JavaScript-koodin lisaumlksi Loput HTMLstauml rakennetaan vasta kaumlyttaumljaumln selaimessa JavaScript-koodin avulla JavaScript-koodin ja sen laumlhettaumlmien palvelinkutsujen ajamisessa kestaumlauml hetken jonka takia dynaamisen sisaumllloumln tilalla naumlkyy usein placehol-der-sisaumlltoumlauml (Vega 2017)

3 WEB-SOVELLUSKEHYKSET

Sovelluskehyksellauml tarkoitetaan ohjelmistoa ja kirjastoja jotka muodosta-vat rungon sen paumlaumllle rakennettavalle sovellukselle Sovelluskehys ei siis ole valmis kaumlytettaumlvissauml oleva ohjelma vaan toimii ainoastaan pohjana si-saumlltaumlen yleisimmin tarvittavat ominaisuudet Sovelluskehyksen taumlrkeim-paumlnauml tehtaumlvaumlnauml on vaumlhentaumlauml usein kaumlytettyjen ominaisuuksien ohjelmoin-tia uudelleen eli ne sisaumlltaumlvaumlt valmiiksi perustoiminnallisuudet ja naumlin ly-hentaumlvaumlt sovelluksen kehitykseen menevaumlauml aikaa merkittaumlvaumlsti (Niemi 2015)

Web-sovelluskehykset ovat nimensauml mukaisesti web-sovelluksien tekoon tarkoitettuja sovelluskehyksiauml Web-sovelluskehyksiauml on tehty laumlhes jokai-selle suositulle ohjelmointikielelle matalan tason C++-kielestauml korkean ta-son C-kieleen Taumlllauml hetkellauml yleisimmaumlt web-sovelluksien tekoon kaumlytet-taumlvaumlt kielet ovat C PHP Java JavaScript Python ja Ruby Web-sovellus-kehykset voidaan jakaa kahteen eri kategoriaan sen perusteella toimivatko ne asiakaspuolella vai palvelinpuolella (Niemi 2015)

31 Mikrosovelluskehykset

Massiivisten sovelluskehysten lisaumlksi on olemassa myoumls pienempiauml sovel-luskehyksiauml jotka sisaumlltaumlvaumlt hyvin vaumlhaumln sisaumlaumlnrakennettuja toimintoja ja ominaisuuksia Naumlitauml kaumlyttaumlen myoumls projektin rakenne on vapaampi jonka ansiosta ohjelmoijalla on vapaammat kaumldet sovelluksen suhteen Yksi esi-merkki taumlllaisesta sovelluskehyksestauml on Python-pohjainen Flask jossa lauml-hes jokainen ominaisuus taumlytyy tehdauml itse tai asentaa erikseen laajennuk-sena Myoumls erittaumlin suosittu Nodejs-pohjainen Expressjs lukeutuu samaan kategoriaan (Ronacher 2017)

3

32 Suosituimmat web-sovelluskehykset

Perehdyin eri web-sovelluskehysten suosioon HotFrameworks on sivusto joka mittaa eri sovelluskehysten suosiota niiden GitHub- ja Stack Overflow -aktiivisuuden perusteella Microsoftin sovelluskehysten GitHub-pisteet kuitenkin puuttuivat kokonaan jonka takia niiden todellinen suosio on to-dennaumlkoumlisesti HotFrameworks-sivuston tuloksia pienempi (Kuva 1) Muilta osin pidin HotFrameworks-sivuston tuloksia luotettavina (HotFrameworks 2018)

Kuva 1 Suosituimmat web-sovelluskehykset HotFrameworks-sivuston mukaan

33 Asiakaspuolen web-sovelluskehykset

Asiakaspuolen web-sovelluskehyksillauml rakennetaan paumlaumlasiassa selaimessa toimivia JavaScript-pohjaisia sovelluksia Ne tavallisesti keskittyvaumlt dynaa-misen datan kaumlsittelyn helpottamiseen ja sen naumlyttaumlmiseen HTML-sivulla Palvelimien ja tietokantojen kanssa keskustelemiseen asiakaspuolen sovel-lukset kaumlyttaumlvaumlt REST-rajapintoja Kaumlytaumlnnoumlssauml kaikki asiakaspuolen web-sovelluskehykset kaumlyttaumlvaumlt JavaScriptia tai jotain siihen pohjautuvaa kieltauml

331 AngularJS ja Angular

AngularJS ja Angular ovat Googlen yllaumlpitaumlmiauml avointa laumlhdekoodia olevia asiakaspuolen sovelluksien rakentamiseen tarkoitettuja sovelluskehyksiauml

4

AngularJS-nimellauml tarkoitetaan vanhempaa 1x-versiota ja pelkaumlllauml Angu-lar-nimellauml tarkoitetaan uudelleenkirjoituksen jaumllkeisiauml versioita (Angular nd) AngularJS on Miško Heveryn kehittaumlmauml sovelluskehys joka oli aluksi vain apuna Googlen sisaumlisissauml projekteissa Sovelluskehyksen ensimmaumlinen jul-kinen versio julkaistiin vuonna 2010 avoimen laumlhdekoodin projektina An-gularJS oli kuitenkin ominaisuuksiltaan rajattu eikauml se soveltunut kunnolla isojen sovellusten rakentamiseen (Gavigan 2018) Sovelluskehykselle tehtiin kokonainen uudelleenkirjoitus josta vastasi Google Uusi versio julkaistiin vuonna 2016 versionumerolla 20 Uudel-leenkirjoituksesta johtuen syntaksi ja sovellusten rakenne oli erilainen Se ei myoumlskaumlaumln ollut taaksepaumlin yhteensopiva vanhempien AngularJS-sovel-lusten kanssa Uudelleenkirjoitettu versio kaumlytti oletuksena JavaScriptin si-jasta TypeScriptia joka lisaumlauml JavaScriptiin uusia ominaisuuksia (Gavigan 2018)

332 React

React on Facebookin yllaumlpitaumlmauml JavaScript-pohjainen kaumlyttoumlliittymien te-koon tarkoitettu sovelluskehys Ensimmaumlinen julkinen versio Reactista jul-kaistiin vuonna 2013 Web-sovellusten lisaumlksi Reactia voi kaumlyttaumlauml mobiiliso-vellusten tekoon Mobiilisovellusten tekoon tarkoitettu kirjasto on nimel-taumlaumln React Native Virtuaalisen DOMin ansiosta dynaamisen ja muuttuvan datan esittaumlminen on erittaumlin nopeaa Kun ainoastaan yhtauml sivun elementtiauml muutetaan riit-taumlauml ettauml ainoastaan kyseinen elementti renderoumlidaumlaumln uudelleen Tavallista DOMia kaumlytettaumlessauml koko sivu pitaumlisi renderoumlidauml uudelleen (Reactjs nd) React-sovellukset rakentuvat komponenteista Komponentit kaumlyttaumlvaumlt JSX-syntaksia jonka avulla voi sekoittaa JavaScript- ja HTML-koodia keske-naumlaumln Taumlmaumln avulla voi esimerkiksi asettaa funktion palauttamaan HTML-elementtejauml mikauml helpottaa ja nopeuttaa kehitystauml (Reactjs nd)

333 Vue

Vue on avointa laumlhdekoodia oleva asiakaspuolen sovelluksien kehittaumlmi-seen tarkoitettu sovelluskehys Vuen mukana tuleva ydinkirjasto keskittyy paumlaumlasiassa naumlkymien rakentamiseen jonka takia sitauml on helppo kaumlyttaumlauml myoumls muiden sovelluskehysten ja tekniikoiden kanssa Pelkaumlllauml Vuella voi myoumls tehdauml kokonaisia SPA-sovelluksia mutta se vaatii ydinkirjaston lisaumlksi muita kirjastoja Muut kirjastot hoitavat esimerkiksi URL-reititykset (Vuejs nd a)

5

Ominaisuuksiltaan Vue on samankaltainen Reactin kanssa Molemmat hyoumldyntaumlvaumlt virtuaalista DOMia ja sovelluksen eri osat rakentuvat kom-ponenteista Vue tukee myoumls tarvittaessa Reactista tuttua JSX-syntaksia vaikkakin oletuksena kaumlytoumlssauml on yksinkertaisempi HTML-pohjainen sivu-pohja-jaumlrjestelmauml (Vuejs nd b)

34 Palvelinpuolen web-sovelluskehykset

Palvelinpuolen web-sovelluskehykset ovat tavallisesti asiakaspuolen web-sovelluskehyksiauml isompia koska ne tarvitsevat enemmaumln eri ominaisuuk-sia Palvelimella on tavallisesti yhteys tietokantaan joka jo sinaumlllaumlaumln aset-taa haasteita muun muassa tietoturvan suhteen Palvelimen taumlytyy myoumls pystyauml palvelemaan useaa kaumlyttaumljaumlauml samanaikaisesti Palvelinpuolen web-sovelluskehykset sisaumlltaumlvaumlt yleensauml vaumlhintaumlaumln seuraa-vat ominaisuudet sisaumlaumlnrakennettuina - tuki HTTP-pyyntoumljen vastaanottamiseen ja laumlhettaumlmiseen - tuki REST-rajapintojen tekoon - kaumlyttaumljaumltilit - lomakkeet ja niiden validointi - vaumllimuisti ja istunnot - sivupohjamoottori - tuki monelle eri tietokannalle - ORM-jaumlrjestelmauml - MVC-arkkitehtuuri tai vastaava - testaustyoumlkalut - suojaus yleisiauml haavoittuvuuksia vastaan (Niemi 2015)

341 ASPNET Core

ASPNET Core on Microsoftin kehittaumlmauml ja markkinoima web-sovelluske-hys Se on ASPNET-sovelluskehysten uusin ja modernein iteraatio ja se jul-kaistiin vuonna 2016 ASPNET Core on avointa laumlhdekoodia ja toi muka-naan huomattavasti aiempaa paremman tuen eri kaumlyttoumljaumlrjestelmille Mic-rosoftin lisaumlksi myoumls kaumlyttaumljaumlt paumlaumlsevaumlt osallistumaan tuotteen jatkokehi-tykseen ASPNET Core on rakennettu Common Language Runtimen paumlaumllle joten se tukee kaikkia NET kieliauml joista yleisimmaumlt ovat C ja Visual Basic NET (Microsoft 2018)

342 Express

Express on Nodejs-pohjainen mikrosovelluskehys Nodejs-pohjan ansi-osta se toimii asynkronisesti joka tekee siitauml toiminnaltaan varsin erilaisen muihin taumlssauml tyoumlssauml mainittuihin web-sovelluskehyksiin verrattuna Ex-press on projektin arkkitehtuurin kannalta hyvin avoin eikauml tee juurikaan paumlaumltoumlksiauml ohjelmoijan puolesta

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 9: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

3

32 Suosituimmat web-sovelluskehykset

Perehdyin eri web-sovelluskehysten suosioon HotFrameworks on sivusto joka mittaa eri sovelluskehysten suosiota niiden GitHub- ja Stack Overflow -aktiivisuuden perusteella Microsoftin sovelluskehysten GitHub-pisteet kuitenkin puuttuivat kokonaan jonka takia niiden todellinen suosio on to-dennaumlkoumlisesti HotFrameworks-sivuston tuloksia pienempi (Kuva 1) Muilta osin pidin HotFrameworks-sivuston tuloksia luotettavina (HotFrameworks 2018)

Kuva 1 Suosituimmat web-sovelluskehykset HotFrameworks-sivuston mukaan

33 Asiakaspuolen web-sovelluskehykset

Asiakaspuolen web-sovelluskehyksillauml rakennetaan paumlaumlasiassa selaimessa toimivia JavaScript-pohjaisia sovelluksia Ne tavallisesti keskittyvaumlt dynaa-misen datan kaumlsittelyn helpottamiseen ja sen naumlyttaumlmiseen HTML-sivulla Palvelimien ja tietokantojen kanssa keskustelemiseen asiakaspuolen sovel-lukset kaumlyttaumlvaumlt REST-rajapintoja Kaumlytaumlnnoumlssauml kaikki asiakaspuolen web-sovelluskehykset kaumlyttaumlvaumlt JavaScriptia tai jotain siihen pohjautuvaa kieltauml

331 AngularJS ja Angular

AngularJS ja Angular ovat Googlen yllaumlpitaumlmiauml avointa laumlhdekoodia olevia asiakaspuolen sovelluksien rakentamiseen tarkoitettuja sovelluskehyksiauml

4

AngularJS-nimellauml tarkoitetaan vanhempaa 1x-versiota ja pelkaumlllauml Angu-lar-nimellauml tarkoitetaan uudelleenkirjoituksen jaumllkeisiauml versioita (Angular nd) AngularJS on Miško Heveryn kehittaumlmauml sovelluskehys joka oli aluksi vain apuna Googlen sisaumlisissauml projekteissa Sovelluskehyksen ensimmaumlinen jul-kinen versio julkaistiin vuonna 2010 avoimen laumlhdekoodin projektina An-gularJS oli kuitenkin ominaisuuksiltaan rajattu eikauml se soveltunut kunnolla isojen sovellusten rakentamiseen (Gavigan 2018) Sovelluskehykselle tehtiin kokonainen uudelleenkirjoitus josta vastasi Google Uusi versio julkaistiin vuonna 2016 versionumerolla 20 Uudel-leenkirjoituksesta johtuen syntaksi ja sovellusten rakenne oli erilainen Se ei myoumlskaumlaumln ollut taaksepaumlin yhteensopiva vanhempien AngularJS-sovel-lusten kanssa Uudelleenkirjoitettu versio kaumlytti oletuksena JavaScriptin si-jasta TypeScriptia joka lisaumlauml JavaScriptiin uusia ominaisuuksia (Gavigan 2018)

332 React

React on Facebookin yllaumlpitaumlmauml JavaScript-pohjainen kaumlyttoumlliittymien te-koon tarkoitettu sovelluskehys Ensimmaumlinen julkinen versio Reactista jul-kaistiin vuonna 2013 Web-sovellusten lisaumlksi Reactia voi kaumlyttaumlauml mobiiliso-vellusten tekoon Mobiilisovellusten tekoon tarkoitettu kirjasto on nimel-taumlaumln React Native Virtuaalisen DOMin ansiosta dynaamisen ja muuttuvan datan esittaumlminen on erittaumlin nopeaa Kun ainoastaan yhtauml sivun elementtiauml muutetaan riit-taumlauml ettauml ainoastaan kyseinen elementti renderoumlidaumlaumln uudelleen Tavallista DOMia kaumlytettaumlessauml koko sivu pitaumlisi renderoumlidauml uudelleen (Reactjs nd) React-sovellukset rakentuvat komponenteista Komponentit kaumlyttaumlvaumlt JSX-syntaksia jonka avulla voi sekoittaa JavaScript- ja HTML-koodia keske-naumlaumln Taumlmaumln avulla voi esimerkiksi asettaa funktion palauttamaan HTML-elementtejauml mikauml helpottaa ja nopeuttaa kehitystauml (Reactjs nd)

333 Vue

Vue on avointa laumlhdekoodia oleva asiakaspuolen sovelluksien kehittaumlmi-seen tarkoitettu sovelluskehys Vuen mukana tuleva ydinkirjasto keskittyy paumlaumlasiassa naumlkymien rakentamiseen jonka takia sitauml on helppo kaumlyttaumlauml myoumls muiden sovelluskehysten ja tekniikoiden kanssa Pelkaumlllauml Vuella voi myoumls tehdauml kokonaisia SPA-sovelluksia mutta se vaatii ydinkirjaston lisaumlksi muita kirjastoja Muut kirjastot hoitavat esimerkiksi URL-reititykset (Vuejs nd a)

5

Ominaisuuksiltaan Vue on samankaltainen Reactin kanssa Molemmat hyoumldyntaumlvaumlt virtuaalista DOMia ja sovelluksen eri osat rakentuvat kom-ponenteista Vue tukee myoumls tarvittaessa Reactista tuttua JSX-syntaksia vaikkakin oletuksena kaumlytoumlssauml on yksinkertaisempi HTML-pohjainen sivu-pohja-jaumlrjestelmauml (Vuejs nd b)

34 Palvelinpuolen web-sovelluskehykset

Palvelinpuolen web-sovelluskehykset ovat tavallisesti asiakaspuolen web-sovelluskehyksiauml isompia koska ne tarvitsevat enemmaumln eri ominaisuuk-sia Palvelimella on tavallisesti yhteys tietokantaan joka jo sinaumlllaumlaumln aset-taa haasteita muun muassa tietoturvan suhteen Palvelimen taumlytyy myoumls pystyauml palvelemaan useaa kaumlyttaumljaumlauml samanaikaisesti Palvelinpuolen web-sovelluskehykset sisaumlltaumlvaumlt yleensauml vaumlhintaumlaumln seuraa-vat ominaisuudet sisaumlaumlnrakennettuina - tuki HTTP-pyyntoumljen vastaanottamiseen ja laumlhettaumlmiseen - tuki REST-rajapintojen tekoon - kaumlyttaumljaumltilit - lomakkeet ja niiden validointi - vaumllimuisti ja istunnot - sivupohjamoottori - tuki monelle eri tietokannalle - ORM-jaumlrjestelmauml - MVC-arkkitehtuuri tai vastaava - testaustyoumlkalut - suojaus yleisiauml haavoittuvuuksia vastaan (Niemi 2015)

341 ASPNET Core

ASPNET Core on Microsoftin kehittaumlmauml ja markkinoima web-sovelluske-hys Se on ASPNET-sovelluskehysten uusin ja modernein iteraatio ja se jul-kaistiin vuonna 2016 ASPNET Core on avointa laumlhdekoodia ja toi muka-naan huomattavasti aiempaa paremman tuen eri kaumlyttoumljaumlrjestelmille Mic-rosoftin lisaumlksi myoumls kaumlyttaumljaumlt paumlaumlsevaumlt osallistumaan tuotteen jatkokehi-tykseen ASPNET Core on rakennettu Common Language Runtimen paumlaumllle joten se tukee kaikkia NET kieliauml joista yleisimmaumlt ovat C ja Visual Basic NET (Microsoft 2018)

342 Express

Express on Nodejs-pohjainen mikrosovelluskehys Nodejs-pohjan ansi-osta se toimii asynkronisesti joka tekee siitauml toiminnaltaan varsin erilaisen muihin taumlssauml tyoumlssauml mainittuihin web-sovelluskehyksiin verrattuna Ex-press on projektin arkkitehtuurin kannalta hyvin avoin eikauml tee juurikaan paumlaumltoumlksiauml ohjelmoijan puolesta

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 10: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

4

AngularJS-nimellauml tarkoitetaan vanhempaa 1x-versiota ja pelkaumlllauml Angu-lar-nimellauml tarkoitetaan uudelleenkirjoituksen jaumllkeisiauml versioita (Angular nd) AngularJS on Miško Heveryn kehittaumlmauml sovelluskehys joka oli aluksi vain apuna Googlen sisaumlisissauml projekteissa Sovelluskehyksen ensimmaumlinen jul-kinen versio julkaistiin vuonna 2010 avoimen laumlhdekoodin projektina An-gularJS oli kuitenkin ominaisuuksiltaan rajattu eikauml se soveltunut kunnolla isojen sovellusten rakentamiseen (Gavigan 2018) Sovelluskehykselle tehtiin kokonainen uudelleenkirjoitus josta vastasi Google Uusi versio julkaistiin vuonna 2016 versionumerolla 20 Uudel-leenkirjoituksesta johtuen syntaksi ja sovellusten rakenne oli erilainen Se ei myoumlskaumlaumln ollut taaksepaumlin yhteensopiva vanhempien AngularJS-sovel-lusten kanssa Uudelleenkirjoitettu versio kaumlytti oletuksena JavaScriptin si-jasta TypeScriptia joka lisaumlauml JavaScriptiin uusia ominaisuuksia (Gavigan 2018)

332 React

React on Facebookin yllaumlpitaumlmauml JavaScript-pohjainen kaumlyttoumlliittymien te-koon tarkoitettu sovelluskehys Ensimmaumlinen julkinen versio Reactista jul-kaistiin vuonna 2013 Web-sovellusten lisaumlksi Reactia voi kaumlyttaumlauml mobiiliso-vellusten tekoon Mobiilisovellusten tekoon tarkoitettu kirjasto on nimel-taumlaumln React Native Virtuaalisen DOMin ansiosta dynaamisen ja muuttuvan datan esittaumlminen on erittaumlin nopeaa Kun ainoastaan yhtauml sivun elementtiauml muutetaan riit-taumlauml ettauml ainoastaan kyseinen elementti renderoumlidaumlaumln uudelleen Tavallista DOMia kaumlytettaumlessauml koko sivu pitaumlisi renderoumlidauml uudelleen (Reactjs nd) React-sovellukset rakentuvat komponenteista Komponentit kaumlyttaumlvaumlt JSX-syntaksia jonka avulla voi sekoittaa JavaScript- ja HTML-koodia keske-naumlaumln Taumlmaumln avulla voi esimerkiksi asettaa funktion palauttamaan HTML-elementtejauml mikauml helpottaa ja nopeuttaa kehitystauml (Reactjs nd)

333 Vue

Vue on avointa laumlhdekoodia oleva asiakaspuolen sovelluksien kehittaumlmi-seen tarkoitettu sovelluskehys Vuen mukana tuleva ydinkirjasto keskittyy paumlaumlasiassa naumlkymien rakentamiseen jonka takia sitauml on helppo kaumlyttaumlauml myoumls muiden sovelluskehysten ja tekniikoiden kanssa Pelkaumlllauml Vuella voi myoumls tehdauml kokonaisia SPA-sovelluksia mutta se vaatii ydinkirjaston lisaumlksi muita kirjastoja Muut kirjastot hoitavat esimerkiksi URL-reititykset (Vuejs nd a)

5

Ominaisuuksiltaan Vue on samankaltainen Reactin kanssa Molemmat hyoumldyntaumlvaumlt virtuaalista DOMia ja sovelluksen eri osat rakentuvat kom-ponenteista Vue tukee myoumls tarvittaessa Reactista tuttua JSX-syntaksia vaikkakin oletuksena kaumlytoumlssauml on yksinkertaisempi HTML-pohjainen sivu-pohja-jaumlrjestelmauml (Vuejs nd b)

34 Palvelinpuolen web-sovelluskehykset

Palvelinpuolen web-sovelluskehykset ovat tavallisesti asiakaspuolen web-sovelluskehyksiauml isompia koska ne tarvitsevat enemmaumln eri ominaisuuk-sia Palvelimella on tavallisesti yhteys tietokantaan joka jo sinaumlllaumlaumln aset-taa haasteita muun muassa tietoturvan suhteen Palvelimen taumlytyy myoumls pystyauml palvelemaan useaa kaumlyttaumljaumlauml samanaikaisesti Palvelinpuolen web-sovelluskehykset sisaumlltaumlvaumlt yleensauml vaumlhintaumlaumln seuraa-vat ominaisuudet sisaumlaumlnrakennettuina - tuki HTTP-pyyntoumljen vastaanottamiseen ja laumlhettaumlmiseen - tuki REST-rajapintojen tekoon - kaumlyttaumljaumltilit - lomakkeet ja niiden validointi - vaumllimuisti ja istunnot - sivupohjamoottori - tuki monelle eri tietokannalle - ORM-jaumlrjestelmauml - MVC-arkkitehtuuri tai vastaava - testaustyoumlkalut - suojaus yleisiauml haavoittuvuuksia vastaan (Niemi 2015)

341 ASPNET Core

ASPNET Core on Microsoftin kehittaumlmauml ja markkinoima web-sovelluske-hys Se on ASPNET-sovelluskehysten uusin ja modernein iteraatio ja se jul-kaistiin vuonna 2016 ASPNET Core on avointa laumlhdekoodia ja toi muka-naan huomattavasti aiempaa paremman tuen eri kaumlyttoumljaumlrjestelmille Mic-rosoftin lisaumlksi myoumls kaumlyttaumljaumlt paumlaumlsevaumlt osallistumaan tuotteen jatkokehi-tykseen ASPNET Core on rakennettu Common Language Runtimen paumlaumllle joten se tukee kaikkia NET kieliauml joista yleisimmaumlt ovat C ja Visual Basic NET (Microsoft 2018)

342 Express

Express on Nodejs-pohjainen mikrosovelluskehys Nodejs-pohjan ansi-osta se toimii asynkronisesti joka tekee siitauml toiminnaltaan varsin erilaisen muihin taumlssauml tyoumlssauml mainittuihin web-sovelluskehyksiin verrattuna Ex-press on projektin arkkitehtuurin kannalta hyvin avoin eikauml tee juurikaan paumlaumltoumlksiauml ohjelmoijan puolesta

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 11: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

5

Ominaisuuksiltaan Vue on samankaltainen Reactin kanssa Molemmat hyoumldyntaumlvaumlt virtuaalista DOMia ja sovelluksen eri osat rakentuvat kom-ponenteista Vue tukee myoumls tarvittaessa Reactista tuttua JSX-syntaksia vaikkakin oletuksena kaumlytoumlssauml on yksinkertaisempi HTML-pohjainen sivu-pohja-jaumlrjestelmauml (Vuejs nd b)

34 Palvelinpuolen web-sovelluskehykset

Palvelinpuolen web-sovelluskehykset ovat tavallisesti asiakaspuolen web-sovelluskehyksiauml isompia koska ne tarvitsevat enemmaumln eri ominaisuuk-sia Palvelimella on tavallisesti yhteys tietokantaan joka jo sinaumlllaumlaumln aset-taa haasteita muun muassa tietoturvan suhteen Palvelimen taumlytyy myoumls pystyauml palvelemaan useaa kaumlyttaumljaumlauml samanaikaisesti Palvelinpuolen web-sovelluskehykset sisaumlltaumlvaumlt yleensauml vaumlhintaumlaumln seuraa-vat ominaisuudet sisaumlaumlnrakennettuina - tuki HTTP-pyyntoumljen vastaanottamiseen ja laumlhettaumlmiseen - tuki REST-rajapintojen tekoon - kaumlyttaumljaumltilit - lomakkeet ja niiden validointi - vaumllimuisti ja istunnot - sivupohjamoottori - tuki monelle eri tietokannalle - ORM-jaumlrjestelmauml - MVC-arkkitehtuuri tai vastaava - testaustyoumlkalut - suojaus yleisiauml haavoittuvuuksia vastaan (Niemi 2015)

341 ASPNET Core

ASPNET Core on Microsoftin kehittaumlmauml ja markkinoima web-sovelluske-hys Se on ASPNET-sovelluskehysten uusin ja modernein iteraatio ja se jul-kaistiin vuonna 2016 ASPNET Core on avointa laumlhdekoodia ja toi muka-naan huomattavasti aiempaa paremman tuen eri kaumlyttoumljaumlrjestelmille Mic-rosoftin lisaumlksi myoumls kaumlyttaumljaumlt paumlaumlsevaumlt osallistumaan tuotteen jatkokehi-tykseen ASPNET Core on rakennettu Common Language Runtimen paumlaumllle joten se tukee kaikkia NET kieliauml joista yleisimmaumlt ovat C ja Visual Basic NET (Microsoft 2018)

342 Express

Express on Nodejs-pohjainen mikrosovelluskehys Nodejs-pohjan ansi-osta se toimii asynkronisesti joka tekee siitauml toiminnaltaan varsin erilaisen muihin taumlssauml tyoumlssauml mainittuihin web-sovelluskehyksiin verrattuna Ex-press on projektin arkkitehtuurin kannalta hyvin avoin eikauml tee juurikaan paumlaumltoumlksiauml ohjelmoijan puolesta

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 12: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

6

Vaikka Express on suosituin Nodejs-pohjainen sovelluskehys se on sisaumlaumln-rakennetuilta ominaisuuksiltaan esimerkiksi Laraveliin verrattuna hyvin minimaalinen ja vastaa laumlhinnauml Flask-sovelluskehystauml Expressillauml on raken-nettu esimerkiksi tunnetut Paypalcom ja Flickrcom (Wappalyzer nd)

343 Flask

Flask on Armin Ronacherin kehittaumlmauml Python-ohjelmointikieltauml kaumlyttaumlvauml mikrosovelluskehys Sen kehitys alkoi alun perin aprillipilana mutta sen yksinkertaisuuden ansiosta saaman suosion myoumltauml sitauml alettiin kehittauml-maumlaumln tosissaan Flaskin uusin versio on 10 Flaskista loumlytyy oletuksena muun muassa seuraavat ominaisuudet - tuki Jinja2-sivupohjille - tuki staattisille tiedostoille - URL-reititys - istunnot - yksinkertainen testipalvelin Alla oleva muutamaan riviin mahtuva esimerkki tulostaa rdquoHei Maailmardquo from flask import Flask app = Flask(__name__) approute() def hello() return Hei Maailma

344 Laravel

Laravel on vuonna 2011 julkaistu ilmainen avointa laumlhdekoodia oleva PHP-pohjainen web-sovelluskehys Myoumls Laravel noudattaa MVC-arkkitehtuuria Laravelistauml loumlytyy suora tuki Vue-komponenteille joita sen sivupohjamoottorikin tukee Laravelin kehitys alkoi kun Taylor Otwell halusi luoda paremman vaihto-ehdon PHP-pohjaiselle CodeIgniter-sovelluskehykselle Ensimmaumlinen ver-sio julkaistiin vuonna 2011 ja se sisaumllsi jo kehittyneitauml ominaisuuksia kuten lokalisoinnin ja kaumlyttaumljien hallinnan (OrsquoBrien 2016) Suhteellisen nuoresta iaumlstaumlaumln huolimatta se on yksi suosituimmista PHP-pohjaisista web-sovelluskehyksistauml Erityisen suosittu se on uusien kehittauml-jien keskuudessa (ValueCoders 2018)

345 Ruby on Rails

Ruby on Rails on vuonna 2004 julkaistu Ruby-ohjelmointikieltauml kaumlyttaumlvauml avointa laumlhdekoodia oleva web-sovelluskehys Se kaumlyttaumlauml useiden muiden

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 13: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

7

web-sovelluskehysten tapaan suosittua MVC-arkkitehtuuria Ruby on Rails syntyi osana Basecamp-nimistauml projektinhallintatyoumlkalua josta kyseisen tyoumlkalun kehittaumljauml David Heinemeier Hansson julkaisi sen erillaumlaumln Vuo-desta 2007 laumlhtien Ruby on Rails on tullut Applen Mac OS -kaumlyttoumljaumlrjestel-maumln mukana mikauml auttoi sen nousemista yhdeksi suosituimmaksi web-so-velluskehykseksi (Basu 2013)

4 PYTHON-OHJELMOINTIKIELI

Python on vuonna 1990 julkaistu tulkattava korkeantason ohjelmointikieli Yleisesti Pythonia pidetaumlaumln yhtenauml helpoimmista ja selkeimmistauml ohjel-mointikielistauml helpon luettavuuden takia Web- ja tyoumlpoumlytaumlsovellusten li-saumlksi Python on erittaumlin suosittu tieteellisessauml laskennassa ja sille on kehi-tetty useita laskennassa kaumlytettaumlviauml kirjastoja Python-ohjelmointikielen etuna on myoumls tuki Python Package Indexissauml oleville avoimen laumlhdekoo-din paketeille Huhtikuussa 2018 eri paketteja oli jaossa yli 136 000 Pythonista loumlytyy kaksi eri versiota joista kumpaakin paumlivitetaumlaumln erikseen Pythonin versio 3 julkaistiin joulukuussa 2008 ja se toi mukanaan monia uudistuksia Uudistusten ja muutosten takia versio 3 ei ole taaksepaumlin yh-teensopiva version 2 kanssa Python 2 on vielauml laajasti kaumlytoumlssauml koska lauml-heskaumlaumln kaikista kolmansien osapuolten paketeista ei loumlydy vielauml versiota uudemmalle Python 3 -versiolle Myoumls monet hieman vanhemmat sovel-lukset pysyvaumlt tarkoituksella Python 2 -versiossa koska refaktorointi ei toisi merkittaumlvaumlauml lisaumlarvoa (Google for Education 2017a)

41 Pythonin suosio

Python on erittaumlin suosittu ohjelmointikieli GitHubin julkaiseman suosi-tuimmat ohjelmointikielet -listan mukaan Python oli toisiksi suosituin (Git-hubcom 2017) IEEE Spectrum -lehden tilastojen mukaan Python oli kai-kista suosituin heinaumlkuussa 2017 (IEEE Spectrum 2017) Huhtikuussa 2018 Python oli TIOBE indeksin mukaan neljaumlnneksi suosituin ohjelmointikieli (Kuva 2)

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 14: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

8

Kuva 2 10 suosituinta ohjelmointikieltauml TIOBE indeksin mukaan

42 Tulkattavuus

Python on tulkattava ohjelmointikieli eli sitauml ei tarvitse erikseen kaumlaumlntaumlauml suoritusta varten toisin kuin esimerkiksi C Tulkattavuudesta johtuen oh-jelmien kehittaumlminen on nopeaa mutta suorituskyky ei ole kaumlaumlnnettaumlvien kielten tasolla Kieli on mahdollista kaumlaumlntaumlauml tavukoodiksi jonka suoritta-minen on huomattavasti nopeampaa Yksi tulkattavien kielten huonoista puolista on Pythonin tapa tarkistaa koodi vasta kun ohjelma on suorittamassa kyseessauml olevaa koodiriviauml Taumlstauml johtuen esimerkiksi yksinkertaiset kirjoitusvirheet tulevat ilmi vasta silloin kun ohjelmaa suoritetaan virheen sisaumlltaumlvaumlaumln riviin asti Isoissa so-velluksissa on mahdollista ettauml yksinkertaiset virheet jaumlaumlvaumlt huomaamatta pitkaumlksikin aikaa jollei koodia ole hyvin testattu mieluiten automaatti-sesti Esimerkiksi C-kielen kohdalla virhe huomattaisiin heti kaumlaumlntoumlvai-heessa (Google for Education 2017b)

43 Tyypitys

Python on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli Dynaami-sella tyypityksellauml muuttujien ja funktioiden palautusarvojen tyyppejauml ei tarvitse maumlaumlrittaumlauml vaan tulkki maumlaumlrittaumlauml ne automaattisesti annetun arvon perusteella Tyyppi voi myoumls muuttua kesken ohjelman Dynaamisen tyy-pityksen vastakohta on staattinen tyypitys taumllloumlin muuttujat ovat ohjel-man alusta asti tiettyauml tyyppiauml eikauml tyyppi voi vaihtua ohjelman ajon ai-kana (Python Wiki nd b) Vahva tyypitys tarkoittaa ettauml muuttujilla voi tehdauml vain niitauml asioita joita niiden tyyppi tukee Esimerkiksi merkkijonoa ja kokonaislukua ei yleensauml voi summata koska merkkijonon ja kokonaisluvun yhteenlaskua ei ole maumlaumlritetty Vahvaa ja staattista tyypitystauml pidetaumlaumln usein virheellisesti sy-nonyymeina vaikka ne tarkoittavat eri asiaa

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 15: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

9

(Python Wiki nd b)

44 Syntaksi

Pythonin syntaksi on useimmista muista suosituista kielistauml hieman poik-keava Rivejauml ei lopeteta puolipisteeseen eikauml eri lohkoja ympaumlroumlidauml aal-tosulkeilla Aaltosulkeiden sijaan lohkoihin kuuluvat rivit maumlaumlritetaumlaumln si-sentaumlmaumlllauml Python on myoumls erittaumlin tarkka siitauml miten sisennys on tehty Esimerkiksi vaumllilyoumlntien sekoittaminen tabulaattorilla tehtyihin vaumlleihin lasketaan virheeksi eikauml ohjelmaa voida suorittaa Python-tiedostot kaumlyt-taumlvaumlt py-tiedostopaumlaumltettauml ja niitauml kutsutaan moduuleiksi

441 Funktiot

Funktiot maumlaumlritetaumlaumln def-avainsanalla jonka jaumllkeen seuraa funktion nimi sulkeet ja kaksoispiste Funktion sisaumllle kuuluvat rivit sisennetaumlaumln Koska Python on dynaamisesti tyypittaumlvauml kieli argumenttien tai palautettavan arvon tyyppiauml ei maumlaumlritetauml def sum(number_one number_two)

sum_of_arguments = number_one + number_two return sum_of_arguments

Funktioita kutsutaan samantyylisesti muiden yleisten ohjelmointikielten kanssa example_sum = sum(3 4)

print(example_sum)

442 Silmukat

Silmukkatyyppejauml Pythonista loumlytyy kaksi erilaista useista muistakin kie-listauml tutut for ja while For-silmukkaa kaumlytetaumlaumln tavallisesti kun jokin ope-raatio halutaan suorittaa tietyn monta kertaa While-silmukkaa kaumlytetaumlaumln kun operaatio halutaan suorittaa niin kauan kunnes silmukalle annettu ehto muuttuu Funktioiden tapaan silmukkaan kuuluva lohko maumlaumlritetaumlaumln sisentaumlmaumlllauml Silmukoita voi myoumls laittaa sisaumlkkaumlin (Python Wiki nd a) Seuraava esimerkkinauml toimiva for-silmukka tulostaa Hello-merkkijonon jo-kaisella silmukan ajokerralla Range on Python-funktio joka palauttaa lis-tan lukuja Taumlssauml esimerkissauml se palauttaa luvut 0-19 eli silmukka ajetaan 20 kertaa for x in range(0 20) print(Hello)

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 16: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

10

For-silmukan vaatima sarja voi olla muutakin kuin pelkkiauml lukuja Esimer-kiksi merkkijono kaumly sellaisenaan Alla olevassa esimerkissauml silmukka kaumly laumlpi Hello-merkkijonon kirjain kerrallaan example_string = Hello for x in example_string print(x)

Seuraava while-silmukka ajetaan niin kauan kunnes example_bool-muut-tujan arvo muuttuu epaumltodeksi Taumlssauml esimerkissauml silmukkaa ajettaisiin lo-puttomiin example_bool = True while example_bool print(It is still true)

443 Luokat

Luokan maumlaumlritys aloitetaan class-avainsanalla jonka jaumllkeen maumlaumlritetaumlaumln luokan nimi ja sen peraumlaumln kaksoispiste Luokan sisaumlllauml oleva lohko maumlaumlri-tetaumlaumln funktioiden tapaan sisentaumlmaumlllauml Lohko voi sisaumlltaumlauml muun muassa muuttujia muiden funktioiden kutsuja ja luokkaan kuuluvien funktioiden maumlaumlrityksiauml Tavallisesti luokasta loumlytyy vaumlhintaumlaumln konstruktori joka on __init__-niminen funktio class ExampleBaseClass

def __init__(self example_value1 example_value2) selfexample_value1 = example_value1

selfexample_value2 = example_value2

Luokat voivat myoumls periytyauml muista luokista Luokka josta peritaumlaumln maumlauml-ritetaumlaumln luokan jaumllkeen tulevien sulkeiden sisaumllle class ExampleDerivedClass(ExampleBaseClass)

def example_function() print(This is a derived class)

Uusi ExampleDerivedClass-objekti luodaan ja example_function-funktiota kutsutaan seuraavalla tavalla x = ExampleClass(y z) xexample_function()

45 PEP 8

PEP 8 on Python-ohjelmointikielen virallinen tyyliopas joka sisaumlltaumlauml ohjeita muun muassa vaumllilyoumlntien maumlaumlraumlaumln ja funktioiden nimeaumlmiseen Tyyliop-paan paumlaumlasiallinen tarkoitus on antaa ohjeet yhtenaumlisen ja selkeaumln koodin kirjoittamiseen Pythonin mukana tuleva standardikirjasto noudattaa PEP 8 -tyyliopasta (Pythonorg 2013)

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 17: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

11

PEP 8 -tyylioppaan noudattamisen taumlrkeys korostuu silloin kun projektissa tyoumlskentelee useita henkiloumlitauml Naumlin koodi saumlilyy yhtenaumlisen naumlkoumlisenauml Esi-merkiksi avoimen laumlhdekoodin projekteissa saattaa mukana olla satoja eri henkiloumlitauml Taumlrkeintauml on kuitenkin kaumlyttaumlauml samaa tyyliauml kuin projektissa on alun perinkin kaumlytetty vaikka se eroaisi joiltakin osin PEP 8 -tyylioppaan ohjeista (Pythonorg 2013)

5 DJANGO

Django on ilmainen vuonna 2005 julkaistu avointa laumlhdekoodia oleva web-sovelluskehys joka kaumlyttaumlauml Python-ohjelmointikieltauml Arkkitehtuurina Django kaumlyttaumlauml MVC-arkkitehtuurin kanssa erittaumlin samankaltaista MVT-arkkitehtuuria Djangoa kaumlyttaumlen on kehitetty erittaumlin suosittuja sivustoja kuten Pinterest Instagram ja BitBucket Django sisaumlltaumlauml monia web-sovelluksissa usein tarvittavia ominaisuuksia Sovelluskehyksestauml loumlytyy muun muassa erittaumlin kattava kaumlyttaumljaumltilijaumlrjes-telmauml joka tukee eri kaumlyttaumljaumltasoja ja ryhmiauml Kaumlyttaumljaumltilijaumlrjestelmaumlauml voi myoumls laajentaa kolmansien osapuolin paketeilla joilla pystyy helposti li-saumlaumlmaumlaumln muun muassa tuen OAuth-kirjautumiselle Djangon oma ORM-jaumlrjestelmauml tekee tietokantojen kaumlytoumlstauml kehittaumljaumllle yksinkertaista ja te-hokasta Sivupohjamoottorina toimii Djangon oma Jinja2-sivupohjamoot-toria muistuttava moottori Django on hyvin vahvasti muokattavissa Halutessaan ison osan sisaumlaumlnra-kennetuista ominaisuuksista voi vaihtaa kolmansien osapuolien kehittauml-miin ominaisuuksiin Esimerkiksi Djangon oman ORM-jaumlrjestelmaumln tilalla voi kaumlyttaumlauml SQLAlchemyauml ja sivupohjamoottorin voi vaihtaa suosittuun Jinja2-moottoriin (Django Girls nd)

51 Historia

Ensimmaumlinen versio Djangosta syntyi vuonna 2003 kun amerikkalaisen Lawrence Journal-World -lehden ohjelmoijat alkoivat kaumlyttaumlmaumlaumln Python-ohjelmointikieltauml web-sovellusten rakentamiseen Yleisoumllle taumlmauml versio julkaistiin BSD-lisenssin alla vuonna 2005 Vuodesta 2008 laumlhtien Djangon kehityksestauml on vastannut voittoa tavoittelematon Django Software Foun-dation -saumlaumltiouml (Django Documentation nd c) Djangon uusi 20 versio julkaistiin joulukuussa 2017 Se sisaumllsi merkittaumlviauml uudistuksia ja ominaisuuksia Muun muassa URL-reitityksen syntaksi muu-tettiin yksinkertaisemmaksi ja tuki Python 2 -versiolle lopetettiin Kirjoitus-hetkellauml Djangon uusin versio on 204

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 18: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

12

52 MVT-arkkitehtuuri

Django noudattaa MVT-arkkitehtuuria joka on Djangon kehittaumljien oma naumlkemys erittaumlin yleisesti kaumlytoumlssauml olevasta MVC-arkkitehtuurista Yksi merkittaumlvissauml eroista on varsinaisen kaumlsittelijaumln puuttuminen Kaumlsittelijaumln tilalla Djangossa on URL-maumlaumlritykset ja naumlkymaumlt (Kuva 3) URL-maumlaumlritysten perusteella Django tietaumlauml mihin naumlkymaumlaumln kaumlyttaumljauml viedaumlaumln jonka jaumllkeen naumlkymauml hakee halutut tiedot malleista ja renderoumli kaumlyttaumljaumllle HTML-sivun kaumlyttaumlen sivupohjia (TheDjangoBook nd a)

Kuva 3 MVC- ja MVT-arkkitehtuurien yhtenevaumlisyydet

MVT- ja MVC-arkkitehtuurien etuna on sovelluksen eri osien erottaminen toisistaan Taumlmaumln ansiosta naumlkymaumlt mallit ja sivupohjat voidaan tehdauml toi-sistaan erillaumlaumln eikauml esimerkiksi mallin tarvitse tietaumlauml mitauml naumlkymaumlssauml tai sivupohjassa tapahtuu

53 Mallit

Mallit ovat Python-luokkia jotka kuvaavat tietokantaan tallennettavaa da-taa Mallien avulla tietokannan dataa voi kaumlsitellauml oliomuotoisena eli ta-vallisten SQL-kyselyiden sijaan dataa voi muokata ja hallita samanlailla kuin Python-objekteja Oletuksena Django kaumlyttaumlauml sen omaa ORM-jaumlrjestelmaumlauml mutta sen voi korvata myoumls jollakin muulla kolmannen osa-puolen ORM-jaumlrjestelmaumlllauml (TheDjangoBook nd b) Mallien maumlaumlrittaumlmisellauml Python-koodina pelkkien tietokantojen sijaan on useita etuja Datan muuttaminen ja kaumlyttaumlminen oliomuodossa on yksin-kertaista koska Django tietaumlauml jo valmiiksi mallien kentaumlt Lisaumlksi sama malli toimii riippumatta kaumlytetystauml tietokantatyypistauml Myoumls versionhallintajaumlr-jestelmaumlt ovat paremmin yhteensopivia Python-tiedostojen kanssa (TheDjangoBook nd b)

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 19: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

13

531 Mallien maumlaumlritys

Mallit maumlaumlritetaumlaumln sovelluskohtaiseen modelspy-tiedostoon Mallien pe-rusluokkana tulee kaumlyttaumlauml djangodbmodels-kirjastosta loumlytyvaumlauml Model-luokkaa Luokalle maumlaumlritetyt muuttujat kuvaavat tietokantaan tulevia kenttiauml Seuraava esimerkki maumlaumlrittaumlauml mallin Person joka sisaumlltaumlauml kentaumlt etunimelle ja sukunimelle class Person(modelsModel) first_name = modelsCharField(max_length=30) last_name = modelsCharField(max_length=30)

Maumlaumlritetty malli luo tietokantaan uuden taulun alla olevan esimerkin mu-kaisesti Taulun nimi koostuu sovelluksen ja mallin nimestauml ID-kenttauml on Djangon automaattisesti lisaumlaumlmauml primaumlaumlriavaimena toimiva kenttauml CREATE TABLE myapp_person ( id serial NOT NULL PRIMARY KEY first_name varchar(30) NOT NULL last_name varchar(30) NOT NULL )

532 Tietokantakyselyiden tekeminen

Tavallisesti SQL-pohjaisten tietokantojen kyselyt tehtaumlisiin SQL-kieltauml kaumlyt-taumlen Djangon ORM-jaumlrjestelmaumln avulla kyselyiden tekeminen on kehittauml-jaumln naumlkoumlkulmasta huomattavasti yksinkertaisempaa Esimerkiksi uusi Per-son-tietue tehdaumlaumln samanlailla kuin uusi instanssi tavallisesta Python-luo-kasta from models import Person new_person = Person( first_name=Esko last_name=Example ) new_personsave()

Tietue tallentuu tietokantaan vasta kun save-funktio kutsutaan Pinnan alla Django kaumlyttaumlauml tavallista SQL-kielen INSERT-lauseketta tietueen teke-miseen (Django Documentation nd a)

54 Naumlkymaumlt

Naumlkymaumlt ovat Python-funktioita jotka vastaanottavat pyynnoumln ja palaut-tavat vastauksen Palautettava vastaus voi olla esimerkiksi HTML-koodia tai 404-virhe Naumlkymaumlssauml oleva koodi paumlaumlttaumlauml mitauml palautettava vastaus sisaumlltaumlauml Tavallisesti naumlkymaumlt maumlaumlritetaumlaumln sovelluskohtaiseen viewspy-tie-dostoon Naumlkymaumlt voivat olla joko funktio- tai luokkapohjaisia

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 20: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

14

541 Funktiopohjaiset naumlkymaumlt

Funktiopohjaiset naumlkymaumlt ovat rakenteeltaan vapaita ja yksinkertaisia Ne maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-funktiot Pyynnoumln HTTP-metodia vastaava lohko valitaan ehtolauseketta kaumlyttaumlen Funktiopohjais-ten naumlkymien etu on niiden yksinkertaisuus ja selkeys Kaikki naumlkymaumln kaumlyttaumlmauml koodi on selkeaumlsti naumlkyvillauml eikauml piilotettuna perusluokassa (Freitas 2017) def example_view(request) if requestmethod == POST Code block for POST request else Code block for other requests

542 Luokkapohjaiset naumlkymaumlt

Luokkapohjaiset naumlkymaumlt maumlaumlritetaumlaumln samanlailla kuin tavalliset Python-luokat Funktiopohjaisista naumlkymistauml poiketen jokaista kaumlytettaumlvaumlauml HTTP-metodia varten maumlaumlritetaumlaumln metodin nimeauml kaumlyttaumlvauml funktio Luokkapohjaisten naumlkymien merkittaumlvin etu on mahdollisuus periyttaumlauml niitauml muista naumlkymistauml Djangon ydinkirjasto sisaumlltaumlauml monia niin sanottuja generic-naumlkymiauml jotka sisaumlltaumlvaumlt usein tarvittavia ominaisuuksia Esimer-kiksi FormView-naumlkymaumlauml laajentamalla voi nopeasti tehdauml naumlkymaumln loma-ketta varten Yksinkertaisin generic-naumlkymauml on View joka hoitaa muun muassa kaumlytettaumlvaumlauml HTTP-metodia vastaavan funktion kutsumisen auto-maattisesti (Freitas 2017) class ExampleView(View) def get(self request) Code block for GET request def post(self request) Code block for POST request

55 URL-maumlaumlritys

URL-maumlaumlritysten avulla kaumlyttaumljaumln laumlhettaumlmauml pyyntouml ohjataan oikeaan nauml-kymaumlaumln Djangossa URL-maumlaumlritys tapahtuu maumlaumlrittaumlmaumlllauml eri reiteille ta-kaisinkutsufunktiot joita kutsutaan kun pyyntouml tulee kyseiseen osoittee-seen Perinteisesti maumlaumlritykset tallennetaan urlspy-tiedostossa olevaan urlpatterns-listaan Path-funktio vastaanottaa ensimmaumlisenauml parametrina lausekkeen osoit-teelle ja toisena naumlkymaumln johon kaumlyttaumljauml halutaan ohjata Alla olevassa esimerkissauml path-funktio ohjaa pyynnoumln special_case_2003-naumlkymaumlaumln kun osoite on muotoa domainarticles2003

urlpatterns = [ path(articles2003 viewsspecial_case_2003)

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 21: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

15

]

Osoitteesta voi myoumls ottaa arvoja talteen ympaumlroumlimaumlllauml haluttu kohta kulmasulkeilla Samalla voi myoumls maumlaumlrittaumlauml arvon tyypin Mikauml tahansa merkkijono kelpaa jos tyyppiauml ei erikseen maumlaumlritetauml Alla oleva esimerkki laumlhettaumlauml year-kohdalla olevan luvun year-nimisenauml avainsana-argument-tina naumlkymaumllle path(articlesltintyeargt viewsyear_archive)

Naumlkymaumlssauml osoitteesta talteen otettua osaa pystyy kaumlyttaumlmaumlaumln seuraa-valla tavalla wanted_year = kwargs[year]

Path-funktion sijasta voi kaumlyttaumlauml saumlaumlnnoumlllisiauml lausekkeita tukevaa re_path-funktiota Syntaksi on muuten samanlainen mutta ensimmaumlisenauml para-metrina annetaan saumlaumlnnoumlllinen lauseke Taumlmauml soveltuu path-funktiota paremmin monimutkaisiin URL-maumlaumlrityksiin re_path(r^articles(Pltyeargt[0-9]4)$ viewsyear_archive)

56 Sivupohjat

Sivupohjat ovat tekstitiedostoja jotka sisaumlltaumlvaumlt esimerkiksi HTML-elementtejauml Sivupohjia ei ole ainoastaan rajattu HTML-muotoon vaan niitauml voi kaumlyttaumlauml missauml tahansa tekstipohjaisessa tiedostomuodossa (HTML XML CSV jne) Djangossa ei ole pakko kaumlyttaumlauml Djangon omaa si-vupohjamoottoria vaan sen voi korvata esimerkiksi Jinja2-moottorilla (Django Documentation nd b) Sivupohjien merkittaumlvin hyoumlty staattisten HTML-tiedostojen sijaan on mahdollisuus dynaamiselle datalle joka voi esimerkiksi riippua sivustolle tulleesta kaumlyttaumljaumlstauml Sivupohjat voivat myoumls laajentaa toisiaan Tavallisesti sivuston yleinen ulkoasu tehdaumlaumln basehtml-sivupohjaan jota myoumlhem-min laajennetaan Naumlin sivuston tekijauml saumlaumlstyy jatkuvalta HTML-elementtien uudelleenkirjoitukselta ja voi keskittyauml vaan vaihtuvaan sisaumll-toumloumln (Django Documentation nd b)

561 Sivupohjien syntaksi

Sivupohja voi tarvittaessa sisaumlltaumlauml muuttujia jotka korvataan arvoilla kun sivupohjaa kaumlyttaumlvauml sivu ladataan Muuttujat ympaumlroumlidaumlaumln kaksinkertai-silla aaltosulkeilla userfirst_name

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 22: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

16

Muuttujien naumlyttaumlmaumlauml arvoa voi myoumls muokata kaumlyttaumlmaumlllauml suodattimia Django sisaumlltaumlauml monia valmiita suodattimia mutta niitauml voi myoumls tehdauml itse Alla oleva esimerkki naumlyttaumlauml name-muuttujan arvon pienaakkosina name|lower

Muuttujien lisaumlksi tarjolla on tageja joiden avulla voi esimerkiksi tehdauml for-silmukan sivupohjan sisaumlltoumloumln Tagit ympaumlroumlidaumlaumln aaltosulkujen ja pro-senttimerkkien yhdistelmaumlllauml extends basehtml

Usein sivupohjat sisaumlltaumlvaumlt yhdistelmaumln muuttujia ja tageja Alla oleva esi-merkki tulostaa jokaisen users-listassa olevan objektin etunimen h1-tason otsikkona pienaakkosina for user in users lth1gt userfirst_name|lower lth1gt endfor

562 Periytyminen

Sivupohjat pystyvaumlt laajentamaan toisiaan jolloin vaumlltytaumlaumln HTML-elementtien uudelleenkirjoittamiselta Sivupohjaan on mahdollista tehdauml lohkoja joiden sisaumllloumln voi myoumlhemmin korvata toisessa sivupohjassa ole-valla samannimisellauml lohkolla Tavallisesti basehtml-sivupohja pitaumlauml sisaumll-laumlaumln sivuston rakenteen ja ainoastaan sisaumlltoumlauml vaihdetaan kaumlyttaumlen loh-koja Extends-tagilla maumlaumlritetaumlaumln sivupohja josta halutaan periytyauml Alla olevassa esimerkissauml luodaan content-lohko lt-- basehtml --gt ltdoctype htmlgt lthtmlgt ltheadgt lttitlegtBlock examplelttitlegt ltheadgt ltbodygt block content lth1gtThis is the original contentlth1gt endblock content ltbodygt lthtmlgt

Lohkon sisaumllloumln voi kokonaan vaihtaa luomalla uuden sivupohja-tiedoston joka periytyy sivupohjasta jossa korvattava lohko on Alla oleva esimerkki korvaa content-lohkon sisaumllloumln eri otsikolla mutta muuten sivupohja pysyy taumlysin samana lt-- different_contenthtml --gt extends basehtml block content

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 23: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

17

lth1gtThis is the new contentlth1gt endblock content

563 Include

Include-tagilla voidaan sisaumlllyttaumlauml toinen sivupohja nykyiseen sivupohjaan Taumltauml kaumlytetaumlaumln usein esimerkiksi navigaation tai alatunnisteen lisaumlaumlmi-seen Hyoumltynauml taumlstauml on ettauml navigaatiota tai alatunnistetta voi muokata erikseen ilman ettauml kaikki on samassa massiivisessa sivupohja-tiedostossa include main_navigationhtml

57 Middleware

Middleware-luokat toimivat palvelimen saaman pyynnoumln ja palvelimen lauml-hettaumlmaumln vastauksen vaumllissauml samalla muuttaen request- tai response-ob-jektia (Kuva 4) Middlewarejen hyoumlty on niiden mahdollistama request- tai response-objektin muokkaaminen ennen kuin haluttu naumlkymauml suorite-taan

Kuva 4 Middleware-luokkien toimintatapa

Middlewaret pystyvaumlt kiinnittymaumlaumln niin sanottuihin hook-funktioihin joita kutsutaan automaattisesti kun pyyntouml tai vastaus kulkee kaumlyttaumljaumln ja naumlkymaumln vaumllissauml Hook-funktioita on viisi erilaista

minus process_request

minus process_view

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 24: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

18

minus process_response

minus process_template_response

minus process_exception (Akshar 2015) Middlewaret aktivoidaan lisaumlaumlmaumlllauml ne settingspy-tiedostossa olevaan MIDDLEWARE-listaan Middlewarejen jaumlrjestyksellauml on vaumlliauml koska ne voi-vat olla riippuvaisia muista middlewareista Esimerkiksi oletuksena aktiivi-sena oleva AuthenticationMiddleware kaumlyttaumlauml SessionMiddlewaren hallit-semaa istuntoa MIDDLEWARE = [ djangomiddlewaresecuritySecurityMiddleware djangocontribsessionsmiddlewareSessionMiddleware djangomiddlewarecommonCommonMiddleware djangomiddlewarecsrfCsrfViewMiddleware djangocontribauthmiddlewareAuthenticationMiddleware djangocontribmessagesmiddlewareMessageMiddleware djangomiddlewareclickjackingXFrameOptionsMiddleware ]

Seuraava middleware-esimerkki tallentaa kaumlyttaumljaumln Profile-mallissa maumlaumlri-tetyn aikavyoumlhykkeen istuntodataan jolloin se on helposti kaumlytettaumlvissauml kaikissa naumlkymissauml class TimezoneMiddleware(object) def process_request(self request) requestsession[timezone] = requestuserprofiletimezone

58 Admin-sivusto

Djangosta loumlytyy valmiina automaattisesti luotu yllaumlpitaumljaumllle tarkoitettu sivusto jonka avulla voi tarkastella ja muokata tietokannassa olevaa da-taa Sivustoa ei ole tarkoitettu sovelluksen tavallisille kaumlyttaumljille vaikka se siihen soveltuisikin muutaman muokkauksen jaumllkeen Sivustolla naumlkyy oletuksena ainoastaan kaumlyttaumljaumlryhmaumlt ja kaumlyttaumljaumlt koska ne ovat val-miiksi rekisteroumlity admin-sivustoa varten (Kuva 5)

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 25: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

19

Kuva 5 Admin-sivuston oletusnaumlkymauml

Mallit taumlytyy erikseen rekisteroumlidauml admin-paneelia varten ennen kuin ne naumlkyvaumlt siellauml Malli rekisteroumlidaumlaumln djangocontribadminsite-kirjastossa olevalla register-funktiolla jonka parametriksi annetaan haluttu malli Ta-vallisesti mallit rekisteroumlidaumlaumln sovelluskohtaisessa adminpy-tiedostossa from djangocontrib import admin models import ExampleModel

adminsiteregister(ExampleModel)

Rekisteroumlinnin jaumllkeen myoumls ExampleModel-mallin objekteja voi tarkas-tella ja muokata admin-sivustolla (Kuva 6)

Kuva 6 ExampleModel-malli lisaumlttynauml admin-sivustolle

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 26: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

20

6 PROJEKTISSA KAumlYTETTAumlVAumlT TYOumlVAumlLINEET JA OHJELMAT

61 Cmder

Cmder on Samuel Vaskon kehittaumlmauml ConEmu-pohjainen konsoliemulaat-tori Windowsille Cmderin etuina tavalliseen komentoriviin on muun mu-assa valmis tuki Linux- ja Git Bash -komennoille Cmder tukee myoumls vaumllileh-tiauml mikauml helpottaa esimerkiksi Djangon testipalvelimen ja versionhallinnan samanaikaista kaumlyttoumlauml Tyoumlkalun ulkoasu on monipuolisesti muokatta-vissa Oletuksena se kaumlyttaumlauml tekstieditoreistakin tuttua Monokai-teemaa (Kuva 7)

Kuva 7 Cmder-tyoumlkalun oletuksena kaumlyttaumlmauml Monokai-teema

62 Git ja GitHub

Git on Linus Torvaldsin kehittaumlmauml hajautettu versionhallintajaumlrjestelmauml jonka ensimmaumlinen julkinen versio julkaistiin vuonna 2005 Se on ilmainen ja avointa laumlhdekoodia ja se on taumlllauml hetkellauml maailman eniten kaumlytetyin versionhallintajaumlrjestelmauml Hajautetulla versionhallintajaumlrjestelmaumlllauml tar-koitetaan sitauml ettauml palvelimen lisaumlksi myoumls jokaisella asiakaskoneella on kopio koko tietolaumlhteestauml Hyoumltynauml taumlstauml on se ettauml vaikka palvelin hajo-aisi tiedot pysyvaumlt silti asiakaskoneilla (Atlassian nd) GitHub on palvelu joka tarjoaa tilaa Git-versionhallintajaumlrjestelmaumlauml kaumlyt-taumlville ohjelmistoprojekteille Tilan lisaumlksi GitHub tarjoaa erilaisia Git-pro-jektien hallintaan liittyviauml ominaisuuksia kuten tehtaumlvien ja bugien seuran-nan GitHubin peruskaumlyttouml on ilmaista mutta esimerkiksi yksityiset repo-sitoryt ovat maksullisia Repository-termillauml tarkoitetaan projektin rdquosaumlily-tyspaikkaardquo (Brown 2016)

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 27: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

21

63 Virtualenv ja virtualenvwrapper

Virtualenv on tyoumlkalu jonka avulla voi luoda muusta kaumlyttoumljaumlrjestelmaumlstauml eristetyn ympaumlristoumln Python-paketteja varten Naumlin samalla tietokoneella voi olla samaan aikaan monta eri versiota esimerkiksi Djangosta Taumlmauml on tarpeellista jos tyoumlstaumlauml samalla koneella useaa eri projektia jotka vaativat eri versioita Python-paketeista Virtualenvwrapper on paketti joka sisaumlltaumlauml laajennuksia ja uusia ominai-suuksia virtualenv-tyoumlkaluun Sen paumlaumlasiallinen tarkoitus on helpottaa ja nopeuttaa virtualenv-tyoumlkalun kaumlyttoumlauml muun muassa yksinkertaistamalla komentoja

64 Visual Studio Code

Visual Studio Code on Microsoftin tekemauml avoimen laumlhdekoodin tekstiedi-tori joka luotiin kevyemmaumlksi vaihtoehdoksi Microsoftin isommalle Visual Studio -ohjelmistokehitysympaumlristoumllle Visual Studio Code kaumlyttaumlauml Electron-sovelluskehystauml jonka avulla tehdaumlaumln Nodejs-pohjaisia sovelluk-sia tyoumlpoumlytaumlkaumlyttoumloumln Visual Studio Code tukee kaikkia kolmea isoa kaumlyt-toumljaumlrjestelmaumlauml eli Windowsia macOSaumlauml ja Linuxia (Visual Studio Code Documentation nd)

7 PROJEKTIN TOTEUTUS

Opinnaumlytetyoumln kaumlytaumlnnoumln osuutena loin projektin jonka tavoitteena oli toteuttaa toimiva web-sovellus Djangolla Tarkoituksena oli luoda linkin ly-hennys -palvelu johon kuuluisi kaumlyttaumljaumltilit ja omien linkkien hallintaan tehty hallintapaneeli Inspiraatiota projektin ominaisuuksiin otin Bitlycom-palvelusta joka on Twitterinkin kaumlyttaumlmauml linkkien lyhentaumlmi-seen keskittynyt palvelu Varsinainen linkin lyhennys -algoritmi oli hyvin yksinkertainen Generoin satunnaisesti 8-merkkisen merkkijonon joka sai sisaumlltaumlauml isoja ja pieniauml kir-jaimia sekauml numeroita Jos generoitu merkkijono oli jo kaumlytoumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Mahdollisia eri yhdistel-miauml on olemassa niin monta ettauml saman yhdistelmaumln generointi pitaumlisi olla erittaumlin harvinaista joten en naumlhnyt tarpeelliseksi paremman algoritmin luontia Projektin visuaalisen ilmeen tekemiseen kaumlytin Bootstrap-kirjastoa joka si-saumlltaumlauml ison maumlaumlraumln valmiiksi tehtyjauml CSS-tyylimaumlaumlrittelyjauml Naumlin minun ei tarvinnut kaumlyttaumlauml juurikaan aikaa sivuston visuaaliseen tyyliin ja pystyin keskittymaumlaumln enemmaumln Djangoon liittyviin asioihin

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 28: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

22

71 Gitin kaumlyttoumloumlnotto

Aloitin projektin tekemaumlllauml GitHubiin uuden repositoryn projektiani varten (Kuva 8) Opiskelijalisenssin ansiosta pystyin tekemaumlaumln ilmaiseksi myoumls yk-sityisiauml repositoryjauml Lisaumlsin gitignore-tiedostoksi GitHubista valmiiksi loumly-tyvaumln Python-projekteille tarkoitetun gitignore-tiedoston Tiedosto maumlauml-ritti mitauml tiedostotyyppejauml tai kansioita ei lisaumltty repositoryyn Tavallisesti naumlmauml olivat automaattisesti generoituvia tiedostoja tai paketteja jotka asennettiin erikseen esimerkiksi Pip-tyoumlkalulla

Kuva 8 Uuden GitHub-repositoryn luonti

Repository piti seuraavaksi kloonata tietokoneelleni komennolla git clone (Kuva 9) Komento kopioi palvelimella olevat tiedostot ja maumlaumlritti joitakin palvelimeen liittyviauml asetuksia valmiiksi

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 29: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

23

Kuva 9 Repositoryn kloonaus komennolla git clone

72 Virtualenvwrapperin kaumlyttoumloumlnotto

Asensin Virtualenvwrapper-tyoumlkalun kaumlyttaumlen Pythonin mukana tullutta Pip-tyoumlkalua joka oli tarkoitettu Python Package Indexissauml olevien paket-tien asentamiseen (Kuva 10)

Kuva 10 Virtualenv-asennus kaumlyttaumlen Pip-tyoumlkalua

Seuraavaksi loin projektiani varten uuden virtualenv-ympaumlristoumln Annoin ympaumlristoumllle nimeksi urlshortener jota kaumlytin repositoryn ja myoumlhemmin myoumls Django-projektin nimenauml mkvirtualenv urlshortener

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 30: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

24

Luotu virtualenv-ympaumlristouml aktivoitui automaattisesti Kun ympaumlristouml oli aktiivisena kaikki Python-paketit jotka sovellusta varten tulin asenta-maan asentuivat koko kaumlyttoumljaumlrjestelmaumln sijasta vain aktiivisena olevaan virtualenv-ympaumlristoumloumln

73 Djangon asennus ja uuden projektin luominen

Asensin Djangon uusimman version joka oli kirjoitushetkellauml 204 Pip-tyoumlkalu asensi automaattisesti paketin uusimman version jollei versiota erikseen maumlaumlritetty pip install django

Seuraavaksi loin uuden Django-projektin kaumlyttaumlen django-admin-komen-torivityoumlkalua joka sisaumlltyi Djangon asennukseen Piste komennon lopussa maumlaumlritti ettauml projektin tiedostot luotiin nykyiseen kansioon uuden kansion sijasta django-admin startproject urlshortener

Django loi automaattisesti yksinkertaisen kansiorakenteen projektille urlshortener managepy urlshortener __init__py settingspy urlspy wsgipy

Projektin juuressa ollut managepy oli komentorivityoumlkalu jolla suoritettiin erilaisia komentoja Sisempi urlshortener-kansio oli Python-paketti ja sen sisaumlllauml ollut __init__py-tiedosto oli tyhjauml Python-tiedosto jonka perus-teella Python tiesi ettauml taumltauml kansiota tuli kohdella Python-pakettina Set-tingspy oli tiedosto joka maumlaumlritti Django-projektiin liittyvaumlt asetukset ku-ten sivupohjat sisaumlltaumlvaumlt kansiot tai staattisten tiedostojen sijainnin Urlspy maumlaumlritti koko projektin URL-osoitteet Sovelluksille kuului yleensauml vielauml erilliset omat urlspy-tiedostot jotka tuotiin taumlhaumln include-funktiota apuna kaumlyttaumlen Asennuksen onnistumisen pystyi nopeasti testaamaan kaumlynnistaumlmaumlllauml Djangon mukana tulleen kehityskaumlyttoumloumln tarkoitetun web-palvelimen Si-vun naumlkymisestauml osoitteessa localhost8000 tiesi ettauml asennus oli onnistu-nut (Kuva 11)

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 31: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

25

Kuva 11 Djangon oletussivu josta naumlki ettauml asennus onnistui

74 Templates- ja static-kansioiden luonti

Ennen varsinaisen ohjelmoinnin aloitusta loin valmiiksi kansiot projektin sivupohjia ja staattisia tiedostoja varten koska niitauml joka tapauksessa tar-vitaan myoumlhemmin Staattisilla tiedostoilla tarkoitetaan esimerkiksi CSS- tai JavaScript-tiedostoja Templates-kansio tuli sisaumlltaumlmaumlaumln sovelluksen kaumlyttaumlmiauml HTML-sivupohjia Loin kummatkin kansiot projektin juureen eli samaan kansioon kuin missauml managepy-tyoumlkalu on Tein static-kansioon myoumls kolme alikansiota css images ja js Kansioiden luonnin jaumllkeen ne taumlytyi vielauml maumlaumlrittaumlauml settingspy-tiedos-tossa Oletuksena Django etsii sivupohjia vain projektiin luotujen sovellus-ten sisaumlltauml Mielestaumlni oli kuitenkin selkeaumlmpaumlauml pitaumlauml esimerkiksi sivuston basehtml-sivupohja suoraan projektin juuressa TEMPLATES = [ BACKEND djangotemplatebackendsdjangoDjangoTemplates DIRS [ ospathjoin(BASE_DIR templates) add root folder ] APP_DIRS True OPTIONS context_processors [ djangotemplatecontext_processorsdebug djangotemplatecontext_processorsrequest djangocontribauthcontext_processorsauth djangocontribmessagescontext_processorsmessages ]

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 32: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

26

] STATICFILES_DIRS = [ ospathjoin(BASE_DIR static) ]

Ospathjoin on Python-funktio joka yhdistaumlauml tiedostopolkuja Funktion kaumlyttaumlminen polkujen suoraan kirjoittamisen sijaan oli suositeltavaa koska muuten polku voisi olla eri riippuen kaumlyttoumljaumlrjestelmaumlstauml

75 Basehtml-sivupohja

Aloitin projektin luomalla koko sivuston pohjalla toimivan basehtml-sivu-pohjan Tein sivupohjan projektin juuressa olevaan templates-kansioon Si-vupohja sisaumllsi muun muassa linkit CSS- ja JavaScript-tiedostoihin Lisaumlsin sivuston navigaation ja alatunnisteen kaumlyttaumlen include-tagia jotta niitauml pystyisi muokkaamaan kaumltevaumlsti erikseen Body-elementin sisaumllle loin loh-kon nimeltauml content joka tulisi sisaumlltaumlmaumlaumln taumltauml laajentavien sivupohjien sisaumllloumln lt-- basehtml --gt load static ltdoctype htmlgt lthtml lang=engt ltheadgt lt-- meta tags title bootstrap and custom css --gt ltheadgt ltbody class=text-centergt ltdiv class=d-flex min-height-100 flex-columngt include main_navigationhtml ltmain role=maingt block content endblock content ltmaingt include main_footerhtml ltdivgt lt-- bootstrap javascript --gt ltbodygt lthtmlgt

76 Accounts-sovellus

Tein projektiin uuden sovelluksen nimellauml accounts Taumlmaumln sovelluksen oli tarkoitus sisaumlltaumlauml kaikki kaumlyttaumljaumltileihin ja niiden hallintaan tarvittavat mal-lit naumlkymaumlt lomakkeet ja URL-reititykset Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida Aktivointi tapahtui lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 33: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

27

djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts ]

77 Auth-sovelluksen kaumlyttoumloumlnotto

Hyoumldynsin kaumlyttaumljaumltilien teossa Djangon sisaumlaumlnrakennettua auth-sovel-lusta joka sisaumllsi User-mallin ja muutamia valmiita naumlkymiauml kuten esimer-kiksi sisaumlaumlnkirjautumiseen tarvittavan naumlkymaumln Otin kaumlyttaumljaumltilit kaumlyttoumloumln lisaumlaumlmaumlllauml auth-sovelluksen valmiit URL-maumlaumlritykset projektin urlspy-tie-dostoon include-funktiolla Maumlaumlritin ne alkamaan accounts-merkkijo-nolla jolloin esimerkiksi kirjautumiseen kaumlytetty osoite olisi domainac-countslogin urlspy from djangourls import include path urlpatterns = [ path(admin adminsiteurls) path(accounts include(djangocontribauthurls))

] Kaumlyttaumljaumltilit olivat toiminnassa mutta sivupohjia ei ollut vielauml olemassa ja kaumlyttaumljaumltilin tekoon tarvittava naumlkymauml piti tehdauml itse Tein sivupohjat ac-counts-sovelluksen juuressa olevaan templates-kansioon Sivupohjille taumly-tyi tehdauml registration-niminen alakansio jotta Django loumlytaumlisi ne

78 Sivupohjat auth-sovelluksen naumlkymille

Seuraavaksi tein sivupohjat jokaiselle auth-sovelluksesta loumlytyvaumllle val-miille naumlkymaumllle Naumlmauml sivupohjat olivat hyvin yksinkertaisia koska ne kaumly-taumlnnoumlssauml sisaumllsivaumlt vain Djangon valmiita lomakkeita Halusin tyylitellauml lo-makkeet kaumlyttaumlen Bootstrap-kirjaston luokkia Tein generic_formhtml-si-vupohjan joka tyylitteli jokaisen lomakkeen kentaumln for-silmukkaa apuna kaumlyttaumlen Ilman taumltauml lomakkeiden kaikki kentaumlt olisi pitaumlnyt tyylitellauml ma-nuaalisesti Alla esimerkkinauml sivuston sisaumlaumlnkirjautumislomakkeen HTML-koodi ja kuva valmiista lomakkeesta (Kuva 12) lt-- accountsregistrationloginhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtLoginlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 34: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

28

Login ltbuttongt ltformgt ltsmallgtDont have an account yet lta href= url register gtSign Upltagt ltsmallgt ltbrgt ltsmallgt lta href= url password_reset gt Forgot your password ltagt ltsmallgt ltdivgt endblock content

Kuva 12 Kirjautumiseen kaumlytetty lomake

79 Rekisteroumlinti

Uuden kaumlyttaumljaumltilin rekisteroumlinti piti tehdauml itse koska sitauml ei ollut valmiiksi auth-kirjastossa Ominaisuus tarvitsi naumlkymaumln lomakkeen sivupohjan ja URL-maumlaumlrityksen Hyoumldynsin ominaisuuden teossa Djangon valmista User-CreationForm-lomaketta

791 Lomake

Aloitin tekemaumlllauml uuden lomakkeen formspy-tiedostoon Maumlaumlritin lomak-keen laajentamaan UserCreationForm-lomaketta Lomakkeeseen tulevat kentaumlt piti erikseen maumlaumlrittaumlauml Meta-luokkaan kuuluvan fields-muuttujan avulla Kaumlyttaumljaumln saumlhkoumlposti piti erikseen hakea lomakkeen laumlhettaumlmaumlstauml datasta ja maumlaumlrittaumlauml se user-objektin email-kentaumln arvoksi Tein taumlmaumln au-tomaattisesti kutsuttavaa save-funktiota kaumlyttaumlen Username password1 ja password2 tallentuivat automaattisesti UserCreationForm-lomakkeen ansiosta

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 35: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

29

accountsformspy class RegisterForm(UserCreationForm) email = formsEmailField(required=True) class Meta model = User fields = ( username email password1 password2 ) def save(self commit=True) user = super(RegisterForm self)save(commit=False) useremail = selfcleaned_data[email] if commit usersave() return user

792 Naumlkymauml

Lomake tarvitsi sitauml kaumlyttaumlvaumln naumlkymaumln Naumlkymaumln olisi voinut tehdauml kaumlyt-taumlmaumlllauml generic-naumlkymiin kuuluvaa FormView-naumlkymaumlauml mutta paumlaumldyin kuitenkin tekemaumlaumln sen itse koska se oli yksinkertainen Naumlkymaumllle piti maumlaumlrittaumlauml funktiot get- ja post-metodeja varten koska taumlytetyn lomakkeen data laumlhetettaumlisiin naumlkymaumllle POST-metodia kaumlyttaumlen

accountsviewspy class RegisterView(View) def get(self request args kwargs) form = RegisterForm() context = form form return render( request registrationregisterhtml context ) def post(self request args kwargs) form = RegisterForm(requestPOST) context = form form if formis_valid() formsave() return redirect(reverse(login)) return render( request registrationregisterhtml context )

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 36: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

30

793 Sivupohja

Tein uuden sivupohjan accountsregistration-kansioon Kaumlytin sivupohjan teossa apuna aiemmin luomaani generic_formhtml-sivupohjaa joka tyy-litteli lomakkeen kentaumlt automaattisesti lt-- accountsregistrationregisterhtml --gt extends basehtml block content ltdiv class=container max-width-smallgt lth1 class=mb-5gtRegisterlth1gt ltform action= method=postgt csrf_token formnon_field_errors include registrationgeneric_formhtml ltbutton type=submit class=btn btn-md btn-primarygt Register ltbuttongt ltformgt ltsmallgt Already signed up lta href= url login gt Login ltagt ltsmallgt ltdivgt endblock content

Kuva 13 Kaumlyttaumljaumltilin tekoon kaumlytetty lomake

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 37: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

31

710 Profiili

Tein kirjautuneita kaumlyttaumljiauml varten naumlkymaumln josta naumlkyi kaumlyttaumljaumltilien tie-dot (Kuva 14) Lisaumlsin samaan naumlkymaumlaumln napit kaumlyttaumljaumltietojen muok-kausta ja salasanan vaihtoa varten Yksinkertaisen ProfileView-naumlkymaumln ai-nut tehtaumlvauml oli palauttaa kaumlyttaumljaumllle sivupohja LoginRequiredMixin-luo-kasta periytymaumlllauml naumlkymaumlaumln paumlaumlsi ainoastaan sisaumlaumlnkirjautuneet kaumlyttauml-jaumlt accountsviewspy class ProfileView(LoginRequiredMixin View) def get(self request args kwargs) return render(request accountsprofilehtml)

Kuva 14 Profiilisivu

711 Kaumlyttaumljaumltilin muokkaus

Tein lomakkeen jonka avulla kaumlyttaumljaumlt pystyivaumlt muokkaamaan omia tie-tojaan kuten etunimeauml tai saumlhkoumlpostiosoitetta Kaumlytin apuna Djangon val-mista UserChangeForm-lomaketta joka on tarkoitettu kaumlyttaumljaumltilin tieto-jen muokkaamiseen Lomake oli muuten yksinkertainen mutta salasana-kenttauml piti poistaa super-funktiota apuna kaumlyttaumlen Tein salasanan vaihdon erikseen kaumlyttaumlen Djangon omaa PasswordChangeForm-lomaketta accountsformspy class EditForm(UserChangeForm) def __init__(self args kwargs) super(EditForm self)__init__(args kwargs) del selffields[password] class Meta model = User

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 38: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

32

fields = ( first_name last_name email password )

Naumlkymaumln avulla asetin lomakkeen kenttien arvot oletuksena kaumlyttaumljaumln ny-kyisiin arvoihin maumlaumlrittaumlmaumlllauml lomakkeen instance-muuttujan arvoksi pyynnoumln laumlhettaumlmaumln kaumlyttaumljaumln accountsviewspy class UserEditView(LoginRequiredMixin View) def get(self request args kwargs) form = EditForm(instance=requestuser) context = form form return render(request accountsuser_edithtml context) def post(self request args kwargs) form = EditForm(requestPOST instance=requestuser) context = form form if formis_valid() formsave() return redirect(reverse(profile)) return render(request accountsuser_edithtml context) Tein sivunpohjan samanlailla kuin aiemmatkin lomakkeet Kenttien arvot olivat oletuksena kaumlyttaumljaumln nykyiset arvot (Kuva 15)

Kuva 15 Kaumlyttaumljaumltilin muokkaus

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 39: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

33

712 Shortener-sovellus

Tein uuden sovelluksen nimellauml shortener Taumlmauml sovellus tuli sisaumlltaumlmaumlaumln kaikki linkin lyhentaumlmiseen liittyvaumlt asiat ShortenedURL-mallista hallinta-paneeliin Uusi sovellus luotiin alla olevalla komennolla python managepy startapp shortener

Sovellus piti vielauml aktivoida lisaumlaumlmaumlllauml se settingspy-tiedostossa olevaan INSTALLED_APPS-listaan settingspy INSTALLED_APPS = [ djangocontribadmin djangocontribauth djangocontribcontenttypes djangocontribsessions djangocontribmessages djangocontribstaticfiles accounts shortener

]

713 ShortenedURL-malli

Tein uuden mallin modelspy-tiedostoon Tarvitsin mallille vain neljauml kent-taumlauml Alkuperaumlisen osoitteen lyhennetyn osoitteen ja paumlivaumlmaumlaumlraumln lisaumlksi lisaumlsin kentaumln kaumlyttaumljaumlauml varten joka linkin lyhensi Taumlmaumln kentaumln avulla pystyin myoumlhemmin hakemaan vain kirjautuneen kaumlyttaumljaumln tekemaumlt linkit shortenermodelspy from djangodb import models from djangocontribauthmodels import User class ShortenedUrl(modelsModel) original_url = modelsCharField(max_length=500) shortcode = modelsCharField(max_length=50 unique=True) date = modelsDateTimeField(auto_now_add=True) owner = modelsForeignKey( User on_delete=modelsCASCADE )

Lisaumlsin malliin vielauml kaksi funktiota __str__-funktio on Python-funktio jota kutsumaan automaattisesti kun objektia halutaan kuvata merkkijonona Get_absolute_url-funktiota tulin kaumlyttaumlmaumlaumln myoumlhemmin uudelleenoh-jauksen teossa Asetin molemmat funktiot palauttamaan original_url-ar-von def __str__(self)

return selforiginal_url

def get_absolute_url(self) return selforiginal_url

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 40: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

34

714 Osoitteen lyhennys

Osoitteen lyhentaumlmistauml varten piti tehdauml algoritmi naumlkymauml lomake ja si-vupohja Suunnittelin lomakkeen sijaitsevan suoraan etusivulla joten sen naumlyttaumlmauml sivupohja toimisi samalla sovelluksen etusivuna Lomake tuli si-saumlltaumlmaumlaumln kaksi kenttaumlauml Ensimmaumliseen kenttaumlaumln syoumltettiin alkuperaumlinen osoite ja toiseen kaumlyttaumljauml sai halutessaan itse maumlaumlrittaumlauml lyhennetyn linkin Jos lyhennettyauml linkkiauml ei erikseen maumlaumlritetty sovellus generoi satunnai-sen

7141 Algoritmi

Osoitteen lyhennys -algoritmi oli erittaumlin yksinkertainen Generoin satun-naisesti 8-merkkisen merkkijonon Jos generoitu merkkijono oli jo kaumly-toumlssauml sovellus generoi uuden merkkijonon kunnes se oli uniikki Tein funk-tion jonka avulla alkuperaumlinen osoite lyhennetaumlaumln Funktion olisi voinut laittaa naumlkymaumlaumln tai malliin mutta halusin pitaumlauml ne mahdollisimman siis-teinauml ja yksinkertaisina Paumlaumltin tehdauml utilspy-tiedoston funktiota varten En ollut vielauml varma milloin ja missauml tulisin taumltauml funktiota kutsumaan utilspy import random import string def create_shortcode(instance) new_shortcode = characters = stringascii_letters + stringdigits add one random character at a time for x in range(8) new_shortcode += randomchoice(characters) check that it is unique MyClass = instance__class__ shortcode_exists = MyClassobjectsfilter( shortcode=new_shortcode )exists() if shortcode exists create a new one if shortcode_exists return create_shortcode(instance) return new_shortcode

7142 Naumlkymauml

Tein lomaketta varten uuden CreateView-naumlkymaumlstauml periytyvaumln naumlkymaumln CreateView-naumlkymaumln kaumlyttouml vaumlhensi kirjoitettavan koodin maumlaumlraumlauml koska lomakkeen kentaumlt ja niiden validaattorit tulivat suoraan aiemmin luodusta mallista Annoin lomaketta kaumlyttaumlvaumln naumlkymaumln nimeksi HomeView koska se tuli toimimaan palvelun etusivuna Kaikkien kenttien sijaan valitsin lo-makkeeseen ainoastaan alkuperaumlisen pitkaumln osoitteen ja lyhyelle osoit-teelle tulevan paumlaumltteen Kun osoitteen lyhennys oli valmis ja tallennettu tietokantaan halusin uudelleenohjata kaumlyttaumljaumln detail-naumlkymaumlaumln josta pystyi esimerkiksi kopioimaan lyhennetyn osoitteen

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 41: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

35

shortenerformspy class HomeView(CreateView) model = ShortenedUrl success_url = template_name_suffix = _create fields = [ original_url shortcode

] Luotua objektia piti vielauml muuttaa ennen sen tallentamista tietokantaan Muun muassa sen tekijauml piti maumlaumlrittaumlauml owner-kenttaumlaumln Lisaumlksi uudel-leenohjausta varten tarvittava URL-osoite piti maumlaumlrittaumlauml luodun objektin primaumlaumlriavainkentaumln arvon avulla Tein myoumls lyhyen osoitteen generoinnin taumlssauml vaiheessa jos kaumlyttaumljauml ei ollut sitauml manuaalisesti maumlaumlrittaumlnyt Kaumlytin generointiin aiemmin luomaani utilspy-tiedostossa olevaa create_short-code-funktiota Tein naumlmauml muutokset automaattisesti ajettavan form_va-lid-funktion avulla jota kutsuttiin kun lomake laumlpaumlisi sille asetetun vali-doinnin Onnistuneen lyhentaumlmisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyt-taumljaumln Detail-naumlkymaumlaumln redirect-funktion avulla shortenerviewspy def form_valid(self form) obj = formsave(commit=False) if (selfrequestuseris_authenticated == False and objshortcode = None) raise PermissionDenied if not objshortcode objshortcode = create_shortcode(obj) if selfrequestuseris_authenticated objowner = selfrequestuser objsave() selfsuccess_url = reverse_lazy( url-detail kwargs= pkobjpk ) return redirect(selfsuccess_url)

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 42: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

36

Kuva 16 Sovelluksen etusivu jossa oli lyhennykseen kaumlytettaumlvauml lomake

715 Detail-naumlkymauml

Detail-naumlkymauml oli naumlkymauml johon kaumlyttaumljauml ohjattiin linkin lyhentaumlmisen jaumll-keen Naumlkymauml naumlytti alkuperaumlisen pitkaumln osoitteen lyhennetyn osoitteen ja napin jonka avulla lyhennetyn osoitteen pystyi automaattisesti kopioi-maan Kaumlytin naumlkymaumlssauml Djangosta valmiiksi loumlytyvaumlauml DetailView-naumlkymaumlauml laajentamalla sitauml Naumlkymaumlstauml tuli hyvin yksinkertainen koska laajennettu DetailView-naumlkymauml toimi laumlhes sellaisenaan Oikean objektin DetailView-naumlkymauml haki URL-maumlaumlrityksen mukana tulevan primaumlaumlriavaimen perus-teella shortenerviewspy class ShortenedUrlDetailView(DetailView)

model = ShortenedUrl Detail-naumlkymaumln URL-maumlaumlritys ja sivupohja shortenerurlspy path( shortened-urlltintpkgt ShortenedUrlDetailViewas_view() name=shortened_url_detail ) ltmdash- shortenershortenedurl_detailhtml --gt extends basehtml block content

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 43: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

37

lth1gt objectshortcode lth1gt lth2gtOriginal URLlth2gt ltpgt objectoriginal_url ltpgt endblock content

716 Lyhyiden osoitteiden uudelleenohjaus

Uudelleenohjausta varten piti tehdauml URL-maumlaumlritys ja naumlkymauml Aloitin naumlky-maumlstauml koska sitauml kaumlytettiin URL-maumlaumlrityksen path-funktiossa Oikea alku-peraumlinen URL haettiin URL-maumlaumlrityksessauml olleen dynaamisen osan avulla Naumlkymaumlssauml taumlhaumln arvoon paumlaumlsi kaumlsiksi kwargs-listan avulla joka sisaumllsi re-quest-objektin mukana tulleet avainsana-argumentit Jos shortcode-muut-tujaa vastaavaa objektia ei loumlytynyt naumlkymauml palautti 404-virheen joka tar-koitti ettei haluttua resurssia loumlytynyt Lopuksi naumlkymauml uudelleenohjasi kaumlyttaumljaumln redirect-funktion avulla shortenerviewspy from djangoshortcuts import get_object_or_404 redirect from djangoviews import View from models import ShortenedUrl class RedirectShortenedUrl(View) def get(self request args kwargs) destination_url = get_object_or_404( ShortenedUrl shortcode=kwargs[shortcode] ) return redirect(destination_url) Tein sovelluskohtaiseen urlspy-tiedostoon uuden URL-maumlaumlrityksen Maumlauml-ritys kaappaa minkauml tahansa merkkijonon joka tulee suoraan sivuston do-mainin jaumllkeen ja ohjaa RedirectShortenedUrl-naumlkymaumlaumln shortenerurlspy from djangourls import path from views import RedirectShortenedUrl urlpatterns = [ path(ltstrshortcodegt RedirectShortenedUrlas_view())

]

717 Lyhennettyjen osoitteiden hallintapaneeli

Tein sovellukseen naumlkymaumln joka listasi kaumlyttaumljaumln lyhentaumlmaumlt osoitteet Sa-malla naumlkymauml teki napit osoitteiden kopioimista ja poistamista varten An-noin naumlkymaumllle nimeksi MyUrlsView Naumlkymauml haki kaikki ShortenedUrl-mallin objektit joiden tekijauml oli requestuser eli pyynnoumln laumlhettaumlnyt kaumlyt-taumljauml Lisaumlsin naumlmauml objektit context-dataan joka laumlhetettiin sivupohjalle shortenerviewspy class MyUrlsView(LoginRequiredMixin View) def get(self request) my_urls = ShortenedUrlobjectsall()filter( owner=requestuser

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 44: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

38

) context = my_urls my_urls return render(request shortenermy_urlshtml context)

Kaumlytin sivupohjassa for-silmukkaa jokaisen lyhennetyn linkin listaamiseen Jaumlrjestin osoitteet shortcoden perusteella aakkosjaumlrjestykseen lt-- shortenermy_urlshtml--gt if my_urls for link in my_urls|dictsortshortcode lt-- Shortened links --gt endfor else ltpgtYou have not shortened any links yetltpgt endif

Koska jaumlrjestaumlminen toimi ASCII-koodien perusteella isolla kirjaimella al-kavat linkit menivaumlt listan ensimmaumlisiksi (Kuva 17) Se ei ollut aivan sitauml mitauml halusin mutten naumlhnyt tarpeelliseksi korjata asiaa

Kuva 17 Linkkien hallintaan tarkoitettu sivu johon oli listattu kaumlyttaumljaumln lyhentaumlmaumlt osoitteet

718 Lyhennettyjen osoitteiden poistaminen

Tein uuden naumlkymaumln osoitteiden poistamista varten Kaumlytin naumlkymaumln poh-jalla DeleteView-naumlkymaumlauml joka oli tarkoitettu malleihin liittyvien objektien poistamiseen Lisaumlsin delete-funktioon tarkistuksen joka varmisti ettauml

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 45: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

39

poistettava linkki oli sillauml hetkellauml kirjautuneen kaumlyttaumljaumln tekemauml Poiston onnistumisen jaumllkeen naumlkymauml uudelleenohjasi kaumlyttaumljaumln takaisin hallinta-paneeliin LoginRequiredMixin-luokkaa apuna kaumlyttaumlen kirjautumattomat kaumlyttaumljaumlt eivaumlt paumlaumlsseet naumlkymaumlaumln shortenerviewspy class ShortenedUrlDeleteView(LoginRequiredMixin DeleteView) model = ShortenedUrl success_url = reverse_lazy(my-urls) def delete(self request args kwargs) selfobject = selfget_object() if selfobjectowner == requestuser selfobjectdelete() return HttpResponseRedirect(selfget_success_url()) else raise Http404

Tein naumlkymaumlauml varten uuden URL-maumlaumlrityksen Maumlaumlritys sisaumllsi dynaamisen osan jonka perusteella ShortenedUrlDeleteView-naumlkymauml valitsi oikean ob-jektin Kaumlytin objektin primaumlaumlriavainta valintaperusteena shortenerurlspy path( shortened-urlltintpkgtdelete ShortenedUrlDeleteViewas_view() name=url-delete )

DeleteView-naumlkymauml tarvitsi sivupohjan joka sisaumllsi POST-metodia kaumlyttauml-vaumln lomakkeen jolla poistaminen vahvistettiin Sivupohjan nimen piti olla muotoa modelname_confirm_deletehtml Valmis lomake sisaumllsi ainoas-taan submit-tyyppiauml olevan napin (Kuva 18)

Kuva 18 Lomake jolla vahvistettiin lyhennetyn osoitteen poistaminen

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 46: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

40

719 Linkin kopioiminen

Lisaumlsin vielauml linkin kopioimiseen tarkoitettuihin nappeihin JavaScript-koo-din jonka avulla linkin kopioiminen leikepoumlydaumllle tapahtui Kaumlytin koodissa jQuery-kirjastoon kuuluvia toimintoja koska se oli jo projektissa Bootstrap-kirjaston takia Tein uuden JavaScript-tiedoston projektin juu-ressa olevan static-kansion sisaumlllauml olevan js-kansion sisaumllle Annoin tiedos-tolle nimeksi mainjs Lisaumlsin taumlmaumln samalla basehtml-sivupohjan body-elementin loppuun Koodi loi vaumlliaikaisen elementin jonka sisaumllloumlksi se maumlaumlritti lyhennetyn linkin Elementin sisaumllloumln kopioimisen jaumllkeen ele-mentti poistettiin remove-funktiolla mainjs windowonload = function () Get all the elements that match the selector as arrays var shortenedUrlCopyButtons = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url-copy-button) ) var shortenedUrls = Arrayprototypeslicecall( documentquerySelectorAll(shortened-url) ) Loop through the button array shortenedUrlCopyButtonsforEach(function(btn idx) btnaddEventListener(click function() var $temp = $(ltinputgt) $(body)append($temp) $tempval(documentlocationhost + + shortene-dUrls[idx]innerHTML)select() documentexecCommand(copy) $tempremove() ) )

8 YHTEENVETO

Projektin tavoitteena oli toteuttaa toimiva web-sovellus Djangolla jolla pystyisi lyhentaumlmaumlaumln pitkiauml linkkejauml Linkkien lyhentaumlmisen lisaumlksi sovelluk-sen vaatimuksina oli tuki kaumlyttaumljaumltilien tekoon ja niiden hallinnointiin Opinnaumlytetyoumln tuloksena syntynyt sovellus vastasi sille asetettuja tavoit-teita ja vaatimuksia Yhtenauml esimerkkiprojektin tarkoituksena oli perehtyauml siihen miten hyvin Django toimii vielauml nykyisin sovellusten teossa kun asiakaspuolen sovelluskehykset ovat alkaneet nousta suosiossa Yhauml use-ammat suositut palvelut ovat siirtyneet kaumlyttaumlmaumlaumln JavaScript-sovelluske-hyksiauml sulavamman ja reaktiivisemman kaumlyttoumlkokemuksen takaamiseksi Mielestaumlni Django kuitenkin soveltui taumlssauml tyoumlssauml tehtyyn sovellukseen erinomaisesti

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 47: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

41

81 Haasteet

Minulla oli jonkin verran kokemusta Djangosta jo ennen opinnaumlytetyoumln aloittamista joten osasin melko hyvin hyoumldyntaumlauml Djangon valmiiksi tarjo-amia ominaisuuksia kuten esimerkiksi Djangon valmiita naumlkymiauml apuna omien naumlkymien teossa Ilman naumlitauml laumlhtoumltietoja ylipaumlaumltaumlaumln naumliden ominai-suuksien loumlytaumlminen olisi tuottanut haasteita tai olisin tehnyt jokaisen nauml-kymaumln taumlysin tyhjaumlstauml Toumlrmaumlsin myoumls ongelmiin tyoumltauml tehdessauml Yksi tyoumlssauml vastaan tullut on-gelma liittyi lomakkeiden tyylittelyyn Mietin pitkaumlaumln miten pystyisin jaumlr-kevaumlsti tyylittelemaumlaumln lomakkeita ilman ettauml niistauml jokainen pitaumlisi kirjoit-taa manuaalisesti kenttauml kerrallaan Taumlhaumlnkin kuitenkin loumlytyi vastaus lauml-hes suoraan Djangon omasta dokumentaatiosta kunhan vain osasi hakea oikealla hakusanalla Toinen ongelma liittyi lomakkeeseen jolla paumlivitettiin kaumlyttaumljaumltietoja Lomake piti sisaumlllaumlaumln oletuksena kaikki mahdolliset rivit kaumlyttaumljaumlnimestauml liittymispaumlivaumlaumln ja niitauml pystyi vapaasti muokkaamaan Turhat kentaumlt sai melko helposti poistettua mutta salasanakentaumln poista-misen jaumllkeen lomake ei toiminut ollenkaan Pienen tutkimisen jaumllkeen se piti poistaa vasta lomakkeen alustuksen jaumllkeen

82 Jatkokehitys

Sovellus on ominaisuuksiltaan taumlllauml hetkellauml yksinkertainen ja sitauml olisi helppo jatkaa joka osa-alueella Ennen tyoumln aloitusta suunnittelin ettauml lopullinen versio sisaumlltaumlisi jokaiselle lyhennetylle linkille oman sivun joka sisaumlltaumlisi muun muassa klikkausten maumlaumlraumln ajankohdan ja muuta tietoa Taumlmaumln datan olisi voinut myoumls visualisoida kaumlyttaumlen esimerkiksi Chartjs-kirjastoa Taumlmauml ominaisuus jaumli kuitenkin puuttumaan koska sovellus olisi mielestaumlni kasvanut liian isoksi opinnaumlytetyoumltauml varten eikauml kyseinen omi-naisuus kuitenkaan ole sovelluksen toiminnan kannalta elintaumlrkeauml Toinen idea olisi korvata Djangon oma sivupohjajaumlrjestelmauml jollakin Ja-vaScript-sovelluskehyksellauml kuten Reactilla Naumlin esimerkiksi linkkien hal-lintapaneelista tulisi hieman sujuvampi kaumlyttaumlauml Reactin tai muun Ja-vaScript-sovelluskehyksen kaumlyttouml lisaumlisi lopulta kuitenkin varsin vaumlhaumln lisauml-arvoa sovelluksen toimintaan Olisi eri asia jos sovellus sisaumlltaumlisi paljon koko ajan muuttuvia dynaamisia elementtejauml Sovelluksen ulkoasu on tehty kaumlyttaumlen enimmaumlkseen Bootstrap-kirjaston oletustyylejauml Ne toimivat mutta on mahdollista ettauml jokin toinen sovellus naumlyttaumlisi ulkoisesti laumlhes identtiseltauml Sovellusta ei ole myoumlskaumlaumln optimoitu isoille resoluutioille joita esimerkiksi linkkien hallintapaneeli ei osaa jaumlrke-vaumlsti hyoumldyntaumlauml

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 48: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

42

LAumlHTEET

Angular (nd) AngularJS to Angular Concepts Quick Reference Viitattu 2842018 httpsangularioguideajs-quick-reference Akshar (2015) Understanding Django Middlewares Viitattu 1252018 httpswwwagiliqcomblog201507understanding-django-middle-wares Atlassian (nd) What is Git Viitattu 2842018 httpswwwatlassiancomgittutorialswhat-is-git Basu S (2013) Ruby of Rails Study Guide The History of Rails Viitattu 1442018 httpscodetutspluscomarticlesruby-on-rails-study-guide-the-history-of-rails--net-29439 Brown K (2016) What is GitHub and What Is It Used For Viitattu 2842018 httpswwwhowtogeekcom180167htg-explains-what-is-github-and-what-do-geeks-use-it-for Django Documentation (nd) Making Queries Viitattu 252018 httpsdocsdjangoprojectcomen20topicsdbqueries Django Documentation (nd) The Django template language Viitattu 252018 httpsdocsdjangoprojectcomen20reftemplateslanguage Django Documentation (nd) Why does this project exist Viitattu 752018 httpsdocsdjangoprojectcomendevfaqgeneralwhy-does-this-pro-ject-exist Freitas V (2017) Class-Based Views vs Function-Based Views Viitattu 552018 httpssimpleisbetterthancomplexcomarticle20170321class-based-views-vs-function-based-viewshtml Gavigan D (2018) The History of Angular Viitattu 552018 httpsmediumcomthe-startup-lab-blogthe-history-of-angular-3e36f7e828c7 GitHub (2017) The fifteen most popular languages on GitHub Viitattu 1552018 httpsoctoversegithubcom

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 49: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

43

Google for Education (2017) Python Introduction Viitattu 1542018 httpsdevelopersgooglecomedupythonintroduction Google for Education (2017) Code Checked at Runtime Viitattu 2832018 httpsdevelopersgooglecomedupythonintroduc-tionCodeCheckedatRuntime HotFrameworks (2018) Web framework rankings Viitattu 642018 httpshotframeworkscomtop-frameworks IEEE Spectrum (2017) The 2017 Top Programming Languages Viitattu 552018 httpsspectrumieeeorgcomputingsoftwarethe-2017-top-program-ming-languages Ndegwa A (2016) What is a Web Application Viitattu 1442018 httpswwwmaxcdncomonevisual-glossaryweb-application Niemi T (2015) Sovelluskehys ndash Verkkoraumlaumltaumllin tyoumlkalupakki Viitattu 1442018 httpswwwsofokuscomfiblogisovelluskehys-verkkoraatalin-tyokalu-pakki Microsoft (2018) Introduction to ASPNET Core Viitattu 2832018 httpsdocsmicrosoftcomen-usaspnetcoreview=aspnetcore-21 OrsquoBrien J (2016) The History of Laravel Viitattu 1442018 httpsmediumcomvehikl-newsa-brief-history-of-laravel-5d55970885bc Pythonorg (2013) Python Introduction Viitattu 252018 httpswwwpythonorgdevpepspep-0008introduction Python Wiki (nd) For Loops Viitattu 552018 httpswikipythonorgmoinForLoop Python Wiki (nd) Why is Python a dynamic language and also a strongly typed language Viitattu 3032018 httpswikipythonorgmoinWhy20is20Python20a20dyna-mic20language20and20also20a20strongly20typed20langu-age Reactjs (nd) Introducing JSX Viitattu 552018 httpsreactjsorgdocsintroducing-jsxhtml Ronacher A (2017) What does ldquomicrordquo mean Viitattu 1432018 httpflaskpocooorgdocs012forewordwhat-does-micro-mean

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress

Page 50: WEB-SOVELLUKSEN TEKO DJANGOLLA · 2018-10-02 · TIIVISTELMÄ Tietotekniikan koulutusohjelma Riihimäki Tekijä Tommi Vuori Vuosi 2018 Työn nimi Web-sovelluksen teko Djangolla Työn

44

TheDjangoBook (nd) Model-View-Controller Design Pattern Viitattu 1542018 httpdjangobookcommodel-view-controller-design-pattern TheDjangoBook (nd) Defining Django Models in Python Viitattu 752018 httpsdjangobookcomdjango-modelsdefining-django-models-in-pyt-hon TIOBE (2018) TIOBE Index for April 2018 Viitattu 1542018 httpwwwtiobecomtiobe-index ValueCoders (2018) Why Laravel Is The Best PHP Framework In 2018 Vii-tattu 1442018 httpswwwvaluecoderscomblogtechnology-and-appslaravel-best-php-framework-2017 Visual Studio Code Documentation (nd) Why did we build Visual Studio Code Viitattu 2842018 httpscodevisualstudiocomdocseditorwhyvscode Vega J (2017) Client-side vs server-side rendering why itrsquos not all black and white Viitattu 1442018 httpsmediumfreecodecamporgwhat-exactly-is-client-side-rende-ring-and-hows-it-different-from-server-side-rendering-bd5c786b340d Vuejs (nd) What is Vuejs Viitattu 852018 httpsvuejsorgv2guide Vuejs (nd) Comparison with Other Frameworks Viitattu 852018 httpsvuejsorgv2guidecomparisonhtml Wappalyzer (nd) Websites using Express Viitattu 852018 httpswwwwappalyzercomtechnologiesexpress