π Django β Guide 7: User Management, Admin Customization & Role-Based Groups
π Note: This guide continues from Guide 6: Authentication. However, it is a complete, standalone collection of all files from the previous guides, so you can start here and everything will work correctly.
π― Objectives
By the end of this guide, you will:
- β Create and manage superusers & normal users
- β Use Groups to separate roles (Admin / Users)
- β Customize Django Admin using groups
- β Make UOM visible to normal users (read-only)
- β Hide all other tables (Items, Categories, etc.) from normal users
- β Give Admin full access to everything
π Project Structure (Relevant Files)
project_folder/
βββ apps/
β βββ users/
β β βββ admin.py β Customize User list
β β βββ templates/users/login.html
β βββ uom/
β β βββ admin.py β Admin full CRUD, Users read-only
β β βββ views.py β Role-based template logic
β β βββ templates/uom/index.html
β βββ items/
β β βββ admin.py β Hidden from normal users
β β βββ views.py
β βββ categories/
β β βββ admin.py β Hidden from normal users
β β βββ views.py
βββ core/
β βββ urls.py
βββ db.sqlite3
1οΈβ£ Creating a Superuser
python manage.py createsuperuser
Sample database already includes:
| Account | Username | Password |
|---|---|---|
| Admin | admin |
root |
| User | user |
demo |
2οΈβ£ Changing User Passwords
Using Admin Panel
- Go to
/admin/ - Click Users
- Select a user
- Click Change Password
Using Django Shell
python manage.py shell
from django.contrib.auth.models import User
u = User.objects.get(username='user')
u.set_password('newpass')
u.save()
3οΈβ£ Customizing User List Display
apps/users/admin.py
from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin # <-- IMPORTANT
class CustomUserAdmin(UserAdmin):
# Display key fields to distinguish admins vs users
list_display = ('username', 'email', 'is_staff', 'is_superuser', 'is_active')
list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
search_fields = ('username', 'email')
# Unregister default User admin
admin.site.unregister(User)
# Register custom admin that still includes password change + profile
admin.site.register(User, CustomUserAdmin)
β Shows admin/staff status, activity, and groups at a glance.
4οΈβ£ Role-Based Groups: Admin & Users
Create Two Groups
- Open
/admin/ β Groups - Add Admin
- Add Users
- No permissions neededβsimple setup for beginners
Assign Users to Groups
| User | Group |
|---|---|
| admin | Admin |
| user / user1 / user2 | Users |
Simple Check in Code
if request.user.groups.filter(name='Admin').exists():
# Admin logic
else:
# User logic
5οΈβ£ UOM Admin & Role-Based Template
apps/uom/admin.py
from django.contrib import admin
from .models import StockItemUOM
class StockItemUOMAdmin(admin.ModelAdmin):
list_display = ['name', 'status', 'created_at', 'updated_at']
search_fields = ['name']
list_filter = ['status']
readonly_fields = (
'id', 'name', 'description', 'status',
'created_at', 'updated_at'
)
# Users β read-only, Admin β full CRUD
def get_readonly_fields(self, request, obj=None):
if request.user.groups.filter(name='Admin').exists():
return ('id', 'created_at', 'updated_at')
return self.readonly_fields
def has_add_permission(self, request):
return request.user.groups.filter(name='Admin').exists()
def has_change_permission(self, request, obj=None):
return request.user.groups.filter(name='Admin').exists()
def has_delete_permission(self, request, obj=None):
return request.user.groups.filter(name='Admin').exists()
admin.site.register(StockItemUOM, StockItemUOMAdmin)
apps/uom/views.py
from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone
import sqlite3
from .models import StockItemUOM
from .forms import StockItemUOMForm
DB_PATH = settings.BASE_DIR / 'db.sqlite3'
@login_required
def index(request):
# Get the logged-in user's primary group
user_groups = request.user.groups.values_list('name', flat=True)
user_group = user_groups[0] if user_groups else None
# List all stock* tables
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'stock%'")
tables = [row[0] for row in cursor.fetchall()]
conn.close()
# Only UOM records
records = StockItemUOM.objects.all().order_by('id')
return render(request, 'uom/index.html', {
'title': 'Stock Items UOM List',
'records': records,
'tables': tables,
'current_table': 'stock_items_uom',
'user_group': user_group, # Pass to template
})
@login_required
def add_record(request):
if request.method == 'POST':
form = StockItemUOMForm(request.POST)
if form.is_valid():
record = form.save(commit=False)
record.created_at = timezone.now()
record.updated_at = timezone.now()
record.save()
return redirect('uom:index')
else:
form = StockItemUOMForm()
return render(request, 'uom/form.html', {'title': 'Add UOM', 'form': form})
@login_required
def update_record(request, pk):
record = get_object_or_404(StockItemUOM, pk=pk)
if request.method == 'POST':
form = StockItemUOMForm(request.POST, instance=record)
if form.is_valid():
updated = form.save(commit=False)
updated.updated_at = timezone.now()
updated.save()
return redirect('uom:index')
else:
form = StockItemUOMForm(instance=record)
return render(request, 'uom/form.html', {'title': f'Update UOM ID {record.id}', 'form': form})
@login_required
def delete_record(request, pk):
record = get_object_or_404(StockItemUOM, pk=pk)
record.delete()
return redirect('uom:index')
apps/uom/templates/uom/index.html
<select class="form-select form-select-sm" id="tableSelect" style="width: auto; min-width: 150px;">
{% for table in tables %}
<option value="{{ table }}" {% if table == current_table %}selected{% endif %}>
{{ table }}
</option>
{% endfor %}
</select>
{% if user_group == 'Admin' %}
<a href="{% url 'admin:index' %}" class="btn btn-white btn-sm custom-outline">Admin Group</a>
{% else %}
<a href="{% url 'admin:index' %}" class="btn btn-white btn-sm custom-outline">User Group</a>
{% endif %}
<script>
document.getElementById('tableSelect').addEventListener('change', function() {
const selectedTable = this.value;
if (selectedTable === 'stock_items_categories') window.location.href = '/categories/';
else if (selectedTable === 'stock_document_type') window.location.href = '/doctype/';
else if (selectedTable === 'stock_items') window.location.href = '/items/';
else if (selectedTable === 'stock_items_uom') window.location.href = '/';
});
</script>
β Admin sees βAdmin Groupβ button, Users see βUser Groupβ button. Users can view UOM but cannot CRUD.
This same pattern can be applied to other templates/modules.
6οΈβ£ Hide All Other Tables From Users
In apps/items/admin.py, apps/categories/admin.py, etc.:
def has_module_permission(self, request):
return request.user.groups.filter(name='Admin').exists()
- Only Admins see these apps in Django Admin
- Users have view-only access to the UOM table; all other tables are hidden.
7οΈβ£ Login Template Reference
apps/users/templates/users/login.html
<ul class="small text-muted">
<li>User account: <code>user</code> / <code>demo</code></li>
<li>Admin account: <code>admin</code> / <code>root</code></li>
</ul>
π Final Result
| Feature | Admin | User |
|---|---|---|
| View all tables | β Yes | β No |
| Edit all tables | β Yes | β No |
| View UOM | β Yes | β Yes |
| Edit UOM | β Yes | β Read-only |
| Access Django Admin | β Yes | β Yes |
| Role handled by | Groups | Groups |