mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-03-16 22:20:26 +00:00
Merge 526da4e990 into 0e3a2ec1a7
This commit is contained in:
commit
537f820434
16 changed files with 579 additions and 0 deletions
100
sample_project/README.md
Normal file
100
sample_project/README.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# Django Auditlog Sample Project
|
||||
|
||||
This is a minimal Django project for testing and demonstrating `django-auditlog` during development.
|
||||
It includes comprehensive models with various field types and relationships (ForeignKey, ManyToMany)
|
||||
to showcase how Auditlog tracks changes across different scenarios.
|
||||
|
||||
## Project Structure
|
||||
|
||||
This sample project includes the following models:
|
||||
|
||||
- **Post**: Blog post model with title, content, author, category, and tags
|
||||
- **Category**: Category model for organizing posts
|
||||
- **Tag**: Tag model for labeling posts with keywords
|
||||
|
||||
All models are registered for audit logging through the `AUDITLOG_INCLUDE_TRACKING_MODELS` setting.
|
||||
|
||||
## Setup and Installation
|
||||
|
||||
### 1. Create Virtual Environment and Install Dependencies
|
||||
|
||||
```bash
|
||||
# Create virtual environment
|
||||
python -m venv venv
|
||||
|
||||
# Activate virtual environment
|
||||
# Linux/Mac:
|
||||
source venv/bin/activate
|
||||
|
||||
# Windows:
|
||||
venv\Scripts\activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. Database Setup
|
||||
|
||||
```bash
|
||||
# Run migrations
|
||||
python manage.py migrate
|
||||
|
||||
# Load sample data
|
||||
python manage.py loaddata initial_data
|
||||
```
|
||||
|
||||
### 3. Create Superuser
|
||||
|
||||
```bash
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
### 4. Start Development Server
|
||||
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
## Testing Auditlog Features
|
||||
|
||||
1. **Access Admin Interface**: Visit `http://127.0.0.1:8000/admin/` and log in with your superuser credentials
|
||||
|
||||
2. **Edit Models**: Try editing `Post`, `Category`, and `Tag` objects:
|
||||
- Create new posts
|
||||
- Edit existing post titles, content
|
||||
- Change categories
|
||||
- Add/remove tags
|
||||
- Change authors
|
||||
|
||||
3. **View Audit Logs**:
|
||||
- Navigate to `auditlog > Log entries` in the Django admin
|
||||
- Filter by content type to see logs for specific models
|
||||
- View detailed change information for each modification
|
||||
- Use the date hierarchy to find logs from specific time periods
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
sample_project/
|
||||
├── demo/ # Demo application
|
||||
│ ├── models.py # Post, Category, Tag models
|
||||
│ ├── admin.py # Django admin configuration
|
||||
│ ├── fixtures/ # Sample data
|
||||
│ └── migrations/ # Database migrations
|
||||
├── sample_project/ # Django project settings
|
||||
│ └── settings.py # Includes Auditlog configuration
|
||||
└── manage.py
|
||||
```
|
||||
|
||||
## Auditlog Configuration
|
||||
|
||||
The project demonstrates configuration via settings:
|
||||
|
||||
```python
|
||||
# In settings.py
|
||||
AUDITLOG_INCLUDE_TRACKING_MODELS = (
|
||||
{"model": "demo.Post", "m2m_fields": ["tags"]},
|
||||
"demo.Category",
|
||||
"demo.Tag",
|
||||
)
|
||||
```
|
||||
0
sample_project/demo/__init__.py
Normal file
0
sample_project/demo/__init__.py
Normal file
37
sample_project/demo/admin.py
Normal file
37
sample_project/demo/admin.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import Category, Post, Tag
|
||||
|
||||
|
||||
@admin.register(Post)
|
||||
class PostAdmin(admin.ModelAdmin):
|
||||
list_display = ("title", "author", "category", "created_at", "updated_at")
|
||||
list_filter = ("author", "category", "tags", "created_at")
|
||||
search_fields = ("title", "content")
|
||||
filter_horizontal = ("tags",)
|
||||
date_hierarchy = "created_at"
|
||||
|
||||
fieldsets = (
|
||||
(None, {"fields": ("title", "author", "category")}),
|
||||
("Content", {"fields": ("content", "tags")}),
|
||||
(
|
||||
"Timestamps",
|
||||
{"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
|
||||
),
|
||||
)
|
||||
|
||||
readonly_fields = ("created_at", "updated_at")
|
||||
|
||||
|
||||
@admin.register(Category)
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ("name",)
|
||||
search_fields = ("name",)
|
||||
|
||||
|
||||
@admin.register(Tag)
|
||||
class TagAdmin(admin.ModelAdmin):
|
||||
list_display = ("name",)
|
||||
search_fields = ("name",)
|
||||
|
||||
|
||||
6
sample_project/demo/apps.py
Normal file
6
sample_project/demo/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DemoConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "demo"
|
||||
53
sample_project/demo/fixtures/initial_data.json
Normal file
53
sample_project/demo/fixtures/initial_data.json
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
[
|
||||
{
|
||||
"model": "demo.category",
|
||||
"pk": 1,
|
||||
"fields": {"name": "Technology"}
|
||||
},
|
||||
{
|
||||
"model": "demo.category",
|
||||
"pk": 2,
|
||||
"fields": {"name": "Programming"}
|
||||
},
|
||||
{
|
||||
"model": "demo.tag",
|
||||
"pk": 1,
|
||||
"fields": {"name": "Django"}
|
||||
},
|
||||
{
|
||||
"model": "demo.tag",
|
||||
"pk": 2,
|
||||
"fields": {"name": "Audit"}
|
||||
},
|
||||
{
|
||||
"model": "demo.tag",
|
||||
"pk": 3,
|
||||
"fields": {"name": "Python"}
|
||||
},
|
||||
{
|
||||
"model": "demo.post",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"author": null,
|
||||
"category": 1,
|
||||
"tags": [1, 2],
|
||||
"title": "Getting Started with Django Auditlog",
|
||||
"content": "This is a comprehensive guide to using django-auditlog for tracking model changes in your Django applications.",
|
||||
"created_at": "2024-01-01T10:00:00Z",
|
||||
"updated_at": "2024-01-01T10:00:00Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "demo.post",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"author": null,
|
||||
"category": 2,
|
||||
"tags": [1, 3],
|
||||
"title": "Advanced Auditlog Features",
|
||||
"content": "Exploring advanced features like custom fields, middleware integration, and performance optimization.",
|
||||
"created_at": "2024-01-02T14:30:00Z",
|
||||
"updated_at": "2024-01-02T14:30:00Z"
|
||||
}
|
||||
}
|
||||
]
|
||||
106
sample_project/demo/migrations/0001_initial.py
Normal file
106
sample_project/demo/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Generated by Django 6.0.dev20250722211940 on 2025-07-23 17:00
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Category",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(help_text="Category name", max_length=50)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Category",
|
||||
"verbose_name_plural": "Categories",
|
||||
"ordering": ["name"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Tag",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(help_text="Tag name", max_length=50)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Tag",
|
||||
"verbose_name_plural": "Tags",
|
||||
"ordering": ["name"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Post",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("title", models.CharField(help_text="Post title", max_length=200)),
|
||||
("content", models.TextField(help_text="Post content")),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"author",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Post author",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"category",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Post category",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="demo.category",
|
||||
),
|
||||
),
|
||||
(
|
||||
"tags",
|
||||
models.ManyToManyField(
|
||||
blank=True, help_text="Post tags", to="demo.tag"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Post",
|
||||
"verbose_name_plural": "Posts",
|
||||
"ordering": ["-created_at"],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
sample_project/demo/migrations/__init__.py
Normal file
0
sample_project/demo/migrations/__init__.py
Normal file
62
sample_project/demo/models.py
Normal file
62
sample_project/demo/models.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
"""Blog post category model for organizing content."""
|
||||
|
||||
name = models.CharField(max_length=50, help_text="Category name")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Category"
|
||||
verbose_name_plural = "Categories"
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Tag(models.Model):
|
||||
"""Tag model for labeling posts with keywords."""
|
||||
|
||||
name = models.CharField(max_length=50, help_text="Tag name")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Tag"
|
||||
verbose_name_plural = "Tags"
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Post(models.Model):
|
||||
"""Blog post model demonstrating auditlog tracking with various field types."""
|
||||
|
||||
author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Post author",
|
||||
)
|
||||
category = models.ForeignKey(
|
||||
Category,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Post category",
|
||||
)
|
||||
tags = models.ManyToManyField(Tag, blank=True, help_text="Post tags")
|
||||
title = models.CharField(max_length=200, help_text="Post title")
|
||||
content = models.TextField(help_text="Post content")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Post"
|
||||
verbose_name_plural = "Posts"
|
||||
ordering = ["-created_at"]
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
3
sample_project/demo/views.py
Normal file
3
sample_project/demo/views.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
22
sample_project/manage.py
Executable file
22
sample_project/manage.py
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_project.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2
sample_project/requirements.txt
Normal file
2
sample_project/requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Django>=4.2
|
||||
-e ..
|
||||
0
sample_project/sample_project/__init__.py
Normal file
0
sample_project/sample_project/__init__.py
Normal file
16
sample_project/sample_project/asgi.py
Normal file
16
sample_project/sample_project/asgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
ASGI config for sample_project project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_project.settings")
|
||||
|
||||
application = get_asgi_application()
|
||||
133
sample_project/sample_project/settings.py
Normal file
133
sample_project/sample_project/settings.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
"""
|
||||
Django settings for sample_project project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 4.2.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = "django-insecure-%2$vp=u*@vr%ujwu4!gmr*5)0hl-y&*htqi4arjf7i&#uozpxy"
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"auditlog",
|
||||
"demo",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"auditlog.middleware.AuditlogMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "sample_project.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "sample_project.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": BASE_DIR / "db.sqlite3",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
TIME_ZONE = "UTC"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||
|
||||
STATIC_URL = "static/"
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
# Register demo models with Auditlog
|
||||
AUDITLOG_INCLUDE_TRACKING_MODELS = (
|
||||
{"model": "demo.Post", "m2m_fields": ["tags"]},
|
||||
"demo.Category",
|
||||
"demo.Tag",
|
||||
)
|
||||
23
sample_project/sample_project/urls.py
Normal file
23
sample_project/sample_project/urls.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
"""
|
||||
URL configuration for sample_project project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
]
|
||||
16
sample_project/sample_project/wsgi.py
Normal file
16
sample_project/sample_project/wsgi.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
WSGI config for sample_project project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_project.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
Loading…
Reference in a new issue