Django

Kategória: Python.

Áttekintés

A Django egy Python alapú webes keretrendszer. Ez az oldal egy áttekintést nyújt az alapokról. A leírást az alábbiak alapján készítettem el:

Első lépések

Python

A Django használatához Pythonra van szükség. Feltételezem, hogy a Python fejlesztőkörnyezet már fel van telepítve. Erről részletesen olvashatunk a Bevezetés a Pythonba oldalon.

A Django telepítése

A Django nem része a Python alaprendszernek, azt külön fel kell telepíteni, a Python könyvtáraknál megszokott módon:

pip install django

Annak ellenőrzése, hogy a telepítés sikeres volt-e:

python -m django --version

Az írás pillanatában nálam a 4.1-es verzió van feltelepítve.

Projekt létrehozása

A használathoz projektet, azon belül pedig appokat kell létrehozni. Lássuk a Helló, világ! programot! Adjuk ki a következő parancsot:

django-admin startproject hello_django_site

Ez létrehoz egy könyvtárszerkezetet ill. fájlokat. Az, hogy mi micsoda, most még nem érdekes; néhányra visszatérünk később.

Lépjünk be a frissen létrehozott könyvtárba:

cd hello_django_site

Indítsuk el a szervert:

python manage.py runserver

A migrációval kapcsolatos figyelmeztetést most hagyjuk figyelmen kívül, hamarosan visszatérünk rá.

Nyissuk meg a http://127.0.0.1:8000/ oldalt. Ha minden hibamentesen történt, akkor a következőt (vagy valami hasonlót) látjuk:

install_success.png

Alkalmazás létrehozása

A rendszer tehát fut, viszont ahhoz, hogy mi magunk is fejleszthessünk, alkalmazásokat (appokat) kell létrehozni.

Állítsuk le a szervert (Ctrl+C). Hozzunk létre egyet a következő paranccsal, amit abból a könyvtárból adjunk ki, ahol a manage.py található (ahol a leírás alapján most vagyunk):

python manage.py startapp hello_django_app

Létrejött egy hello_django_app könyvtár, benne az adott alkalmazáshoz tartozó fájlokkal.

URL minták

Viszonylag központi elemei a Django rendszereknek az URL minták. Webes rendszerekről van szó, és itt adjuk meg azt, hogy az alkalmazásunk milyen URL-en milyen szolgáltatást nyújt.

Keressük meg a hello_django_site/hello_django_site/urls.py fájlt, és módosítsuk a tartalmát a következőre:

from django.contrib import admin
from django.urls import path, include
 
urlpatterns = [
    path('hello_django_app/', include('hello_django_app.urls')),
    path('admin/', admin.site.urls),
]

Ezzel azt adjuk meg, hogy az alkalmazásunkat a hello_django_app URL-en szeretnénk elérni, és azon belül a hello_django_app.urls tartalmazza a további részleteket. Ez a fájl viszont alapból sajnos nem létezik, azt létre kell hozni.

hello_django_site/hello_django_app/urls.py

from django.urls import path
from . import views
 
urlpatterns = [
    path('', views.index, name='index'),
]

Nézetek

A fenti utolsó kódrészletben ezt látjuk: views.index. Ezt a következőképpen valósítsuk meg:

hello_django_site/hello_django_app/views.py

from django.http import HttpResponse
 
def index(request):
    return HttpResponse('Hello, Django world!')

Indítsuk újra a szervert:

python manage.py runserver

Nyissuk meg a http://127.0.0.1:8000/hello_django_app/ oldalt. Ha minden sikeres volt, akkor a Hello, Django world! szöveget látjuk a böngészőben.

Sablonok

A webes alkalmazásoknál a legritkább esetben drótozzuk be a forráskódba az eredményt, a leggyakrabban HTML fájlokat használunk. Valósítsuk meg itt is!

Először be kell regisztrálni az appot az oldal konfigurációs fájljába. Nyissuk meg a hello_django_site/hello_django_app/apps.py fájlt! Ez nálam a következőképpen néz ki:

from django.apps import AppConfig
 
class HelloDjangoAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'hello_django_app'

Az osztály nevét jegyezzük fel: HelloDjangoAppConfig. Most nyissuk meg a hello_django_site/hello_django_site/settings.py oldalt (itt tehát nem a hello_django_app, hanem kétszer a hello_django_site könyvtárat használtuk). Keressünk rá erre: INSTALLED_APPS. A felsorolásba tegyük bele ezt: 'hello_django_app.apps.HelloDjangoAppConfig'. Tehát az imént feljegyzett konfiguráció osztály elérési útvonalát, a következőképpen:

INSTALLED_APPS = [
    'hello_django_app.apps.HelloDjangoAppConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

A hello_django_site/hello_django_app/ könyvtáron belül hozzunk létre egy templates könyvtárat, és azon belül pedig egy index.html fájlt. A tartalmaz az alábbi legyen:

hello_django_site/hello_django_app/templates/index.html

<p>Hello, <b>Django</b> world!</p>

Normál esetben persze célszerű szabályos HTML oldalt írni; most az egyszerűség érdekében döntöttem a nem túl szabályos, ám működőképes változat mellett.

A hello_django_site/hello_django_app/views.py fájlt változtassuk meg a következőre:

from django.http import HttpResponse
from django.template import loader
 
def index(request):
    return HttpResponse(loader.get_template('index.html').render({}, request))

Tehát nem közvetlenül adjuk vissza az eredményt, hanem az index.html tartalmát.

A http://127.0.0.1:8000/hello_django_app/ oldalt megnyitva azt kell látnunk, hogy a Django vastag betűkkel van szedve.

Paraméterek

Ez idáig túl statikus; a HTML fájl közvetlen megnyitásával is kb. ugyanezt értük volna el. Vigyünk bele dinamikus tartalmat! Paraméterként adjunk át egy nevet:

hello_django_site/hello_django_app/views.py

from django.http import HttpResponse
from django.template import loader
 
def index(request):
    return HttpResponse(loader.get_template('index.html').render({
        'name': 'dynamic Django'
    }, request))

A render() függvény első paramétere a kontextus, egy szótár, aminek segítségével kulcs-érték párok formájában tudunk adatokat továbbítani a HTML fájlnak. A felhasználása:

hello_django_site/hello_django_app/templates/index.html

<p>Hello, <b>{{ name }}</b> world!</p>

Az eredmény: Hello, dynamic Django world!

Így dinamikus tartalmat tudtunk generálni.

Ez talán már elmegy Helló, világ! programnak; ennél bonyolultabbat a következőben látunk.

Egy összetettebb alkalmazás

Az alábbiakban egy olyan webes alkalmazást készítünk, melyben országokat és városokat lehet lementeni, módosítani és törölni.

Alapok

A kezdeti lépések parancsai az alábbiak:

django-admin startproject geography_site
cd geography_site
python manage.py startapp geography_app
python manage.py runserver

hello_django_site/hello_django_site/settings.py

INSTALLED_APPS = [
    'geography_app.apps.GeographyAppConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Tehát:

  • Elkészítettünk egy oldalt.
  • Létrehoztunk egy alkalmazást.
  • Elindítottuk a rendszert.
  • Beregisztráltuk az alkalmazást.

URL-ek

geography_site/geography_site/urls.py:

from django.contrib import admin
from django.urls import path, include
 
urlpatterns = [
    path('geography_app/', include('geography_app.urls')),
    path('admin/', admin.site.urls),
]

geography_site/geography_app/urls.py (létre kell hozni):

from django.urls import path
from . import views
 
urlpatterns = [
    path('', views.index, name='index'),
    path('country/add/', views.add_country),
    path('country/add/addrecord', views.add_country_record),
    path('country/update/<int:id>', views.update_country),
    path('country/update/updaterecord/<int:id>', views.update_country_record),
    path('country/delete/<int:id>', views.delete_country),
    path('city/add/', views.add_city),
    path('city/add/addrecord', views.add_city_record),
    path('city/update/<int:id>', views.update_city),
    path('city/update/updaterecord/<int:id>', views.update_city_record),
    path('city/delete/<int:id>', views.delete_city),
]

Itt tehát egy helyen felsoroljuk az összes végpontot, amit a rendszer nyújt. Ez segít jobban átlátni és karbantartani a rendszert. A végpontok jelentése:

  • index: ez a főoldal, ami tartalmazni fogja az országokat és a városokat is.
  • country/add/: új ország hozzáadása. Ez tartalmazza magát az űrlapot.
  • country/add/addrecord: ez az, amely végrehajtja a tulajdonképpeni ország hozzáadást, a kitöltött adatok alapján.
  • country/update/<int:id>: adott azonosítójú (<int:id>) ország adatainak módosítása. Ez tartalmazza az űrlapot a kitöltött adatokkal, amit a felhasználó módosíthat.
  • country/updaterecord/<int:id>: a módosítás tulajdonképpeni végrehajtása.
  • country/delete/<int:id>: adott azonosítójú ország törlése.
  • city/…: ugyanez városokkal.

Annak, hogy van-e a végén / vagy nincs, jelentősége van, így pontosan a fenti módon hozzuk létre.

Nézetek

geography_site/geography_app/views.py

from django.http import HttpResponse
 
def index(request):
    return HttpResponse('index')
 
def add_country(request):
    return HttpResponse('add_country')
 
def add_country_record(request):
    return HttpResponse('add_country_record')
 
def update_country(request, id):
    return HttpResponse(f'update_country: {id}')
 
def update_country_record(request, id):
    return HttpResponse(f'update_country_record: {id}')
 
def delete_country(request, id):
    return HttpResponse(f'delete_country: {id}')
 
def add_city(request):
    return HttpResponse('add_city')
 
def add_city_record(request):
    return HttpResponse('add_city_record')
 
def update_city(request, id):
    return HttpResponse(f'update_city: {id}')
 
def update_city_record(request, id):
    return HttpResponse(f'update_city_record: {id}')
 
def delete_city(request, id):
    return HttpResponse(f'delete_city: {id}')

Ellenőrizzük le, hogy működnek-e az oldalak:

Értelemszerűen a tartalmuk még üres.

Adatbázis

Kapcsolódás

Egy tipikus webes alkalmazás mögött van adatbázis, ahova az adatokat mentjük. A Django elrejti az adatbázishoz történő kapcsolódás részleteit a programozó elől. A kapcsolódáshoz szükséges nélkülözhetetlen adatokat a geography_site/geography_site/settings.py találjuk, a DATABASES résznél. Alapból ezt látjuk:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

Tehát egy SQLite adatbázis az alapértelmezett, ami az alapkönyvtárba menti az adatokat, db.sqlite3 néven. Ez a fájl már esetleg feltűnhetett, és 0 a mérete. Adjuk ki a következő parancsot:

python manage.py migrate

Az eredmény:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK

Ennek következtében egyrészt a db.sqlite3 fájl mérete is megugrott, másrészt ez volt az a parancs, ami miatt figyelmeztetést adott az indításkor; ezentúl nem fog.

Az adatmodell megalkotása

A Django-t használva úgy tudunk SQL adatbázist programozni, hogy valójában nem kell használnunk SQL-t. Adattáblák helyett osztályokat, oszlopok helyett adatmezőket, sorok helyett példányokat, külső kulcsok helyett objektum hivatkozásokat, lekérdezések helyett Python adatszerkezeteket használhatunk, és a háttérben a Django gondoskodik arról, hogy minden rendben le legyen mentve.

Ezt hívjuk egyébként objektum relációs leképezésnek, angolul object-relational mapping, szokásos rövidítéssel ORM. Ez arra utal, hogy a programozás általában objektumorientált, míg az adatbázis relációs.

Alkossuk meg az adatmodellt! Ez az alábbiakat tartalmazza:

  • Ország: van neki neve és egy jelzőbit, hogy EU-tag-e.
  • Város: szintén van neve; egy jelzőbit, hogy főváros-e, és hivatkozás arra az országra, amelyik területén található.

A modell az alábbi:

geography_site/geography_app/models.py

from django.db import models
 
class Country(models.Model):
    name = models.CharField(max_length=100)
    eu_member = models.BooleanField()
 
    def __str__(self):
        return f'{self.name}'
 
class City(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    is_capital = models.BooleanField()
 
    def __str__(self):
        return f'{self.name} ({self.country.name})'

Az str() függvények akár el is hagyhatóak; ezt tesztelési céllal tettem bele.

Érdemes megfigyelni a on_delete=models.CASCADE részt: ezzel azt jelezzük, hogy ha kitöltünk egy országot, akkor automatikusan törlődjenek a hozzá tartozó városok.

Adattáblák létrehozása

A következő lépéshez a geography_site/geography_site/settings.py fájlon belül a INSTALLED_APPS felsorolásban benne kell, hogy legyen a 'geography_app.apps.GeographyAppConfig', de ezt már korábban megtettük a nézetek miatt.

Hajtsuk végre a következő parancsot:

python manage.py makemigrations geography_app

Az eredmény:

Migrations for 'geography_app':
  geography_app\migrations\0001_initial.py
    - Create model Country
    - Create model City

Valamint a geography_site/geography_app/migrations/0001_initial.py fájl, mely leírja, hogy hogyan fogja létrehozni az adattáblákat.

A következő lépésben nézzük meg, hogy egészen pontosan milyen SQL parancsokat fog végrehajtani:

python manage.py sqlmigrate geography_app 0001

Az eredmény (némiképp kikozmetikázva):

BEGIN;
--
-- Create model Country
--
CREATE TABLE "geography_app_country" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "name" varchar(100) NOT NULL,
    "eu_member" bool NOT NULL
);
--
-- Create model City
--
CREATE TABLE "geography_app_city" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "name" varchar(100) NOT NULL,
    "is_capital" bool NOT NULL,
    "country_id" bigint NOT NULL REFERENCES "geography_app_country" ("id") DEFERRABLE INITIALLY DEFERRED
);
CREATE INDEX "geography_app_city_country_id_8c54f27b" ON "geography_app_city" ("country_id");
COMMIT;

Most adjuk ki ismét a következő parancsot:

python manage.py migrate

Az eredmény:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, geography_app, sessions
Running migrations:
  Applying geography_app.0001_initial... OK

Ezzel létrehozta az adattáblákat.

Játék az adatokkal

Mielőtt leprogramozzuk az adatelérést a forráskódban, játsszunk egy kicsit az adatokkal shellben! Adjuk ki a következő parancsot:

python manage.py shell

Ezzel elindítottunk egy Python shellt. Adjuk ki a következő parancsokat:

Python 3.10.4 (tags/v3.10.4:9d38120, Mar 23 2022, 23:13:41) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from geography_app.models import Country, City
>>> magyarország = Country(name='Magyarország', eu_member=True)
>>> magyarország.save()
>>> ausztria = Country(name='Ausztria', eu_member=True)
>>> ausztria.save()
>>> szerbia = Country(name='Szerbia', eu_member=False)
>>> szerbia.save()
>>> magyarország.id
1
>>> ausztria.id
2
>>> szerbia.id
3
>>> Country.objects.all()
<QuerySet [<Country: Magyarország>, <Country: Ausztria>, <Country: Szerbia>]>
>>> Country.objects.get(id=2)
<Country: Ausztria>
>>> City(name='Budapest', is_capital=True, country=magyarország).save()
>>> City(name='Szeged', is_capital=False, country=magyarország).save()
>>> City(name='Debrecen', is_capital=False, country=magyarország).save()
>>> City(name='Bécs', is_capital=True, country=ausztria).save()
>>> City(name='Linz', is_capital=False, country=ausztria).save()
>>> City(name='Belgrád', is_capital=True, country=szerbia).save()
>>> City(name='Újvidék', is_capital=False, country=szerbia).save()
>>> City(name='Szabadka', is_capital=False, country=szerbia).save()
>>> City.objects.all()
<QuerySet [<City: Budapest (Magyarország)>, <City: Szeged (Magyarország)>, <City: Debrecen (Magyarország)>, <City: Bécs (Ausztria)>, <City: Linz (Ausztria)>, <City: Belgrád (Szerbia)>, <City: Újvidék (Szerbia)>, <City: Szabadka (Szerbia)>]>
>>>

A parancsok nagyjából magukért beszélnek; röviden összefoglalva:

  • from geography_app.models import Country, City: be kell tölteni, hogy elérjük.
  • magyarország = Country(name='Magyarország', eu_member=True): példányosítás.
  • magyarország.save(): ezzel menti az adatbázisba.
  • magyarország.id: de nemcsak lementi, hanem létrehoz egy oda-vissza kapcsolatot. Az adatbázis által generált azonosítót beleteszi az objektumba.
  • Country.objects.all(): az összes adat lekérdezése.
  • Country.objects.get(id=2): lekérdezés szűrőfeltételek segítségével. (Kezd SQL "szaga" lenni!)
  • City(name='Budapest', is_capital=True, country=magyarország).save(): egybe is vonhatjuk a műveleteket.

Adatok lekérdezése adatbázis kezelőből

Mivel az adatbázis nem része a Django-nak, hanem egy külön termék, valójában a megfelelő adatbázis kliens segítségével is végre le tudjuk kérdezni, hogy hol tartunk. Az SQLite klienst innen tölthetjük le: https://www.sqlite.org/download.html. Telepítsük fel, majd indítsuk el, abból a könyvtárból, ahol a db.sqlite3 található:

sqlite3

Hajtsuk végre az alábbi maűveleteket:

SQLite version 3.39.2 2022-07-21 15:24:47
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open db.sqlite3
sqlite> .tables
auth_group                  django_admin_log
auth_group_permissions      django_content_type
auth_permission             django_migrations
auth_user                   django_session
auth_user_groups            geography_app_city
auth_user_user_permissions  geography_app_country
sqlite> select * from geography_app_country;
1|Magyarország|1
2|Ausztria|1
3|Szerbia|0
sqlite> select * from geography_app_city;
1|Budapest|1|1
2|Szeged|0|1
3|Debrecen|0|1
4|Bécs|1|2
5|Linz|0|2
6|Belgrád|1|3
7|Ujvidék|0|3
8|Szabadka|0|3
sqlite>

Láthatjuk, hogy alapból jópár táblát létrehoz a rendszer, ill. azt a kettőt is, melynek modelljét mi magunk alkottuk meg. Rendes SQL parancsos segítségével is le tudjuk kérdezni az adatokat.

Áttérés másik adatbázisra

Áttérés más adatbázisra meglehetősen egyszerű, legalábbis a "más adatbázisra áttérés" mércével mérve. A MySQL adatbázissal próbálkoztam. A https://dev.mysql.com/downloads/installer/ oldalon letölthető. Telepítéskor nagyjából mindent alapértelezésen hagytam, kivéve az autentikációt, ahol az 5-ös kompatibilitás verziót választottam.

Miután feltelepítettük, az adatbázis is és a MySQL Workbench is elindul. Ez utóbbiban hozzunk létre egy új sémát, pl. fent a negyedik ikonra (Create a new schema…) kattintva.

Hozzunk létre egy felhasználót is: Administration -> Users and Privileges -> Add Account

  • Login name: csaba
  • Password: farago
  • Administrative Roles: minden (nem volt kedvem kitapasztalni, hogy mi az a minimum, amire mindenképp szükség van)
  • A többi maradjon alapértelmezett
  • Apply

Fel kell telepíteni a mysqlclient Pythin csomagot:

pip install mysqlclient

Állítsuk be a geography_site/geography_site/settings.py fájlban az adatbázis elérést:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'geography',
        'USER': 'csaba',
        'PASSWORD': 'farago',
        'HOST': '127.0.0.1',
        'PORT': '3306',
    }
}

Majd a geography_site könyvtárból ismét:

python manage.py migrate

Ezzel létrejöttek az adattáblák a MySQL adatbázisban, amit a MySQL Workbench segítségével le is tudunk ellenőrizni. A fenti adatfeltöltős lépéseket ugyanígy végrehajtva itt is létrejönnek az adatok.

A főoldal

Végre elérkeztünk ahhoz a ponthoz, hogy tudunk valami látványosat is csinálni! A nézet megfelelő részét írjk át a következőre:

geography_site/geography_app/views.py

from django.http import HttpResponse
from django.template import loader
from geography_app.models import Country, City
 
def index(request):
    return HttpResponse(loader.get_template('index.html').render({
        'countries': Country.objects.all(),
        'cities': City.objects.all(),
    }, request))

Ilyet már láttunk: egy HTML oldalnak adatot adunk át, és azt feldolgozva adjuk vissza. Ebben az esetben az oldal az index.html, ami a szokásos főoldal, paraméterként pedig az összes országot és az összes várost átadjuk. A háttérben itt két SQL SELECT lekérdezés történik, de ezzel nekünk nem kell foglalkoznunk.

Végül valósítsuk meg magát a HTML-t is. Ehhez létre kell hoznunk egy templates könyvtárat a geography_app-on belül, és oda kell tennünk az index.html-t. (A hivatalos dokumentáció szerint a templates alá kell még egy újabb geography_app, és azon belül kell a HTML fájlokat létrehozni. Elképzelhetőnek tarton olyan komplexitást, ami ezt indokolttá teszi, de itt az egyszerűségre törekedtem, és így is működik.)

geography_site/geography_app/templates/index.html

<h1>Országok</h1>
 
<table border="1">
    <tr>
        <th>Név</th>
        <th>EU tag</th>
        <th>Módosítás</th>
        <th>Törlés</th>
    </tr>
    {% for country in countries %}
        <tr>
            <td>{{ country.name }}</td>
            <td>{% if country.eu_member %} igen {% else %} nem {% endif %}</td>
            <td><a href="country/update/{{ country.id }}">módosít</a></td>
            <td><a href="country/delete/{{ country.id }}">töröl</a></td>
        </tr>
    {% endfor %}
</table>
 
<p>
    <a href="country/add/">Országot hozzáad</a>
</p>
 
<h1>Városok</h1>
 
<table border="1">
    <tr>
        <th>Név</th>
        <th>Ország</th>
        <th>Főváros</th>
        <th>Módosítás</th>
        <th>Törlés</th>
    </tr>
    {% for city in cities %}
        <tr>
            <td>{{ city.name }}</td>
            <td>{{ city.country.name }}</td>
            <td>{% if city.is_capital %} igen {% endif %}</td>
            <td><a href="city/update/{{ city.id }}">módosít</a></td>
            <td><a href="city/delete/{{ city.id }}">töröl</a></td>
        </tr>
    {% endfor %}
</table>
 
<p>
    <a href="city/add/">Várost hozzáad</a>
</p>

A HTML részleteket nem magyarázom el, mert ez túlmutat a leírás keretein. Nagy vonalakban: két táblázatot hoztunk létre, az egyikben országok, a másikban városok vannak. Mindkét táblázat fölé írtunk címet, ill. alatta egy linket új ország ill. város hozzáadására. A táblázaton belül adott országra ill. városra vonatkozó módosítási és törlési műveleteket tudunk végrehajtani.

Ami viszont itt izgalmasabb, azok a sablon utasítások. A változó közvetlen kiírására már láttunk példát korábban, és itt is, pl. {{ country.id }}. Ebben a példában újdonság a {% … %} jelölés, ahol szokásos programozási utasításokat tudunk kiadni. Számos lehetőségünk van, melyek ismertetése túlmutat a leírás keretein; itt csak a két leggyakoribbra látunk példát: feltételkezelésre ( {% if … %} ) és a ciklusra ( {% for … %} ). Ciklust az országokon és a városokon történő végiglépkedésre használunk, míg feltételkezelést az EU-tagság és a főváros információ megjelenítésére.

Ha megnyitjuk a http://127.0.0.1:8000/geography_app/ oldalt, akkor ezt látjuk:

főoldal.png

A műveletek még nincsenek megvalósítva, de azt gondolom, hogy így is igen látványos. A következő lépésben a műveleteket valósítjuk meg.

A többi művelet

A view.py fájlba valósítsuk meg a többi műveletet is! Itt bónuszként felvettem egy plusz lekérdezést: a főoldalon megjelenítjük az EU-országok fővárosait.

geography_site/geography_app/views.py

from django.http import HttpResponse, HttpResponseRedirect
from django.template import loader
from django.urls import reverse
from geography_app.models import Country, City
 
def index(request):
    cities = City.objects.all()
    return HttpResponse(loader.get_template('index.html').render({
        'countries': Country.objects.all(),
        'cities': cities,
        'eu_capitals': [city.name for city in cities if city.is_capital and city.country.eu_member],
    }, request))
 
def add_country(request):
    return HttpResponse(loader.get_template('country.html').render({}, request))
 
def add_country_record(request):
    Country(name=request.POST.get('name'), eu_member=request.POST.get('eu_member') == 'on').save()
    return HttpResponseRedirect(reverse('index'))
 
def update_country(request, id):
    return HttpResponse(loader.get_template('country.html').render({'country': Country.objects.get(id=id)}, request))
 
def update_country_record(request, id):
    country = Country.objects.get(id=id)
    country.name = request.POST.get('name')
    country.eu_member = request.POST.get('eu_member') == 'on'
    country.save()
    return HttpResponseRedirect(reverse('index'))
 
def delete_country(request, id):
    Country.objects.get(id=id).delete()
    return HttpResponseRedirect(reverse('index'))
 
def add_city(request):
    return HttpResponse(loader.get_template('city.html').render({
        'countries': Country.objects.all(),
    }, request))
 
def add_city_record(request):
    City(
        name=request.POST.get('name'),
        is_capital=request.POST.get('is_capital') == 'on',
        country=Country.objects.get(id=request.POST.get('country_id'))
    ).save()
    return HttpResponseRedirect(reverse('index'))
 
def update_city(request, id):
    return HttpResponse(loader.get_template('city.html').render({
        'city': City.objects.get(id=id),
        'countries': Country.objects.all(),
    }, request))
 
def update_city_record(request, id):
    city = City.objects.get(id=id)
    city.name = request.POST.get('name')
    city.is_capital = request.POST.get('is_capital') == 'on'
    city.country = Country.objects.get(id=request.POST.get('country_id'))
    city.save()
    return HttpResponseRedirect(reverse('index'))
 
def delete_city(request, id):
    City.objects.get(id=id).delete()
    return HttpResponseRedirect(reverse('index'))

Néhány részlet további magyarázata:

  • Az index-be bekerült az EU-s fővárosok lekérdezése. Webalkalmazás nélkül ez egy összetett SQL SELECT utasítás lenne; így viszont Python lista származtatásra (list comprehension) egyszerűsödött.
  • Hozzáadás: az add_country tölti be az oldalt, és az add_country_record meg azt, ami feldolgozza a kitöltött formanyomtatványt. Itt meghívjuk a save() függvényt, ami a háttérben egy megfelelő INSERT SQL utasítást hajt végre.
  • Ugyanígy az add_city és add_city_record.
  • return HttpResponseRedirect(reverse('index')): ez egy új utasítás: átirányít a főoldalra.
  • request.POST.get('eu_member') == 'on': az EU-tagság (ill. a főváros) egy jelölőnégyzet. Azt így lehet kiolvasni. Igazából ha be van kattintva, akkor az érték az, hogy on, egyébként hiányzik. Emiatt nem lenne jó a request.POST[eu_member] hivatkozás.
  • Módosítás: az update_country tölti be az oldalt. Itt megadjuk azt is, amit módosítani szeretnénk: az azonosítója segítségével kérdezzük le: Country.objects.get(id=id). A háttérben egy megfelelő SQL SELECT utasítás hajtódik végre. Ez ugyanarra hivatkozik mint az add, mivel ugyanazokat a mezőket kell kitölteni; a különbség annyi, hogy az add esetén egy teljesen üres formanyomtatványt lát a felhasználó, míg az update esetén ki van töltve az adatokkal. A feldolgozás is eltér: az add esetén új rekord kerül az adatbázisba, míg az update esetén módosul. Az update esetében ugyanúgy a save() függvényt használjuk; a rendszer rájön arra, hogy ez az azonosítójú rekord már létezik az adatbázisban, és egy megfelelő UPDATE SQL utasítást hajt végre.
  • Ugyanígy működik az add_city és az update_city is; ez utóbbi esetekben viszont megkapja az országok listáját, hogy abból tudjon legördülő menüt készíteni.
  • Törlésnél lekérdezzük az adott azonosítójú objektumot és kitöröljük. Mindez végrehajtja a háttérben a megfelelő DELETE SQL parancsot.

Összefoglalásként fontosnak tartom kihangsúlyozni, hogy lényegében itt található az alkalmazásunk üzleti logikája, ami gyakorlatilag tele van tűzdelve adatbázis műveletekkel, mégis, egyetlen adatbázis kapcsolattal sort vagy SQL utasítást nem látunk. Ebben rejlik a Django (és a többi hasonló webes keretrendszer) erőssége: az üzleti logikára kell koncentrálnunk, és a technikai részleteket megoldja a keretrendszer maga.

Az index oldal már tartalmazza a főváros megjelenítést is:

geography_site/geography_app/templates/index.html

<h1>Országok</h1>
 
<table border="1">
    <tr>
        <th>Név</th>
        <th>EU tag</th>
        <th>Módosítás</th>
        <th>Törlés</th>
    </tr>
    {% for country in countries %}
        <tr>
            <td>{{ country.name }}</td>
            <td>{% if country.eu_member %} igen {% else %} nem {% endif %}</td>
            <td><a href="country/update/{{ country.id }}">módosít</a></td>
            <td><a href="country/delete/{{ country.id }}">töröl</a></td>
        </tr>
    {% endfor %}
</table>
 
<p>
    <a href="country/add/">Országot hozzáad</a>
</p>
 
<h1>Városok</h1>
 
<table border="1">
    <tr>
        <th>Név</th>
        <th>Ország</th>
        <th>Főváros</th>
        <th>Módosítás</th>
        <th>Törlés</th>
    </tr>
    {% for city in cities %}
        <tr>
            <td>{{ city.name }}</td>
            <td>{{ city.country.name }}</td>
            <td>{% if city.is_capital %} igen {% endif %}</td>
            <td><a href="city/update/{{ city.id }}">módosít</a></td>
            <td><a href="city/delete/{{ city.id }}">töröl</a></td>
        </tr>
    {% endfor %}
</table>
 
<p>
    <a href="city/add/">Várost hozzáad</a>
</p>
 
<h1>EU országok fővárosai:</h1>
<ul>
    {% for eu_capital in eu_capitals %}
    <li>{{eu_capital}}</li>
    {% endfor %}
</ul>

A fennmaradó két HTML oldal:

geography_site/geography_app/templates/country.html

<h1>Országot {% if country %} módosít {% else %} hozzáad {% endif %}</h1>
 
<form action= {% if country %} "updaterecord/{{ country.id }}" {% else %} "addrecord" {% endif %} method="post">
    {% csrf_token %}
    Név: <input name="name" {% if country %} value="{{ country.name }}" {% endif %}>
    <br>
    EU-tag: <input type="checkbox" name="eu_member" {% if country and country.eu_member %} checked {% endif %}>
    <br>
 
    <input type="submit" value={% if country %} "Módosít" {% else %} "Hozzáad" {% endif %}>
</form>

geography_site/geography_app/templates/city.html

<h1>Várost {% if city %} módosít {% else %} hozzáad {% endif %}</h1>
 
<form action= {% if city %} "updaterecord/{{ city.id }}" {% else %} "addrecord" {% endif %} method="post">
    {% csrf_token %}
    Név: <input name="name" {% if city %} value="{{ city.name }}" {% endif %}>
    <br>
    Főváros: <input type="checkbox" name="is_capital" {% if city and city.is_capital %} checked {% endif %}>
    <br>
    Ország:
    <select name="country_id">
    {% for country in countries %}
        <option value="{{ country.id }}"{% if city and city.country_id == country.id %} selected{% endif %}>{{ country.name }}</option>
    {% endfor %}
    </select>
    <br>
 
    <input type="submit" value={% if city %} "Módosít" {% else %} "Hozzáad" {% endif %}>
</form>

A két oldal hasonló: egy-egy formanyomtatványt tartalmaz, amit egy HTML POST hívással küld el a szervernek. Az if és for direktívákat már láttuk. A feltételkezelést itt elsősorban arra használjuk, hogy különbséget tegyük a hozzáadás és a töltés között. Módosításkor meg van adva az, amit módosítani szeretnénk, hozzáadáskor nincs.

A városok listája egy legördülő menüvel több, ami az országok listáját tartalmazza. Itt használjuk a for ciklust. Fontos technikai részlet, hogy a külső kulcsnak a generált alakja: hivatkozás_id, jelen esetben country_id. A módosításnál ennek segítségével választjuk ki a megfelelő országot.

A {% csrf_token %} egy generált token, és a cross-site scripting ellen véd. Funkcionális szerepe nincs.

Ha most futtatjuk a programot, akkor elvileg mindegyik műveletet végre tudjuk hajtani. Érdemes kipróbálni egy olyan ország törlését, amihez már vettünk fel városokat: a városok is eltűnnek.

Természetesen nagyon sokféleképpen lehetne ezt továbbfejleszteni: pl. törlés esetén kérdezzen vissza, legyen szebb a felület, lehessen rendezni a táblázatban stb. Mivel az oldal célja a Django alapjainak az ismertetése volt, az alkalmazás ilyen jellegű továbbfejlesztésétől eltekintek.

A teljes program innen letölthető: geography_site.zip.

Zárszó

Létrehoztunk egy egész pofás kis programot, melyben nagyrészt az üzleti logikára kellett fókuszálni, és ezzel az anyag végére értünk. Viszont ez csak hab a tortán; a Django-nak számos olyan része van, amit még nem is említettünk: az adminisztrációs felület, az autentikációs modul, üzemeltetési részletek stb. Azt gondolom, hogy ízelítőnek ennyi elegendő, és szükség esetén akinek kell, majd jobban elmélyed a témában, más forrásokat felhasználva.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License