Source code for django_agents.auditors

"""
Django Agents Auditor Module.

This module provides comprehensive auditing capabilities for Django projects,
including code quality analysis, security checks, and best practices validation.

The auditor system consists of:
- :class:`AuditReport`: Report generation and formatting
- :class:`BaseAuditor`: Base class for all auditors
- Various specialized auditors for different aspects of Django projects

Example:
    Basic usage of the auditor system::

        from django_agents.auditors import AuditReport, CodebaseAuditor

        report = AuditReport(output_format='markdown')
        auditor = CodebaseAuditor()
        result = auditor.audit()
        report.add_section('Codebase Analysis', result)
        print(report.generate())
"""
import ast
from pathlib import Path

from django.apps import apps
from django.conf import settings


[docs] class AuditReport: """ Manages audit report generation and formatting. This class collects audit results from multiple auditors and generates formatted reports in various output formats (text, markdown, JSON, HTML). Args: output_format (str): Output format for the report. Options: 'text', 'markdown', 'json', 'html'. Defaults to 'text'. Attributes: format (str): The output format for the report. sections (list): List of report sections containing audit results. total_issues (int): Total count of critical issues found. total_warnings (int): Total count of warnings found. total_suggestions (int): Total count of suggestions provided. Example: >>> report = AuditReport(output_format='markdown') >>> report.add_section('Security', {'critical': ['Issue 1']}) >>> print(report.generate()) """
[docs] def __init__(self, output_format='text'): """ Initialize the audit report. Args: output_format (str, optional): Format for output generation. Defaults to 'text'. """ self.format = output_format self.sections = [] self.total_issues = 0 self.total_warnings = 0 self.total_suggestions = 0
[docs] def add_section(self, title, content): """ Add a section to the report. Args: title (str): Section title. content (dict): Section content containing audit results with keys: - critical (list): Critical issues - warnings (list): Warnings - suggestions (list): Suggestions - good_practices (list): Detected best practices """ self.sections.append({ 'title': title, 'content': content, }) # Count the issues self.total_issues += content.get('critical_issues', 0) self.total_warnings += content.get('warnings', 0) self.total_suggestions += content.get('suggestions', 0)
[docs] def generate(self): """ Generate the final audit report in the specified format. Returns: str: Formatted report content. Note: The format is determined by the ``output_format`` parameter passed during initialization. """ match self.format: case 'markdown': return self._generate_markdown() case 'json': return self._generate_json() case 'html': return self._generate_html() case _: return self._generate_text()
def _generate_text(self): """ Generate a plain text formatted report. Returns: str: Plain text report with sections and findings. """ output = [] for section in self.sections: output.append(f"\n{'='*60}") output.append(f"{section['title'].upper()}") output.append('='*60) content = section['content'] # Critical issues if content.get('critical', []): output.append("\n🔴 CRITICAL ISSUES:") for issue in content['critical']: output.append(f" - {issue}") # Warnings if content.get('warnings', []): output.append("\n🟡 WARNINGS:") for warning in content['warnings']: output.append(f" - {warning}") # Suggestions if content.get('suggestions', []): output.append("\n💡 SUGGESTIONS:") for suggestion in content['suggestions']: output.append(f" - {suggestion}") # Best practices if content.get('good_practices', []): output.append("\n✅ DETECTED BEST PRACTICES:") for practice in content['good_practices']: output.append(f" - {practice}") return '\n'.join(output) def _generate_markdown(self): """ Generate a Markdown formatted report. Returns: str: Markdown report with formatted sections. """ output = ['# Django audit report\n'] for section in self.sections: output.append(f"\n## {section['title']}\n") content = section['content'] if content.get('critical', []): output.append("### 🔴 Critical issues\n") for issue in content['critical']: output.append(f"- {issue}") output.append("") if content.get('warnings', []): output.append("### 🟡 Warnings\n") for warning in content['warnings']: output.append(f"- {warning}") output.append("") if content.get('suggestions', []): output.append("### 💡 Suggestions\n") for suggestion in content['suggestions']: output.append(f"- {suggestion}") output.append("") if content.get('good_practices', []): output.append("### ✅ Detected best practices\n") for practice in content['good_practices']: output.append(f"- {practice}") output.append("") return '\n'.join(output) def _generate_json(self): """ Generate a JSON formatted report. Returns: str: JSON string containing all audit data and summary. """ import json return json.dumps({ 'sections': self.sections, 'summary': { 'critical_issues': self.total_issues, 'warnings': self.total_warnings, 'suggestions': self.total_suggestions, } }, indent=2) def _generate_html(self): """ Generate an HTML formatted report. Returns: str: Complete HTML document with styled audit report. """ output = ['<!DOCTYPE html>', '<html>', '<head>'] output.append('<meta charset="UTF-8">') output.append('<title>Django Audit Report</title>') output.append('<style>') output.append('body { font-family: Arial, sans-serif; margin: 20px; }') output.append('h1 { color: #0C4B33; }') output.append('h2 { color: #44B78B; margin-top: 30px; }') output.append('.critical { color: #DC2626; }') output.append('.warning { color: #F59E0B; }') output.append('.suggestion { color: #3B82F6; }') output.append('.good { color: #10B981; }') output.append('</style>') output.append('</head>', '<body>') output.append('<h1>Django Audit Report</h1>') for section in self.sections: output.append(f"<h2>{section['title']}</h2>") content = section['content'] if content.get('critical', []): output.append('<h3 class="critical">🔴 Critical issues</h3>') output.append('<ul>') for issue in content['critical']: output.append(f'<li>{issue}</li>') output.append('</ul>') if content.get('warnings', []): output.append('<h3 class="warning">🟡 Warnings</h3>') output.append('<ul>') for warning in content['warnings']: output.append(f'<li>{warning}</li>') output.append('</ul>') if content.get('suggestions', []): output.append('<h3 class="suggestion">💡 Suggestions</h3>') output.append('<ul>') for suggestion in content['suggestions']: output.append(f'<li>{suggestion}</li>') output.append('</ul>') if content.get('good_practices', []): output.append('<h3 class="good">✅ Best practices</h3>') output.append('<ul>') for practice in content['good_practices']: output.append(f'<li>{practice}</li>') output.append('</ul>') output.append('</body>', '</html>') return '\n'.join(output)
[docs] def get_summary(self): """ Return a summary of the audit report. Returns: str: Summary containing counts of issues, warnings, and suggestions. """ lines = [ f"Critical issues: {self.total_issues}", f"Warnings: {self.total_warnings}", f"Suggestions: {self.total_suggestions}", ] return '\n'.join(lines)
[docs] class BaseAuditor: """ Base class for all auditor implementations. This abstract class provides the foundation for creating specialized auditors that analyze different aspects of Django projects. Attributes: critical (list): List of critical issues found during audit. warnings (list): List of warnings identified. suggestions (list): List of improvement suggestions. good_practices (list): List of detected best practices. Note: Subclasses must implement the :meth:`audit` method to perform specific audit operations. """
[docs] def __init__(self): """Initialize the auditor with empty result lists.""" self.critical = [] self.warnings = [] self.suggestions = [] self.good_practices = []
[docs] def audit(self): """ Perform the audit operation. This method must be implemented by subclasses to perform specific audit checks. Returns: dict: Audit results from :meth:`get_result`. Raises: NotImplementedError: If not implemented by subclass. """ raise NotImplementedError
[docs] def get_result(self): """ Return the aggregated audit results. Returns: dict: Dictionary containing: - critical (list): Critical issues - warnings (list): Warnings - suggestions (list): Suggestions - good_practices (list): Best practices - critical_issues (int): Count of critical issues """ return { 'critical': self.critical, 'warnings': self.warnings, 'suggestions': self.suggestions, 'good_practices': self.good_practices, 'critical_issues': len(self.critical), }
[docs] class CodebaseAuditor(BaseAuditor): """ Auditor for analyzing project codebase structure and quality. Performs comprehensive analysis of Python files, project structure, dependencies, and documentation. Checks include: - Project structure validation - Python file analysis (docstrings, function complexity) - Dependency management - Documentation presence """
[docs] def audit(self): """ Run the codebase audit. Returns: dict: Audit results containing identified issues and recommendations. """ base_dir = Path(settings.BASE_DIR) if hasattr(settings, 'BASE_DIR') else Path.cwd() # Check project structure self._check_project_structure(base_dir) # Analyze Python files self._analyze_python_files(base_dir) # Check dependencies self._check_dependencies(base_dir) # Check documentation self._check_documentation(base_dir) return self.get_result()
def _check_project_structure(self, base_dir): """ Check the project structure for required and recommended files. Args: base_dir (Path): Base directory of the project. """ required_files = ['manage.py', 'requirements.txt'] recommended_files = ['README.md', '.gitignore', 'setup.py', 'pyproject.toml'] for file in required_files: if not (base_dir / file).exists(): self.critical.append(f"Missing required file: {file}") for file in recommended_files: if not (base_dir / file).exists(): self.warnings.append(f"Missing recommended file: {file}") else: self.good_practices.append(f"File {file} is present") # Check for a tests directory if not list(base_dir.rglob('test*.py')) and not list(base_dir.rglob('tests/')): self.critical.append("No test files found") def _analyze_python_files(self, base_dir): """ Analyze Python files for quality metrics. Checks for: - Module docstrings - Function complexity - Syntax errors Args: base_dir (Path): Base directory of the project. """ py_files = list(base_dir.rglob('*.py')) if not py_files: self.critical.append("No Python files found") return total_lines = 0 files_without_docstring = 0 files_with_long_functions = 0 for py_file in py_files: # Ignore migrations and virtual environments if 'migrations' in py_file.parts or 'venv' in py_file.parts or '.venv' in py_file.parts: continue try: with open(py_file, 'r', encoding='utf-8') as f: content = f.read() total_lines += len(content.splitlines()) # Ensure module docstrings exist try: tree = ast.parse(content) if not ast.get_docstring(tree): files_without_docstring += 1 # Check function length for node in ast.walk(tree): if isinstance(node, ast.FunctionDef): func_lines = node.end_lineno - node.lineno if func_lines > 50: files_with_long_functions += 1 self.warnings.append( f"Long function ({func_lines} lines) in {py_file.name}: {node.name}" ) except SyntaxError: self.warnings.append(f"Syntax error in {py_file}") except Exception as e: self.warnings.append(f"Error analyzing {py_file}: {str(e)}") # Suggestions if files_without_docstring > len(py_files) * 0.5: self.warnings.append( f"{files_without_docstring}/{len(py_files)} files without a module docstring" ) self.good_practices.append(f"{len(py_files)} Python files analyzed ({total_lines} lines)") def _check_dependencies(self, base_dir): """ Check project dependencies and version pinning. Args: base_dir (Path): Base directory of the project. """ requirements_file = base_dir / 'requirements.txt' if requirements_file.exists(): with open(requirements_file, 'r') as f: deps = f.readlines() if len(deps) == 0: self.warnings.append("Empty requirements.txt file") else: self.good_practices.append(f"{len(deps)} declared dependencies") # Ensure pinned versions unpinned = [dep for dep in deps if '==' not in dep and dep.strip() and not dep.startswith('#')] if unpinned: self.suggestions.append( f"{len(unpinned)} dependencies without pinned versions (use '==')" ) def _check_documentation(self, base_dir): """ Check for project documentation files. Args: base_dir (Path): Base directory of the project. """ readme = base_dir / 'README.md' if readme.exists(): with open(readme, 'r', encoding='utf-8') as f: content = f.read() if len(content) < 100: self.warnings.append("README.md is very short (< 100 characters)") else: self.good_practices.append("README.md is present and detailed") # Check the presence of agent documentation if (base_dir / 'AGENTS.md').exists(): self.good_practices.append("AGENTS.md documentation is present") else: self.suggestions.append("Create an AGENTS.md file to document agents")
[docs] class TestsAuditor(BaseAuditor): """ Auditor for analyzing test coverage and quality. Examines test files, test functions, and testing frameworks to ensure adequate test coverage. Checks include: - Presence of test files - Number of test functions - Testing framework configuration (pytest, unittest) - Coverage tool installation """
[docs] def audit(self): """ Run the tests audit. Returns: dict: Audit results with test coverage analysis. """ base_dir = Path(settings.BASE_DIR) if hasattr(settings, 'BASE_DIR') else Path.cwd() # Locate every test file test_files = list(base_dir.rglob('test*.py')) test_files.extend(list(base_dir.rglob('tests.py'))) test_dirs = list(base_dir.rglob('tests/')) if not test_files and not test_dirs: self.critical.append("No test files found in the project") self.suggestions.append("Write tests to guarantee code quality") return self.get_result() # Analyze test files total_test_functions = 0 total_test_lines = 0 for test_file in test_files: if 'migrations' in test_file.parts or 'venv' in test_file.parts or '.venv' in test_file.parts: continue try: with open(test_file, 'r', encoding='utf-8') as f: content = f.read() total_test_lines += len(content.splitlines()) tree = ast.parse(content) for node in ast.walk(tree): if isinstance(node, ast.FunctionDef): if node.name.startswith('test_'): total_test_functions += 1 except Exception as e: self.warnings.append(f"Error analyzing {test_file}: {str(e)}") # Statistics if total_test_functions == 0: self.critical.append("No test functions found") elif total_test_functions < 10: self.warnings.append(f"Only {total_test_functions} tests found - coverage is likely insufficient") else: self.good_practices.append(f"{total_test_functions} test functions found") # Check for pytest or unittest requirements_file = base_dir / 'requirements.txt' if requirements_file.exists(): with open(requirements_file, 'r') as f: deps = f.read().lower() if 'pytest' in deps: self.good_practices.append("pytest is configured") elif 'unittest' not in deps: self.suggestions.append("Install pytest for a better testing experience") # Check for coverage if requirements_file.exists(): with open(requirements_file, 'r') as f: deps = f.read().lower() if 'coverage' in deps: self.good_practices.append("coverage is installed") else: self.suggestions.append("Install coverage to measure test coverage") return self.get_result()
[docs] class AppsAuditor(BaseAuditor): """ Auditor for analyzing Django app structure and organization. Validates Django app configurations and checks for proper modular organization. Checks include: - Installed apps analysis - App structure validation - Required files in apps - Agent integration """
[docs] def audit(self): """ Run the Django apps audit. Returns: dict: Audit results with app structure analysis. """ installed_apps = getattr(settings, 'INSTALLED_APPS', []) if not installed_apps: self.critical.append("No applications are installed") return self.get_result() # Split Django apps from project apps django_apps = [app for app in installed_apps if app.startswith('django.')] third_party_apps = [app for app in installed_apps if not app.startswith('django.') and '.' in app] project_apps = [app for app in installed_apps if not app.startswith('django.') and '.' not in app] self.good_practices.append(f"Django applications: {len(django_apps)}") self.good_practices.append(f"Third-party applications: {len(third_party_apps)}") self.good_practices.append(f"Project applications: {len(project_apps)}") # Check the structure of each project app for app_name in project_apps: try: app_config = apps.get_app_config(app_name) app_path = Path(app_config.path) # Verify required files required_files = ['__init__.py', 'models.py', 'views.py'] for file in required_files: if not (app_path / file).exists(): self.warnings.append(f"App {app_name}: missing {file}") # Verify recommended files recommended_files = ['tests.py', 'urls.py', 'admin.py', 'apps.py'] for file in recommended_files: if (app_path / file).exists(): self.good_practices.append(f"App {app_name}: {file} is present") else: self.suggestions.append(f"App {app_name}: create {file}") # Check for agents if (app_path / 'agents.py').exists(): self.good_practices.append(f"App {app_name}: agents.py is present") else: self.suggestions.append(f"App {app_name}: create agents.py for smart agents") except LookupError: self.warnings.append(f"App {app_name} not found") # Modularity suggestions if len(project_apps) == 0: self.warnings.append("No project applications - consider modularizing the code") elif len(project_apps) == 1: self.suggestions.append("Only one project app - consider splitting it into functional modules") return self.get_result()
[docs] class SettingsAuditor(BaseAuditor): """ Auditor for analyzing Django settings and security configuration. Performs security-focused analysis of Django settings including DEBUG mode, SECRET_KEY, security middleware, and database configuration. Checks include: - DEBUG mode status - SECRET_KEY security - ALLOWED_HOSTS configuration - Security middleware presence - Database configuration - Static/media files setup """
[docs] def audit(self): """ Run the Django settings audit. Returns: dict: Audit results with security and configuration analysis. """ # Check DEBUG debug_mode = getattr(settings, 'DEBUG', True) if debug_mode: self.warnings.append("DEBUG is enabled - disable it in production") else: self.good_practices.append("DEBUG is disabled") # Check SECRET_KEY secret_key = getattr(settings, 'SECRET_KEY', '') if not secret_key: self.critical.append("SECRET_KEY is not set") elif secret_key == 'django-insecure-' or 'changeme' in secret_key.lower(): self.critical.append("SECRET_KEY uses an insecure default") elif len(secret_key) < 50: self.warnings.append("SECRET_KEY is too short (< 50 characters)") else: self.good_practices.append("SECRET_KEY is properly set") # Check ALLOWED_HOSTS allowed_hosts = getattr(settings, 'ALLOWED_HOSTS', []) if not allowed_hosts or allowed_hosts == ['*']: self.warnings.append("ALLOWED_HOSTS is not configured or too permissive") else: self.good_practices.append(f"ALLOWED_HOSTS is configured ({len(allowed_hosts)} hosts)") # Check security middleware middleware = getattr(settings, 'MIDDLEWARE', []) security_middleware = [ 'django.middleware.security.SecurityMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] for mw in security_middleware: if mw in middleware: self.good_practices.append(f"{mw.split('.')[-1]} enabled") else: self.warnings.append(f"{mw} is missing from MIDDLEWARE") # Check database configuration databases = getattr(settings, 'DATABASES', {}) if not databases: self.critical.append("No database configured") else: default_db = databases.get('default', {}) db_engine = default_db.get('ENGINE', '') if 'sqlite' in db_engine.lower(): self.warnings.append("SQLite is not recommended in production") elif 'postgresql' in db_engine.lower(): self.good_practices.append("PostgreSQL is configured") elif 'mysql' in db_engine.lower(): self.good_practices.append("MySQL is configured") # Check templates configuration templates = getattr(settings, 'TEMPLATES', []) if templates: self.good_practices.append("Templates are configured") # Check static files static_url = getattr(settings, 'STATIC_URL', None) if not static_url: self.warnings.append("STATIC_URL is not configured") else: self.good_practices.append("STATIC_URL is configured") static_root = getattr(settings, 'STATIC_ROOT', None) if not static_root: self.suggestions.append("STATIC_ROOT is not configured - required for collectstatic") # Check media configuration media_url = getattr(settings, 'MEDIA_URL', None) media_root = getattr(settings, 'MEDIA_ROOT', None) if media_url and media_root: self.good_practices.append("MEDIA is configured") elif media_url or media_root: self.warnings.append("MEDIA configuration is incomplete") return self.get_result()
[docs] class ProductionAuditor(BaseAuditor): """ Auditor for production deployment readiness. Analyzes the project for production deployment requirements including HTTPS configuration, deployment files, and security settings. Checks include: - DEBUG mode verification - Deployment configuration files - HTTPS/SSL settings - HSTS configuration - Logging configuration - Environment variable templates """
[docs] def audit(self): """ Run the production readiness audit. Returns: dict: Audit results with production readiness analysis. """ base_dir = Path(settings.BASE_DIR) if hasattr(settings, 'BASE_DIR') else Path.cwd() # Check DEBUG if getattr(settings, 'DEBUG', True): self.critical.append("DEBUG=True in production is dangerous") # Check deployment files deployment_files = { 'Dockerfile': 'Docker', 'docker-compose.yml': 'Docker Compose', 'requirements.txt': 'Requirements', 'Procfile': 'Heroku', 'gunicorn.conf.py': 'Gunicorn', 'nginx.conf': 'Nginx', 'supervisord.conf': 'Supervisor', } for file, tech in deployment_files.items(): if (base_dir / file).exists(): self.good_practices.append(f"{file} is present ({tech})") if not any((base_dir / file).exists() for file in deployment_files): self.warnings.append("No deployment files found") self.suggestions.append("Create deployment configuration files (Docker, Gunicorn, etc.)") # Check environment variable templates if (base_dir / '.env.example').exists(): self.good_practices.append(".env.example is present") else: self.suggestions.append("Create .env.example to document environment variables") # Check HTTPS settings secure_ssl_redirect = getattr(settings, 'SECURE_SSL_REDIRECT', False) if not secure_ssl_redirect: self.warnings.append("SECURE_SSL_REDIRECT is not enabled") session_cookie_secure = getattr(settings, 'SESSION_COOKIE_SECURE', False) if not session_cookie_secure: self.warnings.append("SESSION_COOKIE_SECURE is not enabled") csrf_cookie_secure = getattr(settings, 'CSRF_COOKIE_SECURE', False) if not csrf_cookie_secure: self.warnings.append("CSRF_COOKIE_SECURE is not enabled") # Check HSTS secure_hsts_seconds = getattr(settings, 'SECURE_HSTS_SECONDS', 0) if secure_hsts_seconds == 0: self.warnings.append("SECURE_HSTS_SECONDS is not configured") elif secure_hsts_seconds < 31536000: self.suggestions.append("SECURE_HSTS_SECONDS should be at least 31536000 (1 year)") else: self.good_practices.append("HSTS is properly configured") # Check logging configuration logging_config = getattr(settings, 'LOGGING', None) if not logging_config: self.warnings.append("LOGGING is not configured") self.suggestions.append("Configure LOGGING to monitor the application in production") else: self.good_practices.append("LOGGING is configured") return self.get_result()
[docs] class DatabaseAuditor(BaseAuditor): """ Auditor for database models and migrations. Analyzes Django models, migrations, and database optimization. Checks include: - Model count and structure - __str__ method presence - Migrations existence - Database indexes """
[docs] def audit(self): """ Run the database audit. Returns: dict: Audit results with database analysis. """ # Inspect models all_models = apps.get_models() if not all_models: self.warnings.append("No Django models found") return self.get_result() self.good_practices.append(f"{len(all_models)} models found") # Analyze each model models_without_str = [] models_without_meta = [] for model in all_models: model_name = model.__name__ # Check __str__ if not hasattr(model, '__str__') or model.__str__ is object.__str__: models_without_str.append(model_name) # Check Meta options if not hasattr(model, '_meta') or not hasattr(model, 'Meta'): models_without_meta.append(model_name) if models_without_str: self.suggestions.append( f"{len(models_without_str)} models without __str__: {', '.join(models_without_str[:3])}..." ) # Check migrations base_dir = Path(settings.BASE_DIR) if hasattr(settings, 'BASE_DIR') else Path.cwd() migration_dirs = list(base_dir.rglob('migrations/')) if not migration_dirs: self.warnings.append("No migrations directory found") else: total_migrations = 0 for migration_dir in migration_dirs: migrations = list(migration_dir.glob('*.py')) migrations = [m for m in migrations if m.name != '__init__.py'] total_migrations += len(migrations) if total_migrations == 0: self.warnings.append("No migrations found - run makemigrations") else: self.good_practices.append(f"{total_migrations} migrations found") # Check indexes models_with_indexes = [] for model in all_models: if hasattr(model, '_meta') and hasattr(model._meta, 'indexes'): if model._meta.indexes: models_with_indexes.append(model.__name__) if models_with_indexes: self.good_practices.append( f"{len(models_with_indexes)} models with custom indexes" ) else: self.suggestions.append( "Add indexes to frequently queried fields to improve performance" ) return self.get_result()
[docs] class ServerAuditor(BaseAuditor): """ Auditor for server and deployment configuration. Analyzes server setup including WSGI/ASGI, web servers, and caching configuration. Checks include: - WSGI/ASGI file presence - Gunicorn installation - Static file serving (WhiteNoise) - Server configuration files - Cache configuration """
[docs] def audit(self): """ Run the server audit. Returns: dict: Audit results with server configuration analysis. """ base_dir = Path(settings.BASE_DIR) if hasattr(settings, 'BASE_DIR') else Path.cwd() # Check wsgi.py and asgi.py if (base_dir / 'wsgi.py').exists() or any(base_dir.rglob('wsgi.py')): self.good_practices.append("wsgi.py is present") else: self.warnings.append("wsgi.py not found") if (base_dir / 'asgi.py').exists() or any(base_dir.rglob('asgi.py')): self.good_practices.append("asgi.py is present (async support)") # Check Gunicorn requirements_file = base_dir / 'requirements.txt' if requirements_file.exists(): with open(requirements_file, 'r') as f: deps = f.read().lower() if 'gunicorn' in deps: self.good_practices.append("Gunicorn is installed") else: self.suggestions.append("Install Gunicorn to serve the application in production") if 'whitenoise' in deps: self.good_practices.append("WhiteNoise is installed (static files)") else: self.suggestions.append("Install WhiteNoise to serve static files") # Check server configuration files server_configs = { 'gunicorn.conf.py': 'Gunicorn configuration', 'nginx.conf': 'Nginx configuration', 'apache.conf': 'Apache configuration', } for config_file, description in server_configs.items(): if (base_dir / config_file).exists(): self.good_practices.append(f"{description} is present") # Check async middleware middleware = getattr(settings, 'MIDDLEWARE', []) if any('async' in mw.lower() for mw in middleware): self.good_practices.append("Async middleware detected") # Check cache configuration caches = getattr(settings, 'CACHES', {}) if caches: default_cache = caches.get('default', {}) backend = default_cache.get('BACKEND', '') if 'dummy' in backend.lower(): self.warnings.append("Dummy cache configured - use Redis or Memcached in production") elif 'redis' in backend.lower(): self.good_practices.append("Redis is configured for caching") elif 'memcached' in backend.lower(): self.good_practices.append("Memcached is configured for caching") else: self.suggestions.append("Configure a cache system (Redis, Memcached) to improve performance") return self.get_result()