├── apps ├── __init__.py ├── example │ ├── __init__.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── seed.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── admin.py │ ├── apps.py │ ├── serializers.py │ ├── models.py │ ├── urls.py │ └── views.py └── activity_log │ ├── __init__.py │ ├── migrations │ ├── __init__.py │ └── 0001_initial.py │ ├── tests.py │ ├── apps.py │ ├── utils.py │ ├── views.py │ ├── urls.py │ ├── serializer.py │ ├── constants.py │ ├── admin.py │ ├── signals.py │ ├── models.py │ └── mixins.py ├── config ├── __init__.py ├── urls.py ├── asgi.py ├── wsgi.py └── settings.py ├── README.md ├── manage.py └── .gitignore /apps/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/example/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/activity_log/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/example/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/example/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/activity_log/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/example/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/example/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/activity_log/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/example/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /apps/example/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ExampleConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.example" 7 | -------------------------------------------------------------------------------- /apps/activity_log/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ActivityConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.activity_log" 7 | -------------------------------------------------------------------------------- /apps/example/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework.serializers import ModelSerializer 2 | 3 | from .models import Post 4 | 5 | 6 | class PostSerializer(ModelSerializer): 7 | class Meta: 8 | model = Post 9 | fields = "__all__" 10 | -------------------------------------------------------------------------------- /apps/activity_log/utils.py: -------------------------------------------------------------------------------- 1 | def get_client_ip(request): 2 | x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") 3 | return ( 4 | x_forwarded_for.split(",")[0] 5 | if x_forwarded_for 6 | else request.META.get("REMOTE_ADDR") 7 | ) 8 | -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | urlpatterns = [ 5 | path("admin/", admin.site.urls), 6 | path("api/", include("apps.example.urls")), 7 | path("logs/", include("apps.activity_log.urls")), 8 | ] 9 | -------------------------------------------------------------------------------- /apps/example/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Post(models.Model): 5 | title = models.CharField(max_length=250) 6 | body = models.TextField() 7 | 8 | 9 | class Article(models.Model): 10 | title = models.CharField(max_length=250) 11 | body = models.TextField() 12 | -------------------------------------------------------------------------------- /apps/activity_log/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.viewsets import ReadOnlyModelViewSet 2 | 3 | from .models import ActivityLog 4 | from .serializer import ActivityLogSerializer 5 | 6 | 7 | class ActivityLogReadOnlyViewSet(ReadOnlyModelViewSet): 8 | queryset = ActivityLog.objects.all() 9 | serializer_class = ActivityLogSerializer 10 | -------------------------------------------------------------------------------- /apps/activity_log/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | from rest_framework.routers import DefaultRouter 5 | 6 | from .views import ActivityLogReadOnlyViewSet 7 | 8 | router = DefaultRouter() 9 | 10 | 11 | router.register(r"", ActivityLogReadOnlyViewSet, basename="log") 12 | 13 | urlpatterns = router.urls 14 | -------------------------------------------------------------------------------- /apps/activity_log/serializer.py: -------------------------------------------------------------------------------- 1 | from rest_framework.serializers import ModelSerializer 2 | 3 | from .models import ActivityLog 4 | 5 | 6 | class ActivityLogSerializer(ModelSerializer): 7 | class Meta: 8 | model = ActivityLog 9 | fields = [ 10 | "actor", 11 | "action_type", 12 | "action_time", 13 | "remarks", 14 | "status", 15 | ] 16 | -------------------------------------------------------------------------------- /apps/example/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from rest_framework.routers import DefaultRouter 4 | 5 | from .views import ping, ArticleListView, PostReadOnlyViewSet 6 | 7 | router = DefaultRouter() 8 | 9 | router.register("posts", PostReadOnlyViewSet, basename="post") 10 | 11 | urlpatterns = [ 12 | path("ping", ping), 13 | path("articles", ArticleListView.as_view(), name="articles_list"), 14 | ] 15 | 16 | urlpatterns += router.urls 17 | -------------------------------------------------------------------------------- /config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for django_user_activity project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_user_activity project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /apps/activity_log/constants.py: -------------------------------------------------------------------------------- 1 | CREATE, READ, UPDATE, DELETE = "Create", "Read", "Update", "Delete" 2 | LOGIN, LOGOUT, LOGIN_FAILED = "Login", "Logout", "Login Failed" 3 | ACTION_TYPES = [ 4 | (CREATE, CREATE), 5 | (READ, READ), 6 | (UPDATE, UPDATE), 7 | (DELETE, DELETE), 8 | (LOGIN, LOGIN), 9 | (LOGOUT, LOGOUT), 10 | (LOGIN_FAILED, LOGIN_FAILED), 11 | ] 12 | 13 | SUCCESS, FAILED = "Success", "Failed" 14 | ACTION_STATUS = [(SUCCESS, SUCCESS), (FAILED, FAILED)] 15 | -------------------------------------------------------------------------------- /apps/activity_log/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import ActivityLog 3 | 4 | 5 | class ActivityLogAdmin(admin.ModelAdmin): 6 | model = ActivityLog 7 | 8 | def has_delete_permission(self, request, obj=None) -> bool: 9 | return False 10 | 11 | def has_add_permission(self, request, obj=None) -> bool: 12 | return False 13 | 14 | def has_change_permission(self, request, obj=None) -> bool: 15 | return False 16 | 17 | 18 | admin.site.register(ActivityLog, ActivityLogAdmin) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django User Activity 2 | 3 | #### Tracking user activity in django including api hits, login, logout etc 4 | 5 | 6 | ## Example 7 | 8 | ##### In order to track api hit use ActivityLogMixin with class based API views and ViewSets. 9 | 10 | ```python 11 | class PostReadOnlyViewSet(ActivityLogMixin, ReadOnlyModelViewSet): 12 | queryset = Post.objects.all() 13 | serializer_class = PostSerializer 14 | 15 | def get_log_message(self, request) -> str: 16 | return f"{request.user} is reading blog posts" 17 | ``` 18 | 19 | Actual work is done in this [mixin](https://github.com/paudelgaurav/django-user-activity/blob/develop/apps/activity_log/mixins.py). 20 | -------------------------------------------------------------------------------- /apps/example/management/commands/seed.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand, CommandError 2 | 3 | from ...models import Article, Post 4 | 5 | 6 | class Command(BaseCommand): 7 | help = "Seeding data for Post and Article models" 8 | 9 | def handle(self, *args, **options): 10 | post_bulk_create_list = [ 11 | Post(**{"title": f"Title {i}", "body": f"Body {i}"}) for i in range(0, 5) 12 | ] 13 | article_bulk_create_list = [ 14 | Article(**{"title": f"Title {i}", "body": f"Body {i}"}) for i in range(0, 5) 15 | ] 16 | Post.objects.bulk_create(post_bulk_create_list) 17 | Article.objects.bulk_create(article_bulk_create_list) 18 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /apps/activity_log/signals.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.signals import user_logged_in, user_login_failed 2 | from django.dispatch import receiver 3 | 4 | from .constants import LOGIN, LOGIN_FAILED 5 | from .models import ActivityLog 6 | from .utils import get_client_ip 7 | 8 | 9 | @receiver(user_logged_in) 10 | def log_user_login(sender, request, user, **kwargs): 11 | message = f"{user.full_name} is logged in with ip:{get_client_ip(request)}" 12 | ActivityLog.objects.create(actor=user, action_type=LOGIN, remarks=message) 13 | 14 | 15 | @receiver(user_login_failed) 16 | def log_user_login_failed(sender, credentials, request, **kwargs): 17 | message = f"Login Attempt Failed for number {credentials.get('phone_number')} with ip: {get_client_ip(request)}" 18 | ActivityLog.objects.create(action_type=LOGIN_FAILED, remarks=message) 19 | -------------------------------------------------------------------------------- /apps/example/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | from rest_framework.views import APIView 4 | from rest_framework.decorators import api_view 5 | from rest_framework.response import Response 6 | from rest_framework.viewsets import ReadOnlyModelViewSet 7 | 8 | from apps.activity_log.mixins import ActivityLogMixin 9 | 10 | from .models import Post, Article 11 | from .serializers import PostSerializer 12 | 13 | 14 | @api_view() 15 | def ping(request): 16 | return Response("Pong") 17 | 18 | 19 | class ArticleListView(ActivityLogMixin, APIView): 20 | def get(self, request, *args, **kwargs): 21 | return Response({"articles": Article.objects.values()}) 22 | 23 | 24 | class PostReadOnlyViewSet(ActivityLogMixin, ReadOnlyModelViewSet): 25 | queryset = Post.objects.all() 26 | serializer_class = PostSerializer 27 | 28 | def get_log_message(self, request) -> str: 29 | return f"{request.user} is reading blog posts" 30 | -------------------------------------------------------------------------------- /apps/example/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-18 18:30 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Article', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.CharField(max_length=250)), 19 | ('body', models.TextField()), 20 | ], 21 | ), 22 | migrations.CreateModel( 23 | name='Post', 24 | fields=[ 25 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 26 | ('title', models.CharField(max_length=250)), 27 | ('body', models.TextField()), 28 | ], 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /apps/activity_log/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth import get_user_model 3 | from django.contrib.contenttypes.models import ContentType 4 | from django.contrib.contenttypes.fields import GenericForeignKey 5 | 6 | from .constants import ACTION_TYPES, ACTION_STATUS, SUCCESS 7 | 8 | User = get_user_model() 9 | 10 | 11 | class ActivityLog(models.Model): 12 | actor = models.ForeignKey(User, on_delete=models.CASCADE, null=True) 13 | action_type = models.CharField(choices=ACTION_TYPES, max_length=15) 14 | action_time = models.DateTimeField(auto_now_add=True) 15 | remarks = models.TextField(blank=True, null=True) 16 | status = models.CharField(choices=ACTION_STATUS, max_length=7, default=SUCCESS) 17 | data = models.JSONField(default=dict) 18 | 19 | # for generic relations 20 | content_type = models.ForeignKey( 21 | ContentType, models.SET_NULL, blank=True, null=True 22 | ) 23 | object_id = models.PositiveIntegerField(blank=True, null=True) 24 | content_object = GenericForeignKey() 25 | 26 | def __str__(self) -> str: 27 | return f"{self.action_type} by {self.actor} on {self.action_time}" 28 | -------------------------------------------------------------------------------- /apps/activity_log/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-18 18:30 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('contenttypes', '0002_remove_content_type_name'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='ActivityLog', 20 | fields=[ 21 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('action_type', models.CharField(choices=[('Create', 'Create'), ('Read', 'Read'), ('Update', 'Update'), ('Delete', 'Delete'), ('Login', 'Login'), ('Logout', 'Logout'), ('Login Failed', 'Login Failed')], max_length=15)), 23 | ('action_time', models.DateTimeField(auto_now_add=True)), 24 | ('remarks', models.TextField(blank=True, null=True)), 25 | ('status', models.CharField(choices=[('Failed', 'Failed'), ('Success', 'Success')], default='Failed', max_length=7)), 26 | ('data', models.JSONField(default=dict)), 27 | ('object_id', models.PositiveIntegerField(blank=True, null=True)), 28 | ('actor', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 29 | ('content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.contenttype')), 30 | ], 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Django # 2 | *.log 3 | *.pot 4 | *.pyc 5 | __pycache__ 6 | db.sqlite3 7 | media 8 | 9 | # Backup files # 10 | *.bak 11 | 12 | # If you are using PyCharm # 13 | # User-specific stuff 14 | .idea/**/workspace.xml 15 | .idea/**/tasks.xml 16 | .idea/**/usage.statistics.xml 17 | .idea/**/dictionaries 18 | .idea/**/shelf 19 | 20 | # AWS User-specific 21 | .idea/**/aws.xml 22 | 23 | # Generated files 24 | .idea/**/contentModel.xml 25 | 26 | # Sensitive or high-churn files 27 | .idea/**/dataSources/ 28 | .idea/**/dataSources.ids 29 | .idea/**/dataSources.local.xml 30 | .idea/**/sqlDataSources.xml 31 | .idea/**/dynamic.xml 32 | .idea/**/uiDesigner.xml 33 | .idea/**/dbnavigator.xml 34 | 35 | # Gradle 36 | .idea/**/gradle.xml 37 | .idea/**/libraries 38 | 39 | # File-based project format 40 | *.iws 41 | 42 | # IntelliJ 43 | out/ 44 | 45 | # JIRA plugin 46 | atlassian-ide-plugin.xml 47 | 48 | # Python # 49 | *.py[cod] 50 | *$py.class 51 | 52 | # Distribution / packaging 53 | .Python build/ 54 | develop-eggs/ 55 | dist/ 56 | downloads/ 57 | eggs/ 58 | .eggs/ 59 | lib/ 60 | lib64/ 61 | parts/ 62 | sdist/ 63 | var/ 64 | wheels/ 65 | *.egg-info/ 66 | .installed.cfg 67 | *.egg 68 | *.manifest 69 | *.spec 70 | 71 | # Installer logs 72 | pip-log.txt 73 | pip-delete-this-directory.txt 74 | 75 | # Unit test / coverage reports 76 | htmlcov/ 77 | .tox/ 78 | .coverage 79 | .coverage.* 80 | .cache 81 | .pytest_cache/ 82 | nosetests.xml 83 | coverage.xml 84 | *.cover 85 | .hypothesis/ 86 | 87 | # Jupyter Notebook 88 | .ipynb_checkpoints 89 | 90 | # pyenv 91 | .python-version 92 | 93 | # celery 94 | celerybeat-schedule.* 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Environments 100 | .env 101 | .venv 102 | env/ 103 | venv/ 104 | ENV/ 105 | env.bak/ 106 | venv.bak/ 107 | 108 | # mkdocs documentation 109 | /site 110 | 111 | # mypy 112 | .mypy_cache/ 113 | 114 | # Sublime Text # 115 | *.tmlanguage.cache 116 | *.tmPreferences.cache 117 | *.stTheme.cache 118 | *.sublime-workspace 119 | *.sublime-project 120 | 121 | # sftp configuration file 122 | sftp-config.json 123 | 124 | # Package control specific files Package 125 | Control.last-run 126 | Control.ca-list 127 | Control.ca-bundle 128 | Control.system-ca-bundle 129 | GitHub.sublime-settings 130 | 131 | # Visual Studio Code # 132 | .vscode/* 133 | !.vscode/settings.json 134 | !.vscode/tasks.json 135 | !.vscode/launch.json 136 | !.vscode/extensions.json 137 | .history -------------------------------------------------------------------------------- /apps/activity_log/mixins.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.conf import settings 4 | from django.contrib.contenttypes.models import ContentType 5 | 6 | from rest_framework.exceptions import ValidationError 7 | 8 | from .models import ActivityLog 9 | from .constants import READ, CREATE, UPDATE, DELETE, SUCCESS, FAILED 10 | 11 | 12 | class ActivityLogMixin: 13 | """ 14 | Mixin to track user actions 15 | 16 | :cvar log_message: 17 | Log message to populate remarks in LogAction 18 | 19 | type --> str 20 | 21 | set this value or override get_log_message 22 | 23 | If not set then, default log message is generated 24 | """ 25 | 26 | log_message = None 27 | 28 | def _get_action_type(self, request) -> str: 29 | return self.action_type_mapper().get(f"{request.method.upper()}") 30 | 31 | def _build_log_message(self, request) -> str: 32 | return f"User: {self._get_user(request)} -- Action Type: {self._get_action_type(request)} -- Path: {request.path} -- Path Name: {request.resolver_match.url_name}" 33 | 34 | def get_log_message(self, request) -> str: 35 | return self.log_message or self._build_log_message(request) 36 | 37 | @staticmethod 38 | def action_type_mapper(): 39 | return { 40 | "GET": READ, 41 | "POST": CREATE, 42 | "PUT": UPDATE, 43 | "PATCH": UPDATE, 44 | "DELETE": DELETE, 45 | } 46 | 47 | @staticmethod 48 | def _get_user(request): 49 | return request.user if request.user.is_authenticated else None 50 | 51 | def _write_log(self, request, response): 52 | status = SUCCESS if response.status_code < 400 else FAILED 53 | actor = self._get_user(request) 54 | 55 | if actor and not getattr(settings, "TESTING", False): 56 | logging.info("Started Log Entry") 57 | 58 | data = { 59 | "actor": actor, 60 | "action_type": self._get_action_type(request), 61 | "status": status, 62 | "remarks": self.get_log_message(request), 63 | } 64 | try: 65 | data["content_type"] = ContentType.objects.get_for_model( 66 | self.get_queryset().model 67 | ) 68 | data["content_object"] = self.get_object() 69 | except (AttributeError, ValidationError): 70 | data["content_type"] = None 71 | except AssertionError: 72 | pass 73 | 74 | ActivityLog.objects.create(**data) 75 | 76 | def finalize_response(self, request, *args, **kwargs): 77 | response = super().finalize_response(request, *args, **kwargs) 78 | self._write_log(request, response) 79 | return response 80 | -------------------------------------------------------------------------------- /config/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_user_activity project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.0.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.0/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "django-insecure-86h!8$=4!#)#i(beo0(04to4my6*a4spb7mcg--oft8h4%5b_o" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | # third party 41 | "rest_framework", 42 | # local apps 43 | "apps.activity_log", 44 | "apps.example", 45 | ] 46 | 47 | MIDDLEWARE = [ 48 | "django.middleware.security.SecurityMiddleware", 49 | "django.contrib.sessions.middleware.SessionMiddleware", 50 | "django.middleware.common.CommonMiddleware", 51 | "django.middleware.csrf.CsrfViewMiddleware", 52 | "django.contrib.auth.middleware.AuthenticationMiddleware", 53 | "django.contrib.messages.middleware.MessageMiddleware", 54 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 55 | ] 56 | 57 | ROOT_URLCONF = "config.urls" 58 | 59 | TEMPLATES = [ 60 | { 61 | "BACKEND": "django.template.backends.django.DjangoTemplates", 62 | "DIRS": [], 63 | "APP_DIRS": True, 64 | "OPTIONS": { 65 | "context_processors": [ 66 | "django.template.context_processors.debug", 67 | "django.template.context_processors.request", 68 | "django.contrib.auth.context_processors.auth", 69 | "django.contrib.messages.context_processors.messages", 70 | ], 71 | }, 72 | }, 73 | ] 74 | 75 | WSGI_APPLICATION = "config.wsgi.application" 76 | 77 | 78 | # Database 79 | # https://docs.djangoproject.com/en/4.0/ref/settings/#databases 80 | 81 | DATABASES = { 82 | "default": { 83 | "ENGINE": "django.db.backends.sqlite3", 84 | "NAME": BASE_DIR / "db.sqlite3", 85 | } 86 | } 87 | 88 | 89 | # Password validation 90 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators 91 | 92 | AUTH_PASSWORD_VALIDATORS = [ 93 | { 94 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 95 | }, 96 | { 97 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 98 | }, 99 | { 100 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 101 | }, 102 | { 103 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 104 | }, 105 | ] 106 | 107 | 108 | # Internationalization 109 | # https://docs.djangoproject.com/en/4.0/topics/i18n/ 110 | 111 | LANGUAGE_CODE = "en-us" 112 | 113 | TIME_ZONE = "UTC" 114 | 115 | USE_I18N = True 116 | 117 | USE_TZ = True 118 | 119 | 120 | # Static files (CSS, JavaScript, Images) 121 | # https://docs.djangoproject.com/en/4.0/howto/static-files/ 122 | 123 | STATIC_URL = "static/" 124 | 125 | # Default primary key field type 126 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field 127 | 128 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 129 | 130 | """ 131 | USER_ACTIVITY_LOG = { 132 | "TRACK_SUPERUSERS": True, 133 | "IGNORE_URLS": [], 134 | "TRACK_IGNORE_STATUS_CODES": [], 135 | "TRACK_QUERY_STRING": [], 136 | } 137 | """ 138 | --------------------------------------------------------------------------------