Chapter 19: User Accounts
Add forms for server management, user authentication, and data ownership.
Adding Servers via Form
ModelForm
# inventory/forms.py
from django import forms
from .models import Server, Event
class ServerForm(forms.ModelForm):
class Meta:
model = Server
fields = ['hostname', 'ip_address', 'os', 'status']
labels = {
'hostname': 'Hostname',
'ip_address': 'IP Address',
'os': 'Operating System',
'status': 'Status',
}
class EventForm(forms.ModelForm):
class Meta:
model = Event
fields = ['event_type', 'message']
labels = {'event_type': 'Type', 'message': ''}
widgets = {
'event_type': forms.Select(choices=[
('deploy', 'Deploy'),
('restart', 'Restart'),
('alert', 'Alert'),
('maintenance', 'Maintenance'),
]),
'message': forms.Textarea(attrs={'cols': 60, 'rows': 4}),
}
New Server URL
# inventory/urls.py
urlpatterns = [
# ...existing patterns...
path('new_server/', views.new_server, name='new_server'),
]
New Server View
# inventory/views.py
from django.shortcuts import render, redirect
from .forms import ServerForm
def new_server(request):
"""Register a new server."""
if request.method != 'POST':
form = ServerForm()
else:
form = ServerForm(data=request.POST)
if form.is_valid():
form.save()
return redirect('inventory:servers')
context = {'form': form}
return render(request, 'inventory/new_server.html', context)
New Server Template
<!-- inventory/templates/inventory/new_server.html -->
{% extends 'inventory/base.html' %}
{% block content %}
<h1>Register New Server</h1>
<form action="{% url 'inventory:new_server' %}" method="post">
{% csrf_token %}
{{ form.as_div }}
<button name="submit">Add Server</button>
</form>
{% endblock content %}
Logging Events
Event URL
path('servers/<int:server_id>/log_event/', views.log_event, name='log_event'),
Event View
from .forms import ServerForm, EventForm
def log_event(request, server_id):
"""Log an event for a server."""
server = Server.objects.get(id=server_id)
if request.method != 'POST':
form = EventForm()
else:
form = EventForm(data=request.POST)
if form.is_valid():
new_event = form.save(commit=False)
new_event.server = server
new_event.save()
return redirect('inventory:server_detail', server_id=server_id)
context = {'server': server, 'form': form}
return render(request, 'inventory/log_event.html', context)
Event Template
<!-- inventory/templates/inventory/log_event.html -->
{% extends 'inventory/base.html' %}
{% block content %}
<h1>{{ server.hostname }}</h1>
<h2>Log Event</h2>
<form action="{% url 'inventory:log_event' server.id %}" method="post">
{% csrf_token %}
{{ form.as_div }}
<button name="submit">Log Event</button>
</form>
{% endblock content %}
Editing Servers
# URL
path('servers/<int:server_id>/edit/', views.edit_server, name='edit_server'),
# View
def edit_server(request, server_id):
"""Edit server details."""
server = Server.objects.get(id=server_id)
if request.method != 'POST':
form = ServerForm(instance=server)
else:
form = ServerForm(instance=server, data=request.POST)
if form.is_valid():
form.save()
return redirect('inventory:server_detail', server_id=server.id)
context = {'server': server, 'form': form}
return render(request, 'inventory/edit_server.html', context)
User Authentication
Create accounts app:
python manage.py startapp accounts
Register App and URLs
# inv_project/settings.py
INSTALLED_APPS = [
'inventory',
'accounts',
# ...
]
# inv_project/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('accounts.urls')),
path('', include('inventory.urls')),
]
Login Page
# accounts/urls.py
from django.urls import path, include
app_name = 'accounts'
urlpatterns = [
path('', include('django.contrib.auth.urls')),
]
Create template: accounts/templates/registration/login.html
{% extends 'inventory/base.html' %}
{% block content %}
{% if form.errors %}
<p class="error">Invalid credentials. Try again.</p>
{% endif %}
<h1>Log In</h1>
<form action="{% url 'accounts:login' %}" method="post">
{% csrf_token %}
{{ form.as_div }}
<button name="submit">Log in</button>
</form>
{% endblock content %}
Login Redirect
# inv_project/settings.py
LOGIN_REDIRECT_URL = 'inventory:index'
LOGOUT_REDIRECT_URL = 'inventory:index'
Update Base Template
<!-- base.html nav section -->
<nav>
<a href="{% url 'inventory:index' %}">Dashboard</a>
<a href="{% url 'inventory:servers' %}">Servers</a>
{% if user.is_authenticated %}
| {{ user.username }}
<a href="{% url 'accounts:logout' %}">Log out</a>
{% else %}
<a href="{% url 'accounts:login' %}">Log in</a>
{% endif %}
</nav>
User Registration
# accounts/urls.py
from django.urls import path, include
from . import views
app_name = 'accounts'
urlpatterns = [
path('', include('django.contrib.auth.urls')),
path('register/', views.register, name='register'),
]
# accounts/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm
def register(request):
"""Register a new user."""
if request.method != 'POST':
form = UserCreationForm()
else:
form = UserCreationForm(data=request.POST)
if form.is_valid():
new_user = form.save()
login(request, new_user)
return redirect('inventory:index')
context = {'form': form}
return render(request, 'registration/register.html', context)
Restricting Access
@login_required Decorator
# inventory/views.py
from django.contrib.auth.decorators import login_required
@login_required
def servers(request):
"""List servers - requires authentication."""
servers = Server.objects.order_by('hostname')
context = {'servers': servers}
return render(request, 'inventory/servers.html', context)
@login_required
def new_server(request):
# ... form handling ...
Apply to all views that modify data.
Login URL Setting
# inv_project/settings.py
LOGIN_URL = 'accounts:login'
Data Ownership
Add Owner to Server
# inventory/models.py
from django.contrib.auth.models import User
class Server(models.Model):
hostname = models.CharField(max_length=100)
ip_address = models.GenericIPAddressField()
os = models.CharField(max_length=50, default='linux')
status = models.CharField(max_length=20, default='active')
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f"{self.hostname} ({self.ip_address})"
Migrate with Default
python manage.py makemigrations inventory
Django asks for default. Choose option 1, enter 1 (admin user ID).
python manage.py migrate
Restrict Servers to Owner
@login_required
def servers(request):
"""Show servers owned by current user."""
servers = Server.objects.filter(owner=request.user).order_by('hostname')
context = {'servers': servers}
return render(request, 'inventory/servers.html', context)
Associate New Servers with User
@login_required
def new_server(request):
if request.method != 'POST':
form = ServerForm()
else:
form = ServerForm(data=request.POST)
if form.is_valid():
new_server = form.save(commit=False)
new_server.owner = request.user
new_server.save()
return redirect('inventory:servers')
context = {'form': form}
return render(request, 'inventory/new_server.html', context)
Protecting Server Pages
from django.http import Http404
@login_required
def server_detail(request, server_id):
"""Show server details - only if owned by user."""
server = Server.objects.get(id=server_id)
if server.owner != request.user:
raise Http404
events = server.event_set.all()
context = {'server': server, 'events': events}
return render(request, 'inventory/server_detail.html', context)
Quick Reference
| Forms | Code |
|---|---|
Create ModelForm |
|
Render in template |
|
Process POST |
|
Validate |
|
Save |
|
Save without commit |
|
Pre-fill form |
|
| Auth | Code |
|---|---|
Require login |
|
Check in template |
|
Get current user |
|
Log in user |
|
User creation form |
|
Restrict queryset |
|
Exercises
19-1. Delete Server
Add ability to delete a server (with confirmation).
19-2. Bulk Import
Create a form to import multiple servers from CSV.
19-3. Event Types
Use Django’s choices parameter for event_type field.
19-4. Team Access
Add a Team model. Users belong to teams. Team members see team’s servers.
Summary
-
ModelForms generate forms from models automatically
-
GET displays form, POST processes form data
-
commit=Falsecreates object without saving -
{% csrf_token %}protects against CSRF attacks -
Django’s auth system handles login/logout/registration
-
@login_requiredrestricts views to authenticated users -
ForeignKey to User enables data ownership
-
Filter querysets by
owner=request.user -
Return Http404 for unauthorized access
Next: Styling and deployment.