Development Guide#
This guide covers development setup, testing, and contribution guidelines for the Nopayloaddb project.
Development Environment#
Prerequisites#
Python 3.9 or higher
PostgreSQL 12 or higher
Git
Docker and Docker Compose (for containerized development)
Local Development Setup#
1. Clone and Setup
git clone https://github.com/BNLNPPS/nopayloaddb.git
cd nopayloaddb
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
2. Database Setup
Create PostgreSQL database and user:
CREATE DATABASE nopayloaddb_dev;
CREATE USER npdb_dev WITH PASSWORD 'dev_password';
GRANT ALL PRIVILEGES ON DATABASE nopayloaddb_dev TO npdb_dev;
3. Environment Configuration
Create a .env
file:
export SECRET_KEY='your-development-secret-key-here'
export DJANGO_LOGPATH='/tmp'
# Database configuration
export POSTGRES_DB_W=nopayloaddb_dev
export POSTGRES_USER_W=npdb_dev
export POSTGRES_PASSWORD_W=dev_password
export POSTGRES_HOST_W=localhost
export POSTGRES_PORT_W=5432
# For read replicas (optional in development)
export POSTGRES_DB_R1=nopayloaddb_dev
export POSTGRES_USER_R1=npdb_dev
export POSTGRES_PASSWORD_R1=dev_password
export POSTGRES_HOST_R1=localhost
export POSTGRES_PORT_R1=5432
4. Run Migrations and Start Development Server
source .env
python manage.py migrate
python manage.py runserver
Docker Development Setup#
For a more consistent development environment:
git clone https://github.com/BNLNPPS/nopayloaddb.git
cd nopayloaddb
# Configure .env for Docker
cat > .env << EOF
SECRET_KEY='docker-dev-secret-key'
DJANGO_LOGPATH='/npdb/logs'
POSTGRES_DB_W=nopayloaddb
POSTGRES_USER_W=npdb
POSTGRES_PASSWORD_W=password
POSTGRES_HOST_W=db
POSTGRES_PORT_W=5432
EOF
# Start services
docker-compose up --build
Code Structure#
Project Layout#
nopayloaddb/
├── nopayloaddb/ # Main Django project
│ ├── __init__.py
│ ├── settings.py # Main settings
│ ├── test_settings.py # Test-specific settings
│ ├── urls.py # Main URL routing
│ ├── wsgi.py # WSGI application
│ ├── db_router.py # Database routing logic
│ └── middleware.py # Custom middleware
├── cdb_rest/ # Main application
│ ├── __init__.py
│ ├── models.py # Database models
│ ├── views.py # API views
│ ├── serializers.py # DRF serializers
│ ├── urls.py # API URL routing
│ ├── queries.py # Custom queries
│ ├── authentication.py # Auth logic
│ └── migrations/ # Database migrations
├── docs/ # Documentation
├── requirements.txt # Python dependencies
├── manage.py # Django management
├── Dockerfile # Container definition
└── docker-compose.yml # Development services
Key Components#
Models (cdb_rest/models.py)
GlobalTag
: Named collections of payload versionsGlobalTagStatus
: Status management for global tagsPayloadType
: Categorization of payloadsPayloadList
: Links global tags to payload typesPayloadIOV
: Individual payloads with validity intervals
Views (cdb_rest/views.py)
RESTful API endpoints using Django REST Framework
Custom query views for complex IOV lookups
Bulk operations for efficient data loading
Database Router (nopayloaddb/db_router.py)
Handles read/write database splitting
Currently routes all operations to default database
Ready for read replica scaling
Testing#
Test Framework#
The project uses Django’s test framework with nose for enhanced testing capabilities.
Running Tests
# Run all tests
python manage.py test
# Run specific test module
python manage.py test cdb_rest.tests.test_models
# Run with coverage
python manage.py test --with-coverage --cover-package=cdb_rest
Test Configuration
Test settings are in nopayloaddb/test_settings.py
:
# Use nose for testing
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
# Coverage configuration
NOSE_ARGS = [
'--with-coverage',
'--cover-package=cdb_rest',
]
Writing Tests#
Model Tests
from django.test import TestCase
from cdb_rest.models import GlobalTag, GlobalTagStatus
class GlobalTagTestCase(TestCase):
def setUp(self):
self.status = GlobalTagStatus.objects.create(
name='TEST',
description='Test status'
)
def test_create_global_tag(self):
gt = GlobalTag.objects.create(
name='TestGT',
author='testuser',
description='Test global tag',
status=self.status
)
self.assertEqual(gt.name, 'TestGT')
self.assertEqual(str(gt), 'TestGT')
API Tests
from rest_framework.test import APITestCase
from rest_framework import status
class GlobalTagAPITestCase(APITestCase):
def test_create_global_tag(self):
url = '/api/cdb_rest/gt'
data = {
'name': 'TestGT',
'author': 'testuser',
'description': 'Test global tag',
'status': 1
}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
Database Management#
Migrations#
Creating Migrations
# Create migration for model changes
python manage.py makemigrations cdb_rest
# Apply migrations
python manage.py migrate
# Check migration status
python manage.py showmigrations
Migration Best Practices
Always create migrations for model changes
Review migration files before applying
Test migrations on development data
Use descriptive migration names
Database Initialization
# Create superuser
python manage.py createsuperuser
# Load initial data (if fixtures exist)
python manage.py loaddata initial_data.json
Performance Considerations#
Database Optimization#
Index Strategy
The models include optimized indexes:
class PayloadIOV(models.Model):
# ... fields ...
class Meta:
indexes = [
models.Index('payload_list', F('comb_iov').desc(nulls_last=True), name='covering_idx')
]
Query Optimization
Use
select_related()
for foreign key relationshipsUse
prefetch_related()
for reverse relationshipsAvoid N+1 queries in API endpoints
Custom Queries
Complex IOV queries use raw SQL for performance:
# In cdb_rest/queries.py
def get_payloads_by_iov(gt_name, major_iov, minor_iov):
cursor = connection.cursor()
cursor.execute("""
SELECT DISTINCT ... FROM PayloadIOV p
JOIN PayloadList pl ON p.payload_list_id = pl.id
WHERE pl.global_tag_id = %s AND ...
""", [gt_name, major_iov, minor_iov])
return cursor.fetchall()
Code Quality#
Code Style#
PEP 8 Compliance
Use 4 spaces for indentation
Line length limit of 79 characters
Use descriptive variable names
Follow Django naming conventions
Import Organization
# Standard library imports
import os
import sys
# Third-party imports
from django.db import models
from rest_framework import serializers
# Local imports
from cdb_rest.models import GlobalTag
Documentation#
Docstring Style
def get_payloads_by_iov(gt_name, major_iov, minor_iov, payload_type=None):
"""
Retrieve payloads for a specific global tag and IOV range.
Args:
gt_name (str): Name of the global tag
major_iov (int): Major IOV value
minor_iov (int): Minor IOV value
payload_type (str, optional): Filter by payload type
Returns:
QuerySet: Filtered payload IOVs
"""
pass
API Documentation
Use Django REST Framework’s built-in documentation features:
class GlobalTagListCreateAPIView(ListCreateAPIView):
"""
List all global tags or create a new global tag.
GET: Returns a list of all global tags
POST: Creates a new global tag
"""
queryset = GlobalTag.objects.all()
serializer_class = GlobalTagSerializer
Contributing#
Git Workflow#
Branch Strategy
master
: Main development branchfeature/feature-name
: Feature developmentbugfix/bug-description
: Bug fixeshotfix/urgent-fix
: Production hotfixes
Commit Messages
feat: add bulk payload IOV creation endpoint
- Add new endpoint for bulk creation of payload IOVs
- Improve performance for large data loads
- Add validation for bulk operations
Closes #123
Pull Request Process
Create feature branch from master
Make changes with appropriate tests
Ensure all tests pass
Update documentation if needed
Create pull request with description
Address code review feedback
Merge after approval
Code Review Guidelines#
Review Checklist
[ ] Code follows project style guidelines
[ ] Tests are included and passing
[ ] Documentation is updated
[ ] No sensitive information in code
[ ] Performance impact considered
[ ] Security implications reviewed
Common Issues to Check
SQL injection vulnerabilities
Proper error handling
Resource cleanup (database connections)
Appropriate HTTP status codes
Input validation
Development Tools#
Useful Commands#
Database Operations
# Reset database
python manage.py flush
# Dump data
python manage.py dumpdata cdb_rest --indent=2 > data.json
# Load data
python manage.py loaddata data.json
# Database shell
python manage.py dbshell
Development Server
# Run with debugging
python manage.py runserver --verbosity=2
# Run on specific port
python manage.py runserver 0.0.0.0:9000
Management Commands
# Django shell
python manage.py shell
# Create superuser
python manage.py createsuperuser
# Collect static files
python manage.py collectstatic
IDE Configuration#
VS Code Settings
{
"python.defaultInterpreterPath": "./venv/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.formatting.provider": "black",
"python.testing.pytestEnabled": false,
"python.testing.unittestEnabled": true,
"python.testing.unittestArgs": [
"-v",
"-s",
".",
"-p",
"test_*.py"
]
}
PyCharm Configuration
Set Python interpreter to virtual environment
Configure Django settings module:
nopayloaddb.settings
Enable Django support in project settings
Configure database connection for database tools
Debugging#
Common Issues#
Database Connection Issues
# Check database connectivity
from django.db import connection
cursor = connection.cursor()
cursor.execute("SELECT 1")
print(cursor.fetchone())
Migration Issues
# Check migration status
python manage.py showmigrations
# Fake migration (if needed)
python manage.py migrate --fake cdb_rest 0001_initial
Performance Issues
# Enable SQL logging
import logging
logging.basicConfig()
logging.getLogger('django.db.backends').setLevel(logging.DEBUG)
Debug Tools#
Django Debug Toolbar
Add to development requirements:
# In settings.py for development
if DEBUG:
INSTALLED_APPS.append('debug_toolbar')
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
INTERNAL_IPS = ['127.0.0.1']
Django Extensions
Useful for development:
pip install django-extensions
# Shell with IPython
python manage.py shell_plus
# Generate model graph
python manage.py graph_models -a -g -o models.png