Wiki 15: Django Sehemu ya 1 — Misingi ya Django
Mwezi 4 | Lengo kuu: Mwanafunzi aanzishe mradi wa Django, aelewe muundo wake (settings, URLs, views, templates, static), atengeneze models na migrations, atumie Django admin na ORM — na mwishoni awe na blog ndogo inayoonyesha posts kutoka database.
Maandalizi ya Mwalimu (soma kabla ya wiki kuanza)
- Soma/fanya kwanza: Tutorial rasmi ya Django sehemu 1–3 na 7:
- Kisha: Jenga mradi wa wiki hii (
wiki15_django) wote mwenyewe kwa kufuata "Andika code hii live" ya Kipindi 1–3. Usiende darasani bila kuwa umeuendesha. - Toleo: Tunatumia Django 5.2 (LTS — inasaidiwa kwa muda mrefu). Inahitaji Python 3.10+.
-
Sakinisha:
mkdir wiki15_django cd wiki15_django python -m venv venv .\venv\Scripts\Activate.ps1 pip install "Django>=5.2,<6.0" python -m django --version
-
VS Code: Pendekeza extension ya "Django" (by Baptiste Darthenay) — inapaka rangi templates za
{% %}na{{ }}. -
Kumbuka kuu: Django ina "faili nyingi" mwanzoni. Mwanafunzi anaweza kuchanganyikiwa. Chora ramani (Kipindi 1) na irudie kila kipindi:
Browser -> urls.py -> views.py -> (models.py <-> database) -> template.html -> Browser
Malengo ya Wiki (mwisho wa wiki mwanafunzi ataweza...)
- Kueleza tofauti ya Django na FastAPI, na lini kutumia kila moja.
- Kuanzisha mradi (
startproject) na app (startapp), na kueleza kazi ya kila faili kuu. - Kubadilisha
settings.py(INSTALLED_APPS, LANGUAGE_CODE, TIME_ZONE, TEMPLATES). - Kuunganisha URL na view (function-based), pamoja na URL zenye vigezo (
<int:pk>). - Kutumia templates:
{{ variable }},{% if %},{% for %}, filters,{% url %}, na template inheritance (base.html). - Kuweka static files (CSS) kwa
{% load static %}. - Kuandika models, kuendesha
makemigrationsnamigrate. - Kutumia Django admin (
createsuperuser,admin.site.register,ModelAdmin). - Kuandika ORM queries:
create,all,filter,get,exclude,order_by,count,__icontains. - Kuonyesha posts kutoka database kwenye ukurasa wa orodha na ukurasa wa post moja.
Kipindi 1 — Django ni nini? Mradi, App, URLs na Views
Mtiririko wa kipindi
- Marudio ya kazi ya nyumbani ya Wiki 14 (Msaidizi wa Mapishi)
- Django vs FastAPI; ramani ya ombi (request) kwenye Django
- Kusakinisha,
startproject,runserver, muundo wa folda settings.py— sehemu muhimustartapp, view ya kwanza, URLs, URL zenye vigezo- Zoezi darasani
- Q&A + kazi ya nyumbani
Elezo kwa mwalimu (soma kwanza wewe)
Django ni nini? Ni web framework ya Python yenye "betri zimejumuishwa" (batteries included): inakuja na ORM (database), mfumo wa templates (HTML), admin panel iliyo tayari, authentication (login), forms, na ulinzi wa usalama (CSRF, SQL injection, XSS). Instagram, Pinterest na Mozilla wameitumia.
Django vs FastAPI:
| FastAPI (tuliyojifunza Mwezi 3) | Django (Wiki 15–16) | |
|---|---|---|
| Inarudisha nini kwa kawaida | JSON (API kwa apps/frontend nyingine) | Kurasa kamili za HTML (website) — inaweza pia JSON (kwa Django REST Framework) |
| Database | Unachagua (tulitumia SQLModel) | ORM yake imejengwa ndani |
| Admin panel | Hakuna | Ipo tayari! |
| Login/users | Tuliandika JWT wenyewe | Imejengwa ndani |
| Ukubwa | Ndogo, unaongeza unachohitaji | Kubwa, kila kitu kipo |
| Inafaa kwa | APIs za haraka, ML/AI endpoints, microservices | Websites kamili: blogs, maduka, mifumo ya shule/hospitali |
Mradi (project) vs App:
- Project = website nzima (mipangilio ya jumla). Tunaita
config. - App = sehemu moja yenye kazi maalum ndani ya project (mf.
blog,duka,accounts). Project moja inaweza kuwa na apps nyingi.
Ramani ya ombi (request) — MVT (Model-View-Template):
- Mtumiaji anafungua
http://127.0.0.1:8000/blog/. - Django inaangalia
config/urls.py→ inaonablog/inaelekezwa kwablog/urls.py. blog/urls.pyinaonyesha ni view ipi ya kuita.- View (function kwenye
views.py) inachukua data kutoka Model (database) kama inahitajika. - View inatuma data kwa Template (HTML) na kurudisha ukurasa kwa browser.
Django inaita "MVT": Model (data), View (mantiki/logic), Template (muonekano). Ni sawa na "MVC" ya frameworks nyingine.
Faili muhimu baada ya startproject:
manage.py— "rimoti" ya mradi:runserver,migrate,createsuperuser,shell...config/settings.py— mipangilio yote.config/urls.py— URL kuu (mlango wa kwanza).config/wsgi.py,config/asgi.py— kwa deployment (hatugusi sasa).
Faili za app (startapp blog): views.py, models.py, admin.py, apps.py, tests.py, migrations/. Hakuna urls.py — tunaitengeneza wenyewe.
runserver ni server ya maendeleo (development) tu — haifai kwa production. Inajianzisha upya ukibadilisha code (kama --reload ya uvicorn).
Sema hivi kwa mwanafunzi
"FastAPI ni kama kibanda cha chipsi — kidogo, chepesi, unaongeza unachohitaji. Django ni kama supermarket kubwa — kila kitu kipo ndani tayari: mlinzi (security), ofisi ya meneja (admin), stoo (database), na kaunta (templates)."
"Project ni shule nzima. Apps ni idara — idara ya hisabati, maktaba, kantini. Kila idara ina kazi yake, lakini zote ziko chini ya shule moja yenye sheria (settings) moja."
"
urls.pyni kama mapokezi (reception) ya hospitali: 'Unataka meno? Nenda chumba 3.' View ni daktari wa chumba hicho. Template ni ripoti anayokupa mkononi."
Andika code hii live (share screen)
Hatua 1: Anzisha project
cd wiki15_django .\venv\Scripts\Activate.ps1 django-admin startproject config .
Angalia nukta
.mwishoni! Inamaanisha "tengeneza hapa hapa" — bila hiyo unapata folda ndani ya folda (config/config/) inayochanganya.
Muundo:
wiki15_django/
├── venv/
├── manage.py
└── config/
├── __init__.py
├── settings.py
├── urls.py
├── asgi.py
└── wsgi.py
python manage.py runserver
Fungua http://127.0.0.1:8000 → ukurasa wa roketi "The install worked successfully!" 🚀
Terminal itaonyesha onyo "You have 18 unapplied migration(s)" — sema: "Tutalishughulikia Kipindi 3. Ni la kawaida."
Simamisha server:Ctrl + C.
Hatua 2: Settings — fungua config/settings.py na badilisha:
LANGUAGE_CODE = "sw" # Kiswahili (admin itakuwa sehemu kwa Kiswahili) TIME_ZONE = "Africa/Dar_es_Salaam"
Eleza sehemu nyingine bila kubadilisha:
SECRET_KEY— siri ya mradi (usiicommit kwenye mradi halisi — tutaona Wiki 16).DEBUG = True— inaonyesha errors kwa undani. Lazima iweFalsekwenye production.INSTALLED_APPS— orodha ya apps zinazotumika.DATABASES— default ni SQLite (db.sqlite3) — kama tulivyotumia na SQLModel.
Hatua 3: Tengeneza app
python manage.py startapp blog
Isajili kwenye config/settings.py:
INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "blog", # <- app yetu ]
Hatua 4: View ya kwanza — blog/views.py
from django.http import HttpResponse def nyumbani(request): return HttpResponse("<h1>Karibu kwenye Blog yetu!</h1><p>Imetengenezwa kwa Django.</p>") def kuhusu(request): return HttpResponse("<h1>Kuhusu sisi</h1><p>Sisi ni wanafunzi wa Python.</p>") def salamu(request, jina): return HttpResponse(f"<h1>Habari {jina}!</h1>") def post_namba(request, pk): return HttpResponse(f"<h1>Hii ni post namba {pk}</h1><p>Aina ya pk: {type(pk).__name__}</p>")
Linganisha na FastAPI:
requestni kama object ya ombi; view lazima irudisheHttpResponse(au kitu kinachoirudisha).
Hatua 5: URLs za app — tengeneza faili jipya blog/urls.py
from django.urls import path from . import views app_name = "blog" # "namespace" — inasaidia kutaja URLs kwa jina: "blog:nyumbani" urlpatterns = [ path("", views.nyumbani, name="nyumbani"), path("kuhusu/", views.kuhusu, name="kuhusu"), path("salamu/<str:jina>/", views.salamu, name="salamu"), path("post/<int:pk>/", views.post_namba, name="post_namba"), ]
Hatua 6: Unganisha kwenye URL kuu — config/urls.py
from django.contrib import admin from django.urls import include, path urlpatterns = [ path("admin/", admin.site.urls), path("", include("blog.urls")), # blog itakuwa ukurasa wa mwanzo ]
python manage.py runserver
Jaribu kwenye browser:
- http://127.0.0.1:8000/ → "Karibu kwenye Blog yetu!"
- http://127.0.0.1:8000/kuhusu/
- http://127.0.0.1:8000/salamu/Amina/ → "Habari Amina!"
- http://127.0.0.1:8000/post/5/ → "post namba 5 ... Aina ya pk: int"
- http://127.0.0.1:8000/post/abc/ → Page not found (404) — kwa sababu
<int:pk>inakubali namba tu. Onyesha ukurasa wa njano wa DEBUG unaoorodhesha URL zilizopo — ni rafiki mzuri wa kutafuta makosa.
Hatua 7: Git
.gitignore:
venv/ __pycache__/ db.sqlite3 .env
git init git add . git commit -m "Wiki 15: mradi wa Django na app ya blog"
Maswali ya kumuuliza mwanafunzi
- Swali: Ungetumia Django au FastAPI kutengeneza mfumo wa matokeo ya shule wenye kurasa za walimu na admin? Jibu: Django — una kurasa za HTML, admin, na login vimejengwa ndani.
- Swali: Na API ya kutabiri bei (Wiki 13)? Jibu: FastAPI — ndogo, ya haraka, inarudisha JSON.
- Swali: Tofauti ya project na app? Jibu: Project ni website nzima na mipangilio; app ni sehemu moja yenye kazi maalum.
- Swali: Kwa nini
/post/abc/inatoa 404? Jibu:<int:pk>inakubali namba tu; "abc" hailingani na pattern yoyote. - Swali: Kazi ya
include("blog.urls")ni nini? Jibu: Inakabidhi URLs zinazoanza na njia hiyo kwa faili la URLs la app ya blog.
Makosa ya kawaida
| Kosa | Ujumbe | Suluhisho |
|---|---|---|
Kusahau . kwenye startproject |
Folda config/config/ |
Futa folda, anza tena kwa django-admin startproject config . |
django-admin haijulikani |
django-admin : The term 'django-admin' is not recognized |
Washa venv; au tumia python -m django startproject config . |
| Kusahau kusajili app | Templates/models za app hazionekani; TemplateDoesNotExist |
Ongeza "blog" kwenye INSTALLED_APPS |
Kusahau / mwishoni mwa path |
Inafanya kazi ila URL inachanganya | Kila path iishe na /: "kuhusu/" |
Comma iliyosahaulika kwenye urlpatterns |
SyntaxError au TypeError: 'tuple' object is not callable |
Weka , mwishoni mwa kila path(...) |
| View hairudishi kitu | ValueError: The view blog.views.kuhusu didn't return an HttpResponse object. It returned None instead. |
Ongeza return HttpResponse(...) |
| Port inatumika | Error: That port is already in use. |
Funga server nyingine au python manage.py runserver 8001 |
Kuendesha manage.py kutoka folda tofauti |
can't open file 'manage.py' |
cd kwenda folda yenye manage.py |
Zoezi darasani
Zoezi 1.1: Ongeza view mawasiliano kwenye URL /mawasiliano/ inayoonyesha namba ya simu na email ya blog.
Zoezi 1.2: Ongeza URL /jumlisha/<int:a>/<int:b>/ inayoonyesha jumla ya namba mbili. Mf. /jumlisha/7/5/ → "7 + 5 = 12".
Zoezi 1.3 (changamoto): Ongeza URL /mwaka/<int:mwaka>/ inayoonyesha "Mwaka 2027 una siku 365" (au 366 kwa mwaka mrefu — leap year). Kidokezo: calendar.isleap(mwaka).
Jibu
# blog/views.py (ongeza) import calendar from django.http import HttpResponse def mawasiliano(request): return HttpResponse("<h1>Mawasiliano</h1><p>Simu: 0712 345 678</p><p>Email: blog@mfano.co.tz</p>") def jumlisha(request, a, b): return HttpResponse(f"<h1>{a} + {b} = {a + b}</h1>") def mwaka(request, mwaka): siku = 366 if calendar.isleap(mwaka) else 365 return HttpResponse(f"<h1>Mwaka {mwaka} una siku {siku}</h1>")
# blog/urls.py (ongeza kwenye urlpatterns) path("mawasiliano/", views.mawasiliano, name="mawasiliano"), path("jumlisha/<int:a>/<int:b>/", views.jumlisha, name="jumlisha"), path("mwaka/<int:mwaka>/", views.mwaka, name="mwaka"),
Kipindi 2 — Templates, Inheritance na Static files
Mtiririko wa kipindi
- Marudio ya kazi ya nyumbani (Kipindi 1)
- Kwa nini templates? Tatizo la HTML ndani ya Python
render(), context,{{ }},{% if %},{% for %}, filters- Template inheritance:
base.html,{% block %},{% extends %},{% url %} - Static files: CSS kwa
{% load static %} - Zoezi darasani
- Q&A + kazi ya nyumbani
Elezo kwa mwalimu (soma kwanza wewe)
Tatizo: Kuandika HTML ndani ya HttpResponse("...") ni kugumu kwa kurasa ndefu. Template ni faili la HTML lenye "nafasi" za kujazwa na data kutoka view.
render(request, "jina_la_template.html", context) — inasoma template, inaijaza kwa context (dict), na kurudisha HttpResponse.
Mahali pa templates: Django inatafuta folda templates/ ndani ya kila app iliyosajiliwa (APP_DIRS: True kwenye settings). Tunaweka blog/templates/blog/ — ndiyo, blog mara mbili! Hii inaitwa namespacing: kama apps mbili zote zina index.html, Django isichanganye. Kwa hiyo jina tunalotumia ni "blog/index.html".
Lugha ya template (Django Template Language — DTL):
{{ jina }}— onyesha thamani ya variable.{{ post.kichwa }}— attribute au key ya dict.{% ... %}— amri/mantiki:{% if %}...{% elif %}...{% else %}...{% endif %},{% for x in list %}...{% empty %}...{% endfor %}.- Filters —
{{ jina|upper }},{{ list|length }},{{ tarehe|date:"d/m/Y" }},{{ maandishi|truncatewords:20 }},{{ thamani|default:"Hakuna" }}. {# comment #}— maoni.- Ndani ya
{% for %}:forloop.counter(1, 2, 3...). - Usalama: Django inafanya "auto-escape" — kama mtumiaji ataandika
<script>, inaonyeshwa kama maandishi, haiendeshwi. Hii inazuia mashambulizi ya XSS.
Template inheritance (urithi): Kurasa zote zina header, menyu na footer zile zile. Badala ya kunakili, tunaandika base.html yenye "mashimo" ({% block content %}{% endblock %}), kisha kila ukurasa unasema {% extends "blog/base.html" %} na kujaza mashimo tu. {% extends %} lazima iwe mstari wa kwanza wa template.
{% url "blog:kuhusu" %} — tengeneza link kwa jina la URL badala ya kuandika /kuhusu/ kwa mkono. Ukibadilisha URL kwenye urls.py, links zote zinabadilika zenyewe. Kwa vigezo: {% url "blog:post_detail" post.pk %}.
Static files = CSS, JavaScript, picha zisizobadilika. Mahali: blog/static/blog/style.css (namespacing ile ile). Kwenye template: {% load static %} kisha <link rel="stylesheet" href="{% static 'blog/style.css' %}">. Wakati wa maendeleo (DEBUG=True), runserver inazitoa yenyewe.
Sema hivi kwa mwanafunzi
"Template ni kama fomu ya cheti cha kuzaliwa iliyochapishwa: maandishi mengi yapo tayari, kuna nafasi tupu za 'Jina: ____', 'Tarehe: ____'. View inajaza nafasi hizo."
"
base.htmlni kama ukuta, paa na mlango wa nyumba. Kila chumba (ukurasa) kinapamba ndani tu — hakijengi ukuta upya.""
{{ }}ni kuonyesha,{% %}ni kufanya. Mabano mawili ya kuonyesha, asilimia ya kufanya kazi."
Andika code hii live (share screen)
Hatua 1: Muundo wa folda (tengeneza kwenye VS Code)
blog/
├── templates/
│ └── blog/
│ ├── base.html
│ ├── nyumbani.html
│ └── kuhusu.html
└── static/
└── blog/
└── style.css
Hatua 2: blog/templates/blog/base.html
{% load static %}
<!DOCTYPE html>
<html lang="sw">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Blog Yetu{% endblock %}</title>
<link rel="stylesheet" href="{% static 'blog/style.css' %}">
</head>
<body>
<header>
<h1><a href="{% url 'blog:nyumbani' %}">📝 Blog Yetu</a></h1>
<nav>
<a href="{% url 'blog:nyumbani' %}">Nyumbani</a>
<a href="{% url 'blog:kuhusu' %}">Kuhusu</a>
</nav>
</header>
<main>
{% block content %}
{% endblock %}
</main>
<footer>
<p>© 2027 Blog Yetu — Imetengenezwa kwa Django</p>
</footer>
</body>
</html>
Hatua 3: blog/templates/blog/nyumbani.html
{% extends "blog/base.html" %}
{% block title %}Nyumbani | Blog Yetu{% endblock %}
{% block content %}
<h2>Karibu, {{ mgeni|title }}!</h2>
<p>Leo ni {{ leo|date:"l, d F Y" }}.</p>
{% if posts %}
<p>Tuna posts {{ posts|length }}:</p>
<ol>
{% for post in posts %}
<li>
<strong>{{ post.kichwa }}</strong> — {{ post.mwandishi }}
<br><small>{{ post.maudhui|truncatewords:8 }}</small>
</li>
{% endfor %}
</ol>
{% else %}
<p>Hakuna posts bado.</p>
{% endif %}
<h3>Mada tunazopenda</h3>
<ul>
{% for mada in mada_list %}
<li>{{ forloop.counter }}. {{ mada }}</li>
{% empty %}
<li>Hakuna mada.</li>
{% endfor %}
</ul>
{% endblock %}
Hatua 4: blog/templates/blog/kuhusu.html
{% extends "blog/base.html" %}
{% block title %}Kuhusu | Blog Yetu{% endblock %}
{% block content %}
<h2>Kuhusu sisi</h2>
<p>Sisi ni wanafunzi wa kozi ya Python ya wiki 16.</p>
<p>Mwalimu: {{ mwalimu|default:"Hajatajwa" }}</p>
{% endblock %}
Hatua 5: Views zitumie render — blog/views.py
from datetime import date from django.http import HttpResponse from django.shortcuts import render # Data ya muda (Ijumaa tutaitoa kwenye database) POSTS = [ {"kichwa": "Kujifunza Python", "mwandishi": "Asha", "maudhui": "Python ni lugha rahisi na yenye nguvu inayotumika kila mahali duniani leo."}, {"kichwa": "Machine Learning kwa Kiswahili", "mwandishi": "Juma", "maudhui": "Wiki 13 tulijifunza kutabiri bei za viwanja kwa scikit-learn na ilikuwa poa sana."}, {"kichwa": "Django ni nini?", "mwandishi": "Neema", "maudhui": "Django ni framework yenye betri zimejumuishwa kwa kutengeneza websites kamili."}, ] def nyumbani(request): context = { "mgeni": "mwanafunzi hodari", "leo": date.today(), "posts": POSTS, "mada_list": ["Python", "FastAPI", "AI", "Django"], } return render(request, "blog/nyumbani.html", context) def kuhusu(request): return render(request, "blog/kuhusu.html", {"mwalimu": "Jina la Mwalimu"}) def salamu(request, jina): return HttpResponse(f"<h1>Habari {jina}!</h1>") def post_namba(request, pk): return HttpResponse(f"<h1>Hii ni post namba {pk}</h1>")
Badilisha "Jina la Mwalimu" kuwa jina lako.
Hatua 6: CSS — blog/static/blog/style.css
body { font-family: "Segoe UI", Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 0 16px; background: #f7f7f2; color: #222; } header { background: #1b5e20; color: white; padding: 12px 16px; border-radius: 0 0 8px 8px; } header a { color: white; text-decoration: none; margin-right: 12px; } header h1 { margin: 0 0 8px; } main { min-height: 60vh; } footer { text-align: center; color: #777; border-top: 1px solid #ddd; margin-top: 32px; }
python manage.py runserver
Fungua http://127.0.0.1:8000 — ukurasa wenye header ya kijani, posts 3, na mada 4. Bofya "Kuhusu" — header na footer zile zile (inheritance!).
CSS haionekani? Bonyeza
Ctrl + F5(browser inahifadhi CSS ya zamani), na hakikisha umeanzisha upya server baada ya kutengeneza folda mpya yastatic.
Hatua 7: Onyesha auto-escape (usalama)
Kwenye nyumbani badilisha kwa muda: "mgeni": "<script>alert('hack')</script>" → ukurasa unaonyesha maandishi, hakuna alert. Sema: "Django inatulinda bila sisi kufanya kitu." Rudisha kama ilivyokuwa.
Maswali ya kumuuliza mwanafunzi
- Swali: Tofauti ya
{{ }}na{% %}? Jibu:{{ }}inaonyesha thamani;{% %}ni amri/mantiki (if, for, extends, url, load). - Swali: Kwa nini
blog/templates/blog/na siblog/templates/? Jibu: Namespacing — kuzuia mgongano wa majina ya templates kati ya apps. - Swali:
{% empty %}inafanya nini? Jibu: Inaonyesha kitu mbadala kama list yaforni tupu. - Swali: Faida ya
{% url 'blog:kuhusu' %}badala ya/kuhusu/? Jibu: URL ikibadilishwa kwenyeurls.py, links zote zinasasishwa zenyewe. - Swali: Kwa nini
<script>ya mtumiaji haiendeshwi? Jibu: Django inafanya auto-escape ya{{ }}, inazuia XSS.
Makosa ya kawaida
| Kosa | Ujumbe | Suluhisho |
|---|---|---|
| Template iko mahali pabaya / jina limekosewa | TemplateDoesNotExist at / blog/nyumbani.html |
Angalia njia: blog/templates/blog/nyumbani.html; app imesajiliwa? Anzisha upya server |
{% extends %} si mstari wa kwanza |
TemplateSyntaxError: <ExtendsNode: extends "blog/base.html"> must be the first tag in the template. |
Weka {% extends %} juu kabisa |
Kusahau {% load static %} |
TemplateSyntaxError: Invalid block tag on line 7: 'static'. Did you forget to register or load this tag? |
Weka {% load static %} juu ya template inayotumia {% static %} (hata kama base imeiload) |
Kusahau {% endif %} / {% endfor %} |
TemplateSyntaxError: Unclosed tag on line 9: 'if' |
Funga kila block |
| Jina la URL halipo | NoReverseMatch: Reverse for 'kuhusu' not found |
Tumia namespace: 'blog:kuhusu'; angalia name= na app_name |
| Variable imekosewa jina | (Hakuna error!) Nafasi tupu tu | Django inaonyesha tupu kimya kimya — angalia tahajia ya key kwenye context |
| CSS haibadiliki | (hakuna error) | Ctrl+F5; anzisha upya runserver |
Zoezi darasani
Zoezi 2.1: Badilisha view ya mawasiliano (Kipindi 1) itumie template blog/mawasiliano.html inayorithi base.html, na iongezwe kwenye menyu.
Zoezi 2.2: Tengeneza ukurasa /bei/ unaoonyesha jedwali (<table>) la bidhaa kutoka list ya dicts: [{"jina": "Sukari", "bei": 3200}, ...]. Bidhaa yenye bei > 3000 ionyeshwe kwa rangi nyekundu (tumia {% if %}). Onyesha bei kwa comma (kidokezo: ongeza "django.contrib.humanize" kwenye INSTALLED_APPS, {% load humanize %}, kisha {{ bei|intcomma }}).
Zoezi 2.3: Ongeza kwenye base.html block mpya {% block hero %}{% endblock %} juu ya main, na uijaze kwenye ukurasa wa nyumbani tu kwa ujumbe wa kukaribisha.
Jibu
<!-- blog/templates/blog/mawasiliano.html --> {% extends "blog/base.html" %} {% block title %}Mawasiliano{% endblock %} {% block content %} <h2>Mawasiliano</h2> <p>Simu: {{ simu }}</p> <p>Email: {{ email }}</p> {% endblock %}
# blog/views.py def mawasiliano(request): return render(request, "blog/mawasiliano.html", {"simu": "0712 345 678", "email": "blog@mfano.co.tz"}) BIDHAA = [ {"jina": "Sukari", "bei": 3200}, {"jina": "Unga", "bei": 1800}, {"jina": "Mafuta", "bei": 4500}, {"jina": "Chumvi", "bei": 600}, ] def bei(request): return render(request, "blog/bei.html", {"bidhaa": BIDHAA})
# blog/urls.py path("bei/", views.bei, name="bei"),
# config/settings.py -> INSTALLED_APPS ongeza: "django.contrib.humanize",
<!-- blog/templates/blog/bei.html --> {% extends "blog/base.html" %} {% load humanize %} {% block title %}Bei za bidhaa{% endblock %} {% block content %} <h2>Bei za bidhaa</h2> <table border="1" cellpadding="6"> <tr><th>#</th><th>Bidhaa</th><th>Bei (TSh)</th></tr> {% for b in bidhaa %} <tr> <td>{{ forloop.counter }}</td> <td>{{ b.jina }}</td> <td {% if b.bei > 3000 %}style="color: red;"{% endif %}>{{ b.bei|intcomma }}</td> </tr> {% endfor %} </table> {% endblock %}
<!-- 2.3: kwenye base.html, juu ya <main> --> {% block hero %}{% endblock %} <!-- kwenye nyumbani.html, baada ya block title --> {% block hero %} <div style="background:#c8e6c9; padding:16px; border-radius:8px; margin:12px 0;"> <strong>Karibu!</strong> Soma makala mpya kila wiki. </div> {% endblock %}
Kwenye menyu ya base.html: <a href="{% url 'blog:mawasiliano' %}">Mawasiliano</a> na <a href="{% url 'blog:bei' %}">Bei</a>.
Kipindi 3 — Models, Migrations, Admin na ORM
Mtiririko wa kipindi
- Marudio ya kazi ya nyumbani (Kipindi 2)
- Models — kuelezea jedwali la database kwa class ya Python
makemigrationsnamigrate- Django admin:
createsuperuser, kusajili models,ModelAdmin - ORM kwenye
shell: create, filter, get, order_by... - Views zitumie database: orodha ya posts + post moja (
get_object_or_404) - Zoezi darasani
- Q&A + kazi ya nyumbani ya wiki
Elezo kwa mwalimu (soma kwanza wewe)
Model = class ya Python inayowakilisha jedwali (table) la database. Kila attribute ni column; kila object ni row. Ni kama SQLModel tuliyotumia na FastAPI, lakini ya Django.
Aina za fields za kawaida:
| Field | Inahifadhi | Mfano |
|-------|-----------|-------|
| CharField(max_length=200) | maandishi mafupi | kichwa |
| TextField() | maandishi marefu | maudhui |
| IntegerField(), DecimalField(max_digits, decimal_places) | namba | bei |
| BooleanField(default=True) | kweli/si kweli | imechapishwa |
| DateTimeField(auto_now_add=True) | tarehe ya kutengenezwa (inawekwa yenyewe mara moja) | tarehe |
| DateTimeField(auto_now=True) | inasasishwa kila save | imesasishwa |
| ForeignKey(Model, on_delete=...) | uhusiano "wengi kwa mmoja" | post → mwandishi |
on_delete=models.CASCADE = mwandishi akifutwa, posts zake zinafutwa. SET_NULL (pamoja na null=True) = zinabaki bila kundi.
__str__ — jinsi object inavyoonyeshwa (kwenye admin na shell). Bila hiyo unaona "Post object (1)".
class Meta: ordering = ["-tarehe"] — mpangilio wa default (mpya kwanza; - ni descending).
Migrations — ni "historia ya mabadiliko" ya muundo wa database:
python manage.py makemigrations— Django inalinganisha models na migrations zilizopo, inaandika faili jipya (mf.blog/migrations/0001_initial.py) linaloelezea mabadiliko. Haigusi database bado.python manage.py migrate— inatekeleza migrations kwenye database halisi.Kila unapobadilisha
models.py→makemigrations→migrate. Mara ya kwanzamigratepia inatengeneza majedwali ya apps za Django (users, sessions, admin) — ndio zile "18 unapplied migrations".
User: Django ina model ya User tayari (django.contrib.auth). Kwa ForeignKey tunatumia settings.AUTH_USER_MODEL — njia inayopendekezwa.
Admin: Ukurasa wa kusimamia data ulio tayari kwenye /admin/. Unahitaji (1) superuser, (2) kusajili models kwenye admin.py. ModelAdmin inaboresha muonekano: list_display (columns), search_fields (kisanduku cha kutafuta), list_filter (vichujio pembeni).
ORM (Object-Relational Mapper) — tunaandika Python, Django inaandika SQL:
| Python (ORM) | Maana |
|------|------|
| Post.objects.all() | posts zote |
| Post.objects.filter(imechapishwa=True) | zinazolingana na sharti (QuerySet — inaweza kuwa tupu) |
| Post.objects.exclude(...) | zisizolingana |
| Post.objects.get(pk=1) | object MOJA hasa (error kama haipo au ziko nyingi) |
| .order_by("kichwa") / .order_by("-tarehe") | mpangilio |
| .count() | idadi |
| .first() | ya kwanza au None |
| filter(kichwa__icontains="python") | ina neno (bila kujali herufi kubwa/ndogo) |
| filter(tarehe__year=2027) | lookup za tarehe |
| filter(kundi__jina="Teknolojia") | kupitia uhusiano (ForeignKey) |
| Post.objects.create(...) | tengeneza na hifadhi |
| post.save() / post.delete() | hifadhi mabadiliko / futa |
QuerySet ni "lazy" — haiulizi database mpaka uitumie (loop, print, count). Unaweza kuunganisha: Post.objects.filter(...).exclude(...).order_by(...).
get_object_or_404(Post, pk=pk) — kama get, lakini badala ya kosa la server (500) inarudisha ukurasa wa 404 — sahihi kwa mtumiaji aliyeandika URL ya post isiyokuwepo.
Sema hivi kwa mwanafunzi
"Model ni kama kuchora fomu ya usajili wa wanafunzi: 'Jina — herufi 100', 'Tarehe ya kuzaliwa — tarehe'. Database ni kabati la mafaili; kila fomu iliyojazwa ni safu moja."
"makemigrations ni kuandika ramani ya ukarabati wa nyumba. migrate ni mafundi kuja kujenga kweli kufuatana na ramani."
"Admin ni ofisi ya meneja iliyo tayari — Django imekujengea bure. Kampuni nyingi hutumia hii pekee kwa wafanyakazi wa ndani."
"ORM ni mkalimani: wewe unaongea Python, database inaongea SQL. ORM inatafsiri katikati."
Andika code hii live (share screen)
Hatua 1: Models — blog/models.py
from django.conf import settings from django.db import models from django.urls import reverse class Kundi(models.Model): jina = models.CharField(max_length=50, unique=True) class Meta: verbose_name_plural = "Makundi" ordering = ["jina"] def __str__(self): return self.jina class Post(models.Model): kichwa = models.CharField(max_length=200) maudhui = models.TextField() kundi = models.ForeignKey(Kundi, on_delete=models.SET_NULL, null=True, blank=True, related_name="posts") mwandishi = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="posts") imechapishwa = models.BooleanField(default=True) tarehe = models.DateTimeField(auto_now_add=True) imesasishwa = models.DateTimeField(auto_now=True) class Meta: ordering = ["-tarehe"] def __str__(self): return self.kichwa def get_absolute_url(self): return reverse("blog:post_detail", args=[self.pk])
Hatua 2: Migrations
python manage.py makemigrations
Migrations for 'blog':
blog\migrations\0001_initial.py
+ Create model Kundi
+ Create model Post
python manage.py migrate
Operations to perform: Apply all migrations: admin, auth, blog, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK ... Applying blog.0001_initial... OK Applying sessions.0001_initial... OK
Fungua
blog/migrations/0001_initial.pyuonyeshe — "Django imeandika ramani yenyewe."
Hatua 3: Superuser na Admin
python manage.py createsuperuser
Jibu maswali (jina, email inaweza kuachwa tupu, password mara mbili — haionekani unapoandika, ni kawaida).
blog/admin.py:
from django.contrib import admin from .models import Kundi, Post @admin.register(Kundi) class KundiAdmin(admin.ModelAdmin): list_display = ["jina"] @admin.register(Post) class PostAdmin(admin.ModelAdmin): list_display = ["kichwa", "mwandishi", "kundi", "imechapishwa", "tarehe"] list_filter = ["imechapishwa", "kundi", "tarehe"] search_fields = ["kichwa", "maudhui"] list_editable = ["imechapishwa"]
python manage.py runserver
Fungua http://127.0.0.1:8000/admin/ → ingia → ongeza makundi 2 ("Teknolojia", "Maisha") na posts 3–4 (moja ikiwa imechapishwa = hapana). Onyesha kutafuta na vichujio.
Hatua 4: ORM kwenye shell
python manage.py shell
>>> from blog.models import Post, Kundi >>> from django.contrib.auth.models import User >>> Post.objects.all() <QuerySet [<Post: ...>, <Post: ...>, ...]> >>> Post.objects.count() 4 >>> mimi = User.objects.first() >>> tech = Kundi.objects.get(jina="Teknolojia") >>> p = Post.objects.create(kichwa="ORM ni rahisi", maudhui="Tunaandika Python, Django inaandika SQL.", kundi=tech, mwandishi=mimi) >>> p.pk, p.tarehe (5, datetime.datetime(2027, 2, 19, ...)) >>> Post.objects.filter(imechapishwa=True).count() 4 >>> Post.objects.filter(kichwa__icontains="python") <QuerySet [...]> >>> Post.objects.filter(kundi__jina="Teknolojia").order_by("kichwa") <QuerySet [...]> >>> tech.posts.all() # kupitia related_name <QuerySet [...]> >>> p.kichwa = "ORM ni rahisi sana" >>> p.save() >>> Post.objects.get(pk=999) Traceback (most recent call last): ... blog.models.Post.DoesNotExist: Post matching query does not exist. >>> Post.objects.filter(pk=999).first() is None True >>> exit()
Sema: "
getni kama kuita mtu kwa jina — lazima awepo mmoja tu.filterni kama kuuliza 'nani yupo hapa?' — anaweza asiwepo yeyote."
Hatua 5: Views zitumie database — blog/views.py (badilisha nyumbani, ondoa POSTS na post_namba)
from datetime import date from django.shortcuts import get_object_or_404, render from .models import Post def nyumbani(request): posts = Post.objects.filter(imechapishwa=True) swali = request.GET.get("q", "").strip() # /?q=python if swali: posts = posts.filter(kichwa__icontains=swali) context = { "mgeni": "mwanafunzi hodari", "leo": date.today(), "posts": posts, "swali": swali, "mada_list": ["Python", "FastAPI", "AI", "Django"], } return render(request, "blog/nyumbani.html", context) def post_detail(request, pk): post = get_object_or_404(Post, pk=pk, imechapishwa=True) return render(request, "blog/post_detail.html", {"post": post}) # (acha kuhusu, salamu n.k. kama zilivyo)
blog/urls.py — badilisha mstari wa post_namba:
path("post/<int:pk>/", views.post_detail, name="post_detail"),
Kwenye nyumbani.html, badilisha sehemu ya posts:
<form method="get"> <input type="text" name="q" value="{{ swali }}" placeholder="Tafuta post..."> <button type="submit">Tafuta</button> </form> {% for post in posts %} <article> <h3><a href="{% url 'blog:post_detail' post.pk %}">{{ post.kichwa }}</a></h3> <small>na {{ post.mwandishi.username }} | {{ post.tarehe|date:"d/m/Y H:i" }} {% if post.kundi %}| {{ post.kundi }}{% endif %}</small> <p>{{ post.maudhui|truncatewords:25 }}</p> </article> {% empty %} <p>Hakuna posts{% if swali %} zinazolingana na "{{ swali }}"{% endif %}.</p> {% endfor %}
Tengeneza blog/templates/blog/post_detail.html:
{% extends "blog/base.html" %}
{% block title %}{{ post.kichwa }} | Blog Yetu{% endblock %}
{% block content %}
<article>
<h2>{{ post.kichwa }}</h2>
<p><small>na {{ post.mwandishi.username }} | {{ post.tarehe|date:"d F Y, H:i" }}
{% if post.kundi %}| Kundi: {{ post.kundi }}{% endif %}</small></p>
<div>{{ post.maudhui|linebreaks }}</div>
</article>
<p><a href="{% url 'blog:nyumbani' %}">← Rudi kwenye posts zote</a></p>
{% endblock %}
Jaribu: ukurasa wa mwanzo unaonyesha posts kutoka database; bofya kichwa → post moja; /post/999/ → 404; tafuta "python". Ongeza post kwenye admin → refresh → inaonekana!
Hatua 6: Test moja ya Django — blog/tests.py
from django.contrib.auth.models import User from django.test import TestCase from django.urls import reverse from .models import Post class BlogTests(TestCase): def setUp(self): self.user = User.objects.create_user("asha", password="siri12345") self.post = Post.objects.create(kichwa="Habari Django", maudhui="Maudhui", mwandishi=self.user) def test_nyumbani_inaonyesha_post(self): r = self.client.get(reverse("blog:nyumbani")) self.assertEqual(r.status_code, 200) self.assertContains(r, "Habari Django") def test_post_detail(self): r = self.client.get(reverse("blog:post_detail", args=[self.post.pk])) self.assertContains(r, "Maudhui") def test_post_isiyokuwepo_ni_404(self): r = self.client.get(reverse("blog:post_detail", args=[999])) self.assertEqual(r.status_code, 404)
python manage.py test
Found 3 test(s). Creating test database for alias 'default'... ... Ran 3 tests in 0.05s OK
Eleza: Django inatengeneza database ya muda kwa tests, kwa hiyo data yako halisi haiguswi.
Maswali ya kumuuliza mwanafunzi
- Swali: Umeongeza field
picha_urlkwenyePost. Hatua zipi mbili zinafuata? Jibu:makemigrationskishamigrate. - Swali: Tofauti ya
get()nafilter()? Jibu:getinarudisha object moja hasa (error kama 0 au zaidi ya 1);filterinarudisha QuerySet (inaweza kuwa tupu). - Swali:
auto_now_addvsauto_now? Jibu:auto_now_addinawekwa mara moja wakati wa kutengeneza;auto_nowinasasishwa kilasave(). - Swali: Kwa nini
get_object_or_404badala yagetkwenye view? Jibu: Mtumiaji akiomba post isiyokuwepo apate 404, si kosa la server 500. - Swali:
on_delete=models.CASCADEina maana gani? Jibu: Mwandishi akifutwa, posts zake zote zinafutwa pia.
Makosa ya kawaida
| Kosa | Ujumbe | Suluhisho |
|---|---|---|
Kusahau migrate |
OperationalError: no such table: blog_post |
python manage.py makemigrations kisha migrate |
Kusahau makemigrations baada ya kubadilisha model |
OperationalError: no such column: blog_post.imesasishwa |
makemigrations + migrate |
| Kuongeza field isiyo na default kwenye jedwali lenye data | Django inauliza: It is impossible to add a non-nullable field ... Select an option: |
Chagua 1 na uweke thamani ya default, au ongeza default=... / null=True kwenye field |
get haipati |
Post.DoesNotExist: Post matching query does not exist. |
Tumia filter(...).first() au get_object_or_404 |
get inapata nyingi |
MultipleObjectsReturned: get() returned more than one Post |
Tumia filter, au sharti la kipekee (pk) |
ForeignKey bila on_delete |
TypeError: ForeignKey.__init__() missing 1 required positional argument: 'on_delete' |
Ongeza on_delete=models.CASCADE (au nyingine) |
| Model haionekani admin | (hakuna error) | Sajili kwenye admin.py; anzisha upya server |
| Umesahau password ya superuser | Huwezi kuingia | python manage.py changepassword <jina> |
Zoezi darasani
Zoezi 3.1: Ongeza model Maoni (comment): post (ForeignKey kwa Post, related_name="maoni"), jina (CharField 80), ujumbe (TextField), tarehe (auto_now_add). Fanya migrations, isajili admin, ongeza maoni 2 kwa post moja.
Zoezi 3.2: Kwenye post_detail.html, onyesha maoni yote ya post hiyo na idadi yake (post.maoni.all, post.maoni.count).
Zoezi 3.3: Kwenye shell, andika queries:
- a) Posts zote za kundi "Teknolojia" zilizochapishwa
- b) Idadi ya posts za kila mwandishi (kidokezo: loop juu ya
User.objects.all()nauser.posts.count()) - c) Post ya zamani zaidi
Jibu
# blog/models.py (ongeza chini) class Maoni(models.Model): post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="maoni") jina = models.CharField(max_length=80) ujumbe = models.TextField() tarehe = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "Maoni" ordering = ["tarehe"] def __str__(self): return f"{self.jina} kuhusu '{self.post}'"
# blog/admin.py (ongeza) from .models import Maoni @admin.register(Maoni) class MaoniAdmin(admin.ModelAdmin): list_display = ["jina", "post", "tarehe"]
python manage.py makemigrations python manage.py migrate
<!-- post_detail.html, ndani ya block content baada ya article --> <h3>Maoni ({{ post.maoni.count }})</h3> {% for m in post.maoni.all %} <div style="border-left: 3px solid #1b5e20; padding-left: 8px; margin: 8px 0;"> <strong>{{ m.jina }}</strong> <small>{{ m.tarehe|date:"d/m/Y" }}</small> <p>{{ m.ujumbe }}</p> </div> {% empty %} <p>Hakuna maoni bado. Kuwa wa kwanza!</p> {% endfor %}
# 3.3 kwenye python manage.py shell from blog.models import Post from django.contrib.auth.models import User # a Post.objects.filter(kundi__jina="Teknolojia", imechapishwa=True) # b for u in User.objects.all(): print(u.username, u.posts.count()) # c Post.objects.order_by("tarehe").first()
Kazi ya Nyumbani
(Inatakiwa: kipindi kijacho)
Mradi: "Orodha ya Mapishi" (app mpya mapishi ndani ya project ile ile)
python manage.py startapp mapishi, isajili, ielekeze kwenyepath("mapishi/", include("mapishi.urls")).- Model
Pishi:jina(CharField 100),viungo(TextField),maelekezo(TextField),muda_dakika(PositiveIntegerField),ni_rahisi(BooleanField default True),tarehe(auto_now_add).__str__irudishe jina. - Sajili kwenye admin ukiwa na
list_display,search_fieldsnalist_filter. Ongeza mapishi 4 (pilau, ugali, chapati, wali wa nazi). - Ukurasa
/mapishi/unaoorodhesha mapishi yote (yanarithiblog/base.html), na/mapishi/<pk>/wa pishi moja. - Kwenye ukurasa wa orodha, ruhusu kuchuja kwa
?rahisi=1kuonyesha mapishi rahisi tu, na onyesha muda kwa "dakika X". - Test 2: orodha inarudisha 200, pishi lisilokuwepo ni 404.
Jibu
# mapishi/models.py from django.db import models from django.urls import reverse class Pishi(models.Model): jina = models.CharField(max_length=100) viungo = models.TextField() maelekezo = models.TextField() muda_dakika = models.PositiveIntegerField() ni_rahisi = models.BooleanField(default=True) tarehe = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "Mapishi" ordering = ["jina"] def __str__(self): return self.jina def get_absolute_url(self): return reverse("mapishi:detail", args=[self.pk])
# mapishi/admin.py from django.contrib import admin from .models import Pishi @admin.register(Pishi) class PishiAdmin(admin.ModelAdmin): list_display = ["jina", "muda_dakika", "ni_rahisi", "tarehe"] search_fields = ["jina", "viungo"] list_filter = ["ni_rahisi"]
# mapishi/views.py from django.shortcuts import get_object_or_404, render from .models import Pishi def orodha(request): mapishi = Pishi.objects.all() rahisi = request.GET.get("rahisi") == "1" if rahisi: mapishi = mapishi.filter(ni_rahisi=True) return render(request, "mapishi/orodha.html", {"mapishi": mapishi, "rahisi": rahisi}) def detail(request, pk): pishi = get_object_or_404(Pishi, pk=pk) return render(request, "mapishi/detail.html", {"pishi": pishi})
# mapishi/urls.py from django.urls import path from . import views app_name = "mapishi" urlpatterns = [ path("", views.orodha, name="orodha"), path("<int:pk>/", views.detail, name="detail"), ]
# config/urls.py urlpatterns = [ path("admin/", admin.site.urls), path("mapishi/", include("mapishi.urls")), path("", include("blog.urls")), ] # na "mapishi" kwenye INSTALLED_APPS
<!-- mapishi/templates/mapishi/orodha.html --> {% extends "blog/base.html" %} {% block title %}Mapishi{% endblock %} {% block content %} <h2>Mapishi {% if rahisi %}rahisi{% endif %}</h2> <p> <a href="{% url 'mapishi:orodha' %}">Yote</a> | <a href="{% url 'mapishi:orodha' %}?rahisi=1">Rahisi tu</a> </p> <ul> {% for p in mapishi %} <li><a href="{{ p.get_absolute_url }}">{{ p.jina }}</a> — dakika {{ p.muda_dakika }} {% if p.ni_rahisi %}✅{% endif %}</li> {% empty %} <li>Hakuna mapishi.</li> {% endfor %} </ul> {% endblock %}
<!-- mapishi/templates/mapishi/detail.html --> {% extends "blog/base.html" %} {% block title %}{{ pishi.jina }}{% endblock %} {% block content %} <h2>{{ pishi.jina }}</h2> <p>Muda: dakika {{ pishi.muda_dakika }} | {% if pishi.ni_rahisi %}Rahisi{% else %}Inahitaji ujuzi{% endif %}</p> <h3>Viungo</h3> {{ pishi.viungo|linebreaks }} <h3>Maelekezo</h3> {{ pishi.maelekezo|linebreaks }} <a href="{% url 'mapishi:orodha' %}">← Rudi</a> {% endblock %}
# mapishi/tests.py from django.test import TestCase from django.urls import reverse from .models import Pishi class MapishiTests(TestCase): def test_orodha(self): Pishi.objects.create(jina="Pilau", viungo="mchele", maelekezo="pika", muda_dakika=60) r = self.client.get(reverse("mapishi:orodha")) self.assertEqual(r.status_code, 200) self.assertContains(r, "Pilau") def test_pishi_lisilokuwepo(self): r = self.client.get(reverse("mapishi:detail", args=[999])) self.assertEqual(r.status_code, 404)
Ukiulizwa swali usilolijua
Sema: "Swali zuri. Django ina documentation bora kuliko frameworks nyingi — tuangalie pamoja." Tafuta kwenye docs kwa jina la kitu (mf. "QuerySet API", "built-in template tags"). Ukipata error, nakili mstari wa mwisho wa error kwenye Google pamoja na neno "django".
- Django docs (5.2): https://docs.djangoproject.com/en/5.2/
- Template tags na filters: https://docs.djangoproject.com/en/5.2/ref/templates/builtins/
- QuerySet API: https://docs.djangoproject.com/en/5.2/ref/models/querysets/
Checklist ya mwisho wa wiki
- Anaeleza Django vs FastAPI na anajua lini kutumia kila moja
- Anaanzisha project (
startproject config .) na app, na kusajili app - Anaeleza mtiririko: urls → view → model → template
- Anaandika URLs zenye
name,app_namena vigezo (<int:pk>) - Anatumia
renderna context;{{ }},{% if %},{% for %}, filters - Ana
base.htmlna kurasa zinazorithi ({% extends %},{% block %}) - CSS inapakiwa kwa
{% load static %} - Anaandika model, anafanya
makemigrationsnamigrate - Ana superuser, models zimesajiliwa admin na
list_display/search_fields - Anatumia ORM kwenye shell (create, filter, get, order_by, count,
__icontains) - Ukurasa wa orodha na wa post moja unasoma kutoka database; 404 inafanya kazi
-
python manage.py testinapita