Yaliyomo

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:

    powershell
    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:

    text
    Browser -> urls.py -> views.py -> (models.py <-> database) -> template.html -> Browser
    

Malengo ya Wiki (mwisho wa wiki mwanafunzi ataweza...)

  1. Kueleza tofauti ya Django na FastAPI, na lini kutumia kila moja.
  2. Kuanzisha mradi (startproject) na app (startapp), na kueleza kazi ya kila faili kuu.
  3. Kubadilisha settings.py (INSTALLED_APPS, LANGUAGE_CODE, TIME_ZONE, TEMPLATES).
  4. Kuunganisha URL na view (function-based), pamoja na URL zenye vigezo (<int:pk>).
  5. Kutumia templates: {{ variable }}, {% if %}, {% for %}, filters, {% url %}, na template inheritance (base.html).
  6. Kuweka static files (CSS) kwa {% load static %}.
  7. Kuandika models, kuendesha makemigrations na migrate.
  8. Kutumia Django admin (createsuperuser, admin.site.register, ModelAdmin).
  9. Kuandika ORM queries: create, all, filter, get, exclude, order_by, count, __icontains.
  10. 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

  1. Marudio ya kazi ya nyumbani ya Wiki 14 (Msaidizi wa Mapishi)
  2. Django vs FastAPI; ramani ya ombi (request) kwenye Django
  3. Kusakinisha, startproject, runserver, muundo wa folda
  4. settings.py — sehemu muhimu
  5. startapp, view ya kwanza, URLs, URL zenye vigezo
  6. Zoezi darasani
  7. 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):

  1. Mtumiaji anafungua http://127.0.0.1:8000/blog/.
  2. Django inaangalia config/urls.py → inaona blog/ inaelekezwa kwa blog/urls.py.
  3. blog/urls.py inaonyesha ni view ipi ya kuita.
  4. View (function kwenye views.py) inachukua data kutoka Model (database) kama inahitajika.
  5. 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.py ni 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

powershell
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:

text
wiki15_django/
├── venv/
├── manage.py
└── config/
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    ├── asgi.py
    └── wsgi.py
powershell
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:

python
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 iwe False kwenye production.
  • INSTALLED_APPS — orodha ya apps zinazotumika.
  • DATABASES — default ni SQLite (db.sqlite3) — kama tulivyotumia na SQLModel.

Hatua 3: Tengeneza app

powershell
python manage.py startapp blog

Isajili kwenye config/settings.py:

python
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

python
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: request ni kama object ya ombi; view lazima irudishe HttpResponse (au kitu kinachoirudisha).

Hatua 5: URLs za app — tengeneza faili jipya blog/urls.py

python
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

python
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
]
powershell
python manage.py runserver

Jaribu kwenye browser:

Hatua 7: Git .gitignore:

text
venv/
__pycache__/
db.sqlite3
.env
powershell
git init
git add .
git commit -m "Wiki 15: mradi wa Django na app ya blog"

Maswali ya kumuuliza mwanafunzi

  1. 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.
  2. Swali: Na API ya kutabiri bei (Wiki 13)? Jibu: FastAPI — ndogo, ya haraka, inarudisha JSON.
  3. Swali: Tofauti ya project na app? Jibu: Project ni website nzima na mipangilio; app ni sehemu moja yenye kazi maalum.
  4. Swali: Kwa nini /post/abc/ inatoa 404? Jibu: <int:pk> inakubali namba tu; "abc" hailingani na pattern yoyote.
  5. 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
python
# 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>")
python
# 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

  1. Marudio ya kazi ya nyumbani (Kipindi 1)
  2. Kwa nini templates? Tatizo la HTML ndani ya Python
  3. render(), context, {{ }}, {% if %}, {% for %}, filters
  4. Template inheritance: base.html, {% block %}, {% extends %}, {% url %}
  5. Static files: CSS kwa {% load static %}
  6. Zoezi darasani
  7. 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.html ni 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)

text
blog/
├── templates/
│   └── blog/
│       ├── base.html
│       ├── nyumbani.html
│       └── kuhusu.html
└── static/
    └── blog/
        └── style.css

Hatua 2: blog/templates/blog/base.html

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>&copy; 2027 Blog Yetu — Imetengenezwa kwa Django</p>
    </footer>
</body>
</html>

Hatua 3: blog/templates/blog/nyumbani.html

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

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

python
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

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; }
powershell
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 ya static.

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

  1. Swali: Tofauti ya {{ }} na {% %}? Jibu: {{ }} inaonyesha thamani; {% %} ni amri/mantiki (if, for, extends, url, load).
  2. Swali: Kwa nini blog/templates/blog/ na si blog/templates/? Jibu: Namespacing — kuzuia mgongano wa majina ya templates kati ya apps.
  3. Swali: {% empty %} inafanya nini? Jibu: Inaonyesha kitu mbadala kama list ya for ni tupu.
  4. Swali: Faida ya {% url 'blog:kuhusu' %} badala ya /kuhusu/? Jibu: URL ikibadilishwa kwenye urls.py, links zote zinasasishwa zenyewe.
  5. 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
html
<!-- 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 %}
python
# 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})
python
# blog/urls.py
    path("bei/", views.bei, name="bei"),
python
# config/settings.py -> INSTALLED_APPS ongeza:
    "django.contrib.humanize",
html
<!-- 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 %}
html
<!-- 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

  1. Marudio ya kazi ya nyumbani (Kipindi 2)
  2. Models — kuelezea jedwali la database kwa class ya Python
  3. makemigrations na migrate
  4. Django admin: createsuperuser, kusajili models, ModelAdmin
  5. ORM kwenye shell: create, filter, get, order_by...
  6. Views zitumie database: orodha ya posts + post moja (get_object_or_404)
  7. Zoezi darasani
  8. 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:

  1. python manage.py makemigrations — Django inalinganisha models na migrations zilizopo, inaandika faili jipya (mf. blog/migrations/0001_initial.py) linaloelezea mabadiliko. Haigusi database bado.
  2. python manage.py migrate — inatekeleza migrations kwenye database halisi.

    Kila unapobadilisha models.py → makemigrations → migrate. Mara ya kwanza migrate pia 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

python
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

powershell
python manage.py makemigrations
text
Migrations for 'blog':
  blog\migrations\0001_initial.py
    + Create model Kundi
    + Create model Post
powershell
python manage.py migrate
text
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.py uonyeshe — "Django imeandika ramani yenyewe."

Hatua 3: Superuser na Admin

powershell
python manage.py createsuperuser

Jibu maswali (jina, email inaweza kuachwa tupu, password mara mbili — haionekani unapoandika, ni kawaida).

blog/admin.py:

python
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"]
powershell
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

powershell
python manage.py shell
python
>>> 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: "get ni kama kuita mtu kwa jina — lazima awepo mmoja tu. filter ni kama kuuliza 'nani yupo hapa?' — anaweza asiwepo yeyote."

Hatua 5: Views zitumie database — blog/views.py (badilisha nyumbani, ondoa POSTS na post_namba)

python
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:

python
    path("post/<int:pk>/", views.post_detail, name="post_detail"),

Kwenye nyumbani.html, badilisha sehemu ya posts:

html
    <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:

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' %}">&larr; 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

python
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)
powershell
python manage.py test
text
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

  1. Swali: Umeongeza field picha_url kwenye Post. Hatua zipi mbili zinafuata? Jibu: makemigrations kisha migrate.
  2. Swali: Tofauti ya get() na filter()? Jibu: get inarudisha object moja hasa (error kama 0 au zaidi ya 1); filter inarudisha QuerySet (inaweza kuwa tupu).
  3. Swali: auto_now_add vs auto_now? Jibu: auto_now_add inawekwa mara moja wakati wa kutengeneza; auto_now inasasishwa kila save().
  4. Swali: Kwa nini get_object_or_404 badala ya get kwenye view? Jibu: Mtumiaji akiomba post isiyokuwepo apate 404, si kosa la server 500.
  5. Swali: on_delete=models.CASCADE ina 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() na user.posts.count())
  • c) Post ya zamani zaidi
Jibu
python
# 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}'"
python
# blog/admin.py (ongeza)
from .models import Maoni

@admin.register(Maoni)
class MaoniAdmin(admin.ModelAdmin):
    list_display = ["jina", "post", "tarehe"]
powershell
python manage.py makemigrations
python manage.py migrate
html
<!-- 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 %}
python
# 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)

  1. python manage.py startapp mapishi, isajili, ielekeze kwenye path("mapishi/", include("mapishi.urls")).
  2. Model Pishi: jina (CharField 100), viungo (TextField), maelekezo (TextField), muda_dakika (PositiveIntegerField), ni_rahisi (BooleanField default True), tarehe (auto_now_add). __str__ irudishe jina.
  3. Sajili kwenye admin ukiwa na list_display, search_fields na list_filter. Ongeza mapishi 4 (pilau, ugali, chapati, wali wa nazi).
  4. Ukurasa /mapishi/ unaoorodhesha mapishi yote (yanarithi blog/base.html), na /mapishi/<pk>/ wa pishi moja.
  5. Kwenye ukurasa wa orodha, ruhusu kuchuja kwa ?rahisi=1 kuonyesha mapishi rahisi tu, na onyesha muda kwa "dakika X".
  6. Test 2: orodha inarudisha 200, pishi lisilokuwepo ni 404.
Jibu
python
# 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])
python
# 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"]
python
# 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})
python
# 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"),
]
python
# config/urls.py
urlpatterns = [
    path("admin/", admin.site.urls),
    path("mapishi/", include("mapishi.urls")),
    path("", include("blog.urls")),
]
# na "mapishi" kwenye INSTALLED_APPS
html
<!-- 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 %}
html
<!-- 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' %}">&larr; Rudi</a>
{% endblock %}
python
# 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".


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_name na vigezo (<int:pk>)
  • Anatumia render na context; {{ }}, {% if %}, {% for %}, filters
  • Ana base.html na kurasa zinazorithi ({% extends %}, {% block %})
  • CSS inapakiwa kwa {% load static %}
  • Anaandika model, anafanya makemigrations na migrate
  • 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 test inapita