Vom leeren Ordner zur laufenden Seite β in 5 Minuten.
Immer in einem venv arbeiten β hΓ€lt dein System sauber.
# Venv erstellen und aktivieren python -m venv venv venv\Scripts\activate # Windows source venv/bin/activate # Mac / Linux # Django installieren pip install django
startproject baut den Hauptordner. cd rein. runserver startet.
django-admin startproject meinshop . # Punkt = aktueller Ordner python manage.py runserver # β Browser ΓΆffnen: http://127.0.0.1:8000
Django trennt Logik in Apps. Eine App = ein Bereich (z.B. Shop, Blog, Login).
python manage.py startapp shop
App in settings.py eintragen β sonst kennt Django sie nicht:
INSTALLED_APPS = [
# Django Standard-Apps ...
'django.contrib.admin',
'django.contrib.auth',
# Deine App:
'shop', # β hier eintragen
]
from django.http import HttpResponse def startseite(request): return HttpResponse( "Hallo Shop!" )
from django.urls import path from . import views urlpatterns = [ path( '', views.startseite, name='home' ), ]
Jetzt noch die Haupt-URLs verbinden:
from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('shop.urls')), # β Shop-URLs einbinden ]
urls.py. Die Haupt-urls.py im Projektordner verbindet alle Apps mit include().Echte HTML-Seiten statt HttpResponse-Text.
Django sucht HTML-Dateien in einem templates/ Ordner innerhalb der App.
shop/shop/-Ordner ist Django-Konvention um Namenskonflikte zu vermeiden, wenn du mehrere Apps hast.<!DOCTYPE html> <html lang="de"> <head> <meta charset="UTF-8"> <title>Mein Shop</title> </head> <body> <h1>Willkommen im Shop! π</h1> <p>Hier kommen bald Produkte rein.</p> </body> </html>
render() lΓ€dt die HTML-Datei und gibt sie zurΓΌck.
from django.shortcuts import render def startseite(request): return render(request, 'shop/index.html')
Mit dem Context-Dictionary kannst du Python-Variablen ins HTML geben.
def startseite(request): context = { 'titel': 'Mein Shop', 'anzahl': 42, } return render( request, 'shop/index.html', context )
<h1>{{ titel }}</h1> <p> {{ anzahl }} Produkte </p> {# Das ist ein Kommentar #} {% if anzahl > 0 %} <p>Auf Lager β </p> {% endif %}
{{ variable }} β Wert ausgeben{% if %} {% endif %} β Bedingung{% for item in liste %} {% endfor %} β Schleife
Styling mit Static Files β CSS, JS, Bilder alles lΓ€uft so.
body { margin: 0; font-family: Arial, sans-serif; background: #f5f5f5; } h1 { color: #333; text-align: center; padding: 2rem; } .container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; }
{% load static %} muss oben stehen β dann funktioniert {% static %}.
{% load static %} <!-- β Erste Zeile! --> <!DOCTYPE html> <html lang="de"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="{% static 'shop/style.css' %}" > </head> <body> <div class="container"> <h1>Mein Shop</h1> </div> </body> </html>
Kein lokales CSS nΓΆtig β einfach CDN-Link einfΓΌgen und fertig.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" >
Einmal schreiben β ΓΌberall haben. Das Kern-Konzept von Django Templates.
base.html mit der Navbar. Alle anderen Seiten erben davon mit {% extends %}. Navbar Γ€ndern? Nur einmal, in base.html β fertig.base.html kommt in einen globalen templates-Ordner auf Root-Ebene β nicht in der App.
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # β diese Zeile!
'APP_DIRS': True,
# ... rest bleibt gleich
},
]
Das ist die komplette, professionelle Vorlage. Einmal anlegen, nie wieder anfassen.
{% load static %} <!DOCTYPE html> <html lang="de"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{% block title %}Mein Shop{% endblock %}</title> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" > <!-- Eigenes CSS --> <link rel="stylesheet" href="{% static 'shop/style.css' %}"> <!-- Extra CSS pro Seite mΓΆglich --> {% block extra_css %}{% endblock %} </head> <body> <!-- βββ NAVBAR βββ --> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <div class="container"> <!-- Logo / Brand --> <a class="navbar-brand fw-bold" href="{% url 'home' %}">π MeinShop</a> <!-- Hamburger fΓΌr Mobile --> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMenu"> <span class="navbar-toggler-icon"></span> </button> <!-- MenΓΌ-Links --> <div class="collapse navbar-collapse" id="navMenu"> <ul class="navbar-nav me-auto"> <li class="nav-item"> <a class="nav-link" href="{% url 'home' %}">Home</a> </li> <li class="nav-item"> <a class="nav-link" href="{% url 'produkte' %}">Produkte</a> </li> <li class="nav-item"> <a class="nav-link" href="{% url 'kontakt' %}">Kontakt</a> </li> </ul> <!-- Login-Bereich rechts in der Navbar --> <ul class="navbar-nav"> {% if user.is_authenticated %} <li class="nav-item"> <span class="nav-link"> Hallo, {{ user.username }} </span> </li> <li class="nav-item"> <a class="nav-link" href="{% url 'logout' %}">Logout</a> </li> {% else %} <li class="nav-item"> <a class="nav-link btn btn-outline-light btn-sm px-3" href="{% url 'login' %}">Login</a> </li> {% endif %} </ul> </div> </div> </nav> <!-- βββ SEITENINHALT βββ --> <main class="container py-4"> {% block content %}{% endblock %} </main> <!-- βββ FOOTER βββ --> <footer class="bg-dark text-light text-center py-3 mt-5"> <p class="mb-0">Β© 2025 MeinShop</p> </footer> <!-- Bootstrap JS (fΓΌr Hamburger-MenΓΌ) --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" ></script> {% block extra_js %}{% endblock %} </body> </html>
{% extends 'base.html' %} {% load static %} {% block title %}Home β Mein Shop{% endblock %} {% block content %} <h1>Willkommen im Shop! π</h1> <p>Hier kommen bald Produkte.</p> {% endblock %}
{% extends 'base.html' %} oben β Navbar + Footer kommen automatisch.{% block content %} musst du befΓΌllen. Alle anderen Seiten genauso.
Immer das gleiche Schema: View β URL β Template. Einmal verstehen, immer wissen.
from django.shortcuts import render def startseite(request): return render(request, 'shop/index.html') def produkte(request): return render(request, 'shop/produkte.html') def kontakt(request): return render(request, 'shop/kontakt.html') def ueber_uns(request): return render(request, 'shop/ueber_uns.html')
from django.urls import path from . import views urlpatterns = [ path('', views.startseite, name='home'), path('produkte/', views.produkte, name='produkte'), path('kontakt/', views.kontakt, name='kontakt'), path('ueber-uns/', views.ueber_uns, name='ueber_uns'), ]
{% url 'name' %} im HTML β und wenn du die URL-Adresse Γ€nderst, musst du nur hier Γ€ndern, nicht in 20 HTML-Dateien.Jede neue Seite startet mit diesen 6 Zeilen. Nur der Inhalt im block Γ€ndert sich.
{% extends 'base.html' %} {% load static %} {% block title %}Produkte{% endblock %} {% block content %} <h1>Unsere Produkte</h1> <p>Produkte kommen im nΓ€chsten Slide π</p> {% endblock %}
β Falsch β nie hardcoden
<a href="/produkte/"> Produkte </a>
β Richtig β mit url tag
<a href="{% url 'produkte' %}"> Produkte </a>
Flexible GrΓΆΓen, Shop-Cards, und Videos β ohne das lΓ€stige 25/50/100%-Problem.
{% load static %} <!-- Beliebige Breite: einfach width= setzen --> <img src="{% static 'shop/images/produkt1.jpg' %}" alt="Produkt 1" style="width: 400px; height: 300px; object-fit: cover;" > <!-- ODER: Prozent relativ zum Elternelement --> <img src="{% static 'shop/images/produkt1.jpg' %}" alt="Produkt 1" style="width: 100%; height: 250px; object-fit: cover;" >
Komplett flexibel: du kontrollierst HΓΆhe im CSS, Spalten im Grid.
{% extends 'base.html' %} {% load static %} {% block content %} <h2 class="mb-4">Produkte</h2> <!-- 3 Spalten auf Desktop, 1 auf Mobile --> <div class="row row-cols-1 row-cols-md-3 g-4"> <!-- Wenn Produkte aus der Datenbank kommen: --> {% for produkt in produkte_liste %} <div class="col"> <div class="card h-100 shadow-sm"> <!-- BILD: exakt 280px hoch, egal wie groΓ das Original --> <img src="{% static 'shop/images/produkt.jpg' %}" class="card-img-top" alt="{{ produkt.name }}" style="height: 280px; object-fit: cover;" > <div class="card-body d-flex flex-column"> <h5 class="card-title">{{ produkt.name }}</h5> <p class="card-text text-muted">{{ produkt.beschreibung }}</p> <p class="fw-bold fs-5 mt-auto">{{ produkt.preis }} β¬</p> <a href="#" class="btn btn-dark mt-2">In den Warenkorb</a> </div> </div> </div> {% empty %} <p>Noch keine Produkte. FΓΌge welche in views.py hinzu.</p> {% endfor %} </div> {% endblock %}
Und in views.py gibst du eine Test-Liste mit:
def produkte(request): produkte_liste = [ {'name': 'T-Shirt', 'preis': 19.99, 'beschreibung': 'Super Shirt'}, {'name': 'Hoodie', 'preis': 49.99, 'beschreibung': 'Warm und weich'}, {'name': 'MΓΌtze', 'preis': 12.99, 'beschreibung': 'Cozy af'}, ] return render(request, 'shop/produkte.html', { 'produkte_liste': produkte_liste })
<!-- Volle Breite, 500px hoch, Text darΓΌber --> <div style=" position: relative; width: 100%; height: 500px; overflow: hidden; "> <img src="{% static 'shop/images/hero.jpg' %}" alt="Hero" style=" width: 100%; height: 100%; object-fit: cover; object-position: center; " > <!-- Text ΓΌber dem Bild --> <div style=" position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; text-align: center; "> <h1>Willkommen im Shop</h1> <a href="{% url 'produkte' %}" class="btn btn-light btn-lg">Jetzt shoppen</a> </div> </div>
Option A: Lokales Video (static)
{% load static %} <video style="width: 100%; height: 400px; object-fit: cover;" autoplay muted loop > <source src="{% static 'shop/videos/clip.mp4' %}" type="video/mp4" > </video>
Option B: YouTube einbetten
<!-- Responsive 16:9 --> <div style=" position: relative; padding-bottom: 56.25%; height: 0; "> <iframe src="https://www.youtube.com/embed/VIDEO_ID" style=" position: absolute; top: 0; left: 0; width: 100%; height: 100%; " allowfullscreen ></iframe> </div>
Django bringt ein komplettes Auth-System mit. Du musst fast nichts selbst bauen.
Nur einmal nΓΆtig β legt alle Django-Tabellen (inkl. User) an.
python manage.py migrate # Admin-User erstellen (optional, aber nΓΌtzlich) python manage.py createsuperuser # β Benutzername, E-Mail, Passwort eingeben # β http://127.0.0.1:8000/admin/ ΓΆffnen
Django stellt Login/Logout-URLs fertig bereit β einfach in der Haupt-urls.py includen.
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('shop.urls')), path('accounts/', include('django.contrib.auth.urls')), # β Das gibt dir automatisch: # /accounts/login/ # /accounts/logout/ # /accounts/password_change/ # /accounts/password_reset/ ]
# Nach Login β Home LOGIN_REDIRECT_URL = '/' # Nach Logout β Login-Seite LOGOUT_REDIRECT_URL = '/accounts/login/' # Login-Seite URL LOGIN_URL = '/accounts/login/'
Django sucht das Login-Template unter registration/login.html.
{% extends 'base.html' %} {% block title %}Login{% endblock %} {% block content %} <div class="row justify-content-center"> <div class="col-md-4"> <div class="card shadow-sm mt-5"> <div class="card-body p-4"> <h3 class="mb-4 text-center">π Login</h3> <form method="post"> {% csrf_token %} <!-- IMMER! Sicherheit --> <!-- Benutzername --> <div class="mb-3"> <label class="form-label">Benutzername</label> <input type="text" name="username" class="form-control" required > </div> <!-- Passwort --> <div class="mb-3"> <label class="form-label">Passwort</label> <input type="password" name="password" class="form-control" required > </div> <!-- Fehler anzeigen --> {% if form.errors %} <div class="alert alert-danger"> Falscher Benutzername oder Passwort. </div> {% endif %} <button type="submit" class="btn btn-dark w-100"> Einloggen </button> </form> <hr> <p class="text-center mb-0"> Noch kein Account? <a href="{% url 'register' %}">Registrieren</a> </p> </div> </div> </div> </div> {% endblock %}
from django.contrib.auth.forms import \ UserCreationForm from django.shortcuts import \ render, redirect def register(request): if request.method == 'POST': form = UserCreationForm( request.POST ) if form.is_valid(): form.save() return redirect('login') else: form = UserCreationForm() return render( request, 'registration/register.html', {'form': form} )
{% extends 'base.html' %} {% block content %} <div class="row justify-content-center"> <div class="col-md-5 mt-5"> <div class="card shadow-sm"> <div class="card-body p-4"> <h3>Registrieren</h3> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit" class="btn btn-dark w-100" >Account erstellen</button> </form> </div></div></div></div> {% endblock %}
urlpatterns = [
path('', views.startseite, name='home'),
path('produkte/', views.produkte, name='produkte'),
path('kontakt/', views.kontakt, name='kontakt'),
path('register/', views.register, name='register'), # β NEU
]
from django.contrib.auth.decorators import login_required @login_required # β eine Zeile drΓΌber = Seite geschΓΌtzt def warenkorb(request): return render(request, 'shop/warenkorb.html') # Wer nicht eingeloggt ist β wird automatisch zu /accounts/login/ weitergeleitet