mirror of
https://github.com/ansible/awx.git
synced 2026-01-08 14:32:07 -03:30
* Requirements POC docs from Claude Code eval * Removed unnecessary reference. * Excluded custom DRF configurations per @AlanCoding * Implement review changes from @chrismeyersfsu --------- Co-authored-by: Peter Braun <pbraun@redhat.com>
725 lines
19 KiB
ReStructuredText
725 lines
19 KiB
ReStructuredText
=====================================================
|
|
Django Development Requirements
|
|
=====================================================
|
|
|
|
**AWX Codebase Best Practices**
|
|
|
|
:Version: 1.0
|
|
:Date: September 2025
|
|
:Based on: AWX Enterprise Django Application Analysis
|
|
:Generated by: Claude Code AI
|
|
|
|
----
|
|
|
|
.. contents:: Table of Contents
|
|
:depth: 3
|
|
:local:
|
|
|
|
----
|
|
|
|
1. Project Structure
|
|
====================
|
|
|
|
1.1 Modular Application Architecture
|
|
------------------------------------
|
|
|
|
**REQUIRED**: Organize Django project with clear separation of concerns::
|
|
|
|
awx/
|
|
├── __init__.py # Version management and environment detection
|
|
├── main/ # Core business logic and models
|
|
├── api/ # REST API layer (Django REST Framework)
|
|
├── ui/ # Frontend integration
|
|
├── conf/ # Configuration management
|
|
├── settings/ # Environment-specific settings
|
|
├── templates/ # Django templates
|
|
└── static/ # Static assets
|
|
|
|
**Requirements**:
|
|
|
|
- Each functional area must have its own Django app
|
|
- Use descriptive app names that reflect business domains
|
|
- Separate API logic from core business logic
|
|
|
|
1.2 Pre-Management Command Code
|
|
--------------------------------
|
|
|
|
This section describes the code that runs before every management command.
|
|
|
|
AWX persistent services (i.e. wsrelay, heartbeat, dispatcher) all have management commands as entry points. So if you want to write a new persistent service, make a management command.
|
|
|
|
System jobs are implemented as management commands too.
|
|
|
|
|
|
**REQUIRED**: Implement custom Django management integration:
|
|
|
|
.. code-block:: python
|
|
|
|
# awx/__init__.py
|
|
def manage():
|
|
"""Custom management function with environment preparation"""
|
|
prepare_env()
|
|
from django.core.management import execute_from_command_line
|
|
|
|
# Version validation for production
|
|
if not MODE == 'development':
|
|
validate_production_requirements()
|
|
|
|
execute_from_command_line(sys.argv)
|
|
|
|
**Requirements**:
|
|
|
|
- Environment detection (development/production modes)
|
|
- Production deployment validation
|
|
- Custom version checking mechanisms
|
|
- Database version compatibility checks
|
|
|
|
----
|
|
|
|
2. Settings Management
|
|
======================
|
|
|
|
2.1 Environment-Based Settings Architecture
|
|
-------------------------------------------
|
|
|
|
**REQUIRED**: Use ``django-split-settings`` for modular configuration::
|
|
|
|
# settings/defaults.py - Base configuration
|
|
# settings/development.py - Development overrides
|
|
# settings/production.py - Production security settings
|
|
# settings/testing.py - Test-specific configuration
|
|
|
|
**Settings Pattern**:
|
|
|
|
.. code-block:: python
|
|
|
|
# development.py
|
|
from .defaults import *
|
|
from split_settings.tools import optional, include
|
|
|
|
DEBUG = True
|
|
ALLOWED_HOSTS = ['*']
|
|
|
|
# Include optional local settings
|
|
include(optional('local_settings.py'))
|
|
|
|
2.2 Sourcing config from files
|
|
-------------------------------
|
|
|
|
**REQUIRED**: Sourcing config from multiple files (in a directory) on disk:
|
|
|
|
.. code-block:: python
|
|
|
|
# External settings loading
|
|
EXTERNAL_SETTINGS = os.environ.get('AWX_SETTINGS_FILE')
|
|
if EXTERNAL_SETTINGS:
|
|
include(EXTERNAL_SETTINGS, scope=locals())
|
|
|
|
|
|
3. URL Patterns and Routing
|
|
============================
|
|
|
|
3.1 Modular URL Architecture
|
|
-----------------------------
|
|
|
|
**REQUIRED**: Implement hierarchical URL organization with namespacing:
|
|
|
|
.. code-block:: python
|
|
|
|
# urls.py
|
|
def get_urlpatterns(prefix=None):
|
|
"""Dynamic URL pattern generation with prefix support"""
|
|
if not prefix:
|
|
prefix = '/'
|
|
else:
|
|
prefix = f'/{prefix}/'
|
|
|
|
return [
|
|
path(f'api{prefix}', include('awx.api.urls', namespace='api')),
|
|
path(f'ui{prefix}', include('awx.ui.urls', namespace='ui')),
|
|
]
|
|
|
|
urlpatterns = get_urlpatterns()
|
|
|
|
3.2 Environment-Specific URL Inclusion
|
|
--------------------------------------
|
|
|
|
**REQUIRED**: Conditional URL patterns based on environment:
|
|
|
|
This example allows the Django debug toolbar to work.
|
|
|
|
.. code-block:: python
|
|
|
|
# Development-only URLs
|
|
if settings.DEBUG:
|
|
try:
|
|
import debug_toolbar
|
|
urlpatterns += [path('__debug__/', include(debug_toolbar.urls))]
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
**OPTIONAL**: If you want to include your own debug logic and endpoints:
|
|
|
|
.. code-block:: python
|
|
|
|
if MODE == 'development':
|
|
# Only include these if we are in the development environment
|
|
from awx.api.swagger import schema_view
|
|
|
|
from awx.api.urls.debug import urls as debug_urls
|
|
|
|
urlpatterns += [re_path(r'^debug/', include(debug_urls))]
|
|
urlpatterns += [
|
|
re_path(r'^swagger(?P<format>\.json|\.yaml)/$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
|
re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
|
re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
|
]
|
|
|
|
**Requirements**:
|
|
|
|
- Use Django's ``include()`` for modular organization
|
|
- Implement URL namespacing for API versioning
|
|
- Support dynamic URL prefix configuration
|
|
- Separate URL patterns by functional area
|
|
|
|
----
|
|
|
|
4. Model Design
|
|
===============
|
|
|
|
4.1 Abstract Base Models
|
|
------------------------
|
|
|
|
**REQUIRED**: Use abstract base models for common functionality:
|
|
|
|
.. code-block:: python
|
|
|
|
# models/base.py
|
|
class BaseModel(models.Model):
|
|
"""Common fields and methods for all models"""
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
modified = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
class AuditableModel(BaseModel):
|
|
"""Models requiring audit trail"""
|
|
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
4.2 Mixin-Based Architecture
|
|
----------------------------
|
|
|
|
**REQUIRED**: Implement reusable model behaviors through mixins:
|
|
|
|
.. code-block:: python
|
|
|
|
# models/mixins.py
|
|
class ResourceMixin(models.Model):
|
|
"""Common resource management functionality"""
|
|
class Meta:
|
|
abstract = True
|
|
|
|
class ExecutionEnvironmentMixin(models.Model):
|
|
"""Execution environment configuration"""
|
|
class Meta:
|
|
abstract = True
|
|
|
|
4.3 Model Organization
|
|
----------------------
|
|
|
|
**REQUIRED**: Organize models by domain functionality::
|
|
|
|
models/
|
|
├── __init__.py
|
|
├── base.py # Abstract base models
|
|
├── mixins.py # Reusable model behaviors
|
|
├── inventory.py # Inventory-related models
|
|
├── jobs.py # Job execution models
|
|
├── credential.py # Credential management
|
|
└── organization.py # Organization models
|
|
|
|
**Requirements**:
|
|
|
|
- One file per logical domain until the domain gets too big, create a folder for it instead. In the past, credentials were broken out into logical domains until they were moved out of AWX, then they were collapsed back down to a single file.
|
|
- Use consistent naming conventions
|
|
- Implement comprehensive model validation
|
|
- Custom managers for complex queries
|
|
|
|
----
|
|
|
|
5. REST API Development
|
|
=======================
|
|
|
|
5.1 Custom Authentication Classes
|
|
----------------------------------
|
|
|
|
The recommended best practice is to log all of the terminal (return) paths of authentication, not just the successful ones.
|
|
|
|
**REQUIRED**: Implement domain-specific authentication with logging:
|
|
|
|
.. code-block:: python
|
|
|
|
# api/authentication.py
|
|
class LoggedBasicAuthentication(authentication.BasicAuthentication):
|
|
"""Basic authentication with request logging"""
|
|
|
|
def authenticate(self, request):
|
|
if not settings.AUTH_BASIC_ENABLED:
|
|
return
|
|
|
|
ret = super().authenticate(request)
|
|
if ret:
|
|
username = ret[0].username if ret[0] else '<none>'
|
|
logger.info(
|
|
f"User {username} performed {request.method} "
|
|
f"to {request.path} through the API"
|
|
)
|
|
return ret
|
|
|
|
5.2 Custom Permission Classes
|
|
-----------------------------
|
|
|
|
**REQUIRED**: Implement comprehensive permission checking:
|
|
|
|
.. code-block:: python
|
|
|
|
# api/permissions.py
|
|
class ModelAccessPermission(permissions.BasePermission):
|
|
"""Model-based access control with hierarchy support"""
|
|
|
|
def has_permission(self, request, view):
|
|
if hasattr(view, 'parent_model'):
|
|
parent_obj = view.get_parent_object()
|
|
return check_user_access(
|
|
request.user,
|
|
view.parent_model,
|
|
'read',
|
|
parent_obj
|
|
)
|
|
return True
|
|
|
|
**Requirements**:
|
|
|
|
- Multiple authentication methods (JWT, Session, Basic)
|
|
- Custom pagination, renderers, and metadata classes
|
|
- Comprehensive API exception handling
|
|
- Resource-based URL organization
|
|
- Logging for authentication events
|
|
|
|
----
|
|
|
|
6. Security Requirements
|
|
========================
|
|
|
|
6.1 Production Security Settings
|
|
--------------------------------
|
|
|
|
**REQUIRED**: Enforce secure defaults for production:
|
|
|
|
.. code-block:: python
|
|
|
|
# settings/production.py
|
|
DEBUG = False
|
|
SECRET_KEY = None # Force explicit configuration
|
|
ALLOWED_HOSTS = [] # Must be explicitly set
|
|
|
|
# Session security
|
|
SESSION_COOKIE_SECURE = True
|
|
SESSION_COOKIE_HTTPONLY = True
|
|
SESSION_COOKIE_SAMESITE = 'Lax'
|
|
SESSION_COOKIE_AGE = 1800
|
|
|
|
# CSRF protection
|
|
CSRF_COOKIE_SECURE = True
|
|
CSRF_COOKIE_HTTPONLY = True
|
|
CSRF_TRUSTED_ORIGINS = []
|
|
|
|
6.2 Django SECRET_KEY loading
|
|
------------------------------
|
|
|
|
**REQUIRED**: Implement Django SECRET_KEY loading:
|
|
|
|
.. code-block:: python
|
|
|
|
# Secret key from external file
|
|
SECRET_KEY_FILE = os.environ.get('SECRET_KEY_FILE', '/etc/awx/SECRET_KEY')
|
|
if os.path.exists(SECRET_KEY_FILE):
|
|
with open(SECRET_KEY_FILE, 'rb') as f:
|
|
SECRET_KEY = f.read().strip().decode()
|
|
else:
|
|
if not DEBUG:
|
|
raise ImproperlyConfigured("SECRET_KEY must be configured in production")
|
|
|
|
For more detail, refer to the `Django documentation <https://docs.djangoproject.com/en/5.2/ref/settings/#secret-key>`_.
|
|
|
|
6.3 Proxy and Network Security
|
|
------------------------------
|
|
|
|
**REQUIRED**: Configure reverse proxy security:
|
|
|
|
.. code-block:: python
|
|
|
|
# Proxy configuration
|
|
REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST']
|
|
PROXY_IP_ALLOWED_LIST = []
|
|
USE_X_FORWARDED_HOST = True
|
|
USE_X_FORWARDED_PORT = True
|
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
|
|
|
**Requirements**:
|
|
|
|
- External secret file management
|
|
- Secure cookie configuration
|
|
- CSRF protection with trusted origins
|
|
- Proxy header validation
|
|
- Force HTTPS in production
|
|
|
|
----
|
|
|
|
7. Database Management
|
|
======================
|
|
|
|
7.1 Advanced Database Configuration
|
|
-----------------------------------
|
|
|
|
**REQUIRED**: Robust database connections for production:
|
|
|
|
.. code-block:: python
|
|
|
|
# Database configuration with connection tuning
|
|
DATABASES = {
|
|
'default': {
|
|
'ENGINE': 'django.db.backends.postgresql',
|
|
'NAME': os.environ.get('DATABASE_NAME', 'awx'),
|
|
'ATOMIC_REQUESTS': True,
|
|
'CONN_MAX_AGE': 0,
|
|
'OPTIONS': {
|
|
'keepalives': 1,
|
|
'keepalives_idle': 5,
|
|
'keepalives_interval': 5,
|
|
'keepalives_count': 5,
|
|
},
|
|
}
|
|
}
|
|
|
|
7.2 Database Version Validation
|
|
-------------------------------
|
|
|
|
**REQUIRED**: Implement database compatibility checking:
|
|
|
|
.. code-block:: python
|
|
|
|
# PostgreSQL version enforcement
|
|
def validate_database_version():
|
|
from django.db import connection
|
|
if (connection.pg_version // 10000) < 12:
|
|
raise ImproperlyConfigured(
|
|
"PostgreSQL version 12 or higher is required"
|
|
)
|
|
|
|
7.3 Migration Management
|
|
------------------------
|
|
|
|
**REQUIRED**: Structured migration organization
|
|
|
|
::
|
|
|
|
migrations/
|
|
├── 0001_initial.py
|
|
├── 0002_squashed_v300_release.py
|
|
├── 0003_squashed_v300_v303_updates.py
|
|
└── _migration_utils.py
|
|
|
|
**Requirements**:
|
|
It is best practice to not to re-write migrations. If possible, include a reverse migration, especially for data migrations to make testing easier.
|
|
|
|
----
|
|
|
|
8. Testing Standards
|
|
====================
|
|
|
|
8.1 Pytest Configuration
|
|
-------------------------
|
|
|
|
**REQUIRED**: Comprehensive test setup with optimization:
|
|
|
|
.. code-block:: ini
|
|
|
|
# pytest.ini
|
|
[pytest]
|
|
DJANGO_SETTINGS_MODULE = awx.main.tests.settings_for_test
|
|
python_files = *.py
|
|
addopts = --reuse-db --nomigrations --tb=native
|
|
markers =
|
|
ac: access control test
|
|
survey: tests related to survey feature
|
|
inventory_import: tests of code used by inventory import command
|
|
integration: integration tests requiring external services
|
|
|
|
8.2 Test Settings Module
|
|
-------------------------
|
|
|
|
**REQUIRED**: Dedicated test configuration:
|
|
|
|
.. code-block:: python
|
|
|
|
# settings/testing.py
|
|
from .defaults import *
|
|
|
|
# Fast test database
|
|
DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
|
|
DATABASES['default']['NAME'] = ':memory:'
|
|
|
|
# Disable migrations for speed
|
|
class DisableMigrations:
|
|
def __contains__(self, item):
|
|
return True
|
|
def __getitem__(self, item):
|
|
return None
|
|
|
|
MIGRATION_MODULES = DisableMigrations()
|
|
|
|
8.3 Coverage Requirements
|
|
-------------------------
|
|
|
|
**REQUIRED**: Enforce comprehensive test coverage:
|
|
|
|
.. code-block:: python
|
|
|
|
# Coverage targets
|
|
COVERAGE_TARGETS = {
|
|
'project_overall': 75,
|
|
'library_code': 75,
|
|
'test_code': 95,
|
|
'new_patches': 100,
|
|
'type_checking': 100,
|
|
}
|
|
|
|
**Requirements**:
|
|
|
|
- Database reuse for faster execution
|
|
- Skip migrations in tests
|
|
- Custom test markers for categorization
|
|
- Dedicated test settings module
|
|
- Comprehensive warning filters
|
|
|
|
----
|
|
|
|
9. Application Configuration
|
|
=============================
|
|
|
|
9.1 Advanced AppConfig Implementation
|
|
--------------------------------------
|
|
|
|
**REQUIRED**: Custom application configuration with initialization:
|
|
|
|
.. code-block:: python
|
|
|
|
# apps.py
|
|
class MainConfig(AppConfig):
|
|
name = 'awx.main'
|
|
verbose_name = _('Main')
|
|
default_auto_field = 'django.db.models.AutoField'
|
|
|
|
def ready(self):
|
|
super().ready()
|
|
|
|
# Feature loading with environment checks
|
|
if not os.environ.get('AWX_SKIP_FEATURES', None):
|
|
self.load_credential_types()
|
|
self.load_inventory_plugins()
|
|
self.load_named_urls()
|
|
|
|
# Signal registration
|
|
self.register_signals()
|
|
|
|
def load_credential_types(self):
|
|
"""Load credential type definitions"""
|
|
pass
|
|
|
|
def register_signals(self):
|
|
"""Register Django signals"""
|
|
pass
|
|
|
|
**Requirements**:
|
|
|
|
- Custom AppConfig for complex initialization
|
|
- Feature loading in ``ready()`` method
|
|
- Environment-based feature toggling
|
|
- Plugin system integration
|
|
- Signal registration
|
|
|
|
----
|
|
|
|
10. Middleware Implementation
|
|
=============================
|
|
|
|
10.1 Custom Middleware for Enterprise Features
|
|
----------------------------------------------
|
|
|
|
**REQUIRED**: Implement domain-specific middleware:
|
|
|
|
.. code-block:: python
|
|
|
|
# middleware.py
|
|
class SettingsCacheMiddleware(MiddlewareMixin):
|
|
"""Clear settings cache on each request"""
|
|
|
|
def process_request(self, request):
|
|
from django.conf import settings
|
|
if hasattr(settings, '_awx_conf_memoizedcache'):
|
|
settings._awx_conf_memoizedcache.clear()
|
|
|
|
class TimingMiddleware(threading.local, MiddlewareMixin):
|
|
"""Request timing and performance monitoring"""
|
|
|
|
def process_request(self, request):
|
|
self.start_time = time.time()
|
|
|
|
def process_response(self, request, response):
|
|
if hasattr(self, 'start_time'):
|
|
duration = time.time() - self.start_time
|
|
response['X-Response-Time'] = f"{duration:.3f}s"
|
|
return response
|
|
|
|
**Requirements**:
|
|
|
|
- Settings cache management middleware
|
|
- Performance monitoring middleware
|
|
- Thread-local storage for request data
|
|
- Conditional middleware activation
|
|
|
|
----
|
|
|
|
11. Deployment Patterns
|
|
========================
|
|
|
|
11.1 Production-Ready ASGI/WSGI Configuration
|
|
---------------------------------------------
|
|
|
|
**REQUIRED**: Proper application server setup:
|
|
|
|
.. code-block:: python
|
|
|
|
# asgi.py
|
|
import os
|
|
import django
|
|
from channels.routing import get_default_application
|
|
from awx import prepare_env
|
|
|
|
prepare_env()
|
|
django.setup()
|
|
|
|
application = get_default_application()
|
|
|
|
# wsgi.py
|
|
import os
|
|
from django.core.wsgi import get_wsgi_application
|
|
from awx import prepare_env
|
|
|
|
prepare_env()
|
|
application = get_wsgi_application()
|
|
|
|
----
|
|
|
|
Compliance Checklist
|
|
=====================
|
|
|
|
Development Standards
|
|
---------------------
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 50 10
|
|
|
|
* - Requirement
|
|
- Status
|
|
* - Modular app architecture implemented
|
|
- ☐
|
|
* - Environment-based settings configured
|
|
- ☐
|
|
* - Custom authentication and permissions
|
|
- ☐
|
|
* - Comprehensive test coverage (>75%)
|
|
- ☐
|
|
* - Security settings enforced
|
|
- ☐
|
|
* - Database optimization configured
|
|
- ☐
|
|
* - Static files properly organized
|
|
- ☐
|
|
* - Custom middleware implemented
|
|
- ☐
|
|
|
|
Production Readiness
|
|
--------------------
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 50 10
|
|
|
|
* - Requirement
|
|
- Status
|
|
* - External secret management
|
|
- ☐
|
|
* - Database version validation
|
|
- ☐
|
|
* - Version deployment verification
|
|
- ☐
|
|
* - Performance monitoring
|
|
- ☐
|
|
* - Security headers configured
|
|
- ☐
|
|
* - HTTPS enforcement
|
|
- ☐
|
|
* - Proper logging setup
|
|
- ☐
|
|
* - Error handling and monitoring
|
|
- ☐
|
|
|
|
Code Quality
|
|
------------
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 50 10
|
|
|
|
* - Requirement
|
|
- Status
|
|
* - Abstract base models used
|
|
- ☐
|
|
* - Mixin-based architecture
|
|
- ☐
|
|
* - Custom management commands
|
|
- ☐
|
|
* - Plugin system support
|
|
- ☐
|
|
* - Signal registration
|
|
- ☐
|
|
* - Migration organization
|
|
- ☐
|
|
* - API documentation
|
|
- ☐
|
|
* - Type hints and validation
|
|
- ☐
|
|
|
|
----
|
|
|
|
References
|
|
==========
|
|
|
|
- **Django Documentation**: https://docs.djangoproject.com/
|
|
- **Django REST Framework**: https://www.django-rest-framework.org/
|
|
- **Django Split Settings**: https://github.com/sobolevn/django-split-settings
|
|
- **AWX Source Code**: https://github.com/ansible/awx
|
|
|
|
----
|
|
|
|
| **Document Maintainer**: Development Team
|
|
| **Last Updated**: September 2025
|
|
| **Review Schedule**: Quarterly |