- Created a base email template (base.html) for consistent styling across all emails. - Added moderation approval email template (moderation_approved.html) to notify users of approved submissions. - Added moderation rejection email template (moderation_rejected.html) to inform users of required changes for their submissions. - Created password reset email template (password_reset.html) for users requesting to reset their passwords. - Developed a welcome email template (welcome.html) to greet new users and provide account details and tips for using ThrillWiki.
8.6 KiB
PostGIS Integration - Dual-Mode Setup
Overview
ThrillWiki Django backend uses a conditional PostGIS setup that allows geographic data to work in both local development (SQLite) and production (PostgreSQL with PostGIS).
How It Works
Database Backends
-
Local Development: Uses regular SQLite without GIS extensions
- Geographic coordinates stored in
latitudeandlongitudeDecimalFields - No spatial query capabilities
- Simpler setup, easier for local development
- Geographic coordinates stored in
-
Production: Uses PostgreSQL with PostGIS extension
- Geographic coordinates stored in
location_pointPointField (PostGIS) - Full spatial query capabilities (distance calculations, geographic searches, etc.)
- Automatically syncs with legacy
latitude/longitudefields
- Geographic coordinates stored in
Model Implementation
The Park model uses conditional field definition:
# Conditionally import GIS models only if using PostGIS backend
_using_postgis = (
'postgis' in settings.DATABASES['default']['ENGINE']
)
if _using_postgis:
from django.contrib.gis.db import models as gis_models
from django.contrib.gis.geos import Point
Fields in SQLite mode:
latitude(DecimalField) - Primary coordinate storagelongitude(DecimalField) - Primary coordinate storage
Fields in PostGIS mode:
location_point(PointField) - Primary coordinate storage with GIS capabilitieslatitude(DecimalField) - Deprecated, kept for backward compatibilitylongitude(DecimalField) - Deprecated, kept for backward compatibility
Helper Methods
The Park model provides methods that work in both modes:
set_location(longitude, latitude)
Sets park location from coordinates. Works in both modes:
- SQLite: Updates latitude/longitude fields
- PostGIS: Updates location_point and syncs to latitude/longitude
park.set_location(-118.2437, 34.0522)
coordinates property
Returns coordinates as (longitude, latitude) tuple:
- SQLite: Returns from latitude/longitude fields
- PostGIS: Returns from location_point (falls back to lat/lng if not set)
coords = park.coordinates # (-118.2437, 34.0522)
latitude_value property
Returns latitude value:
- SQLite: Returns from latitude field
- PostGIS: Returns from location_point.y
longitude_value property
Returns longitude value:
- SQLite: Returns from longitude field
- PostGIS: Returns from location_point.x
Setup Instructions
Local Development (SQLite)
-
No special setup required! Just use the standard SQLite database:
# django/config/settings/local.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } -
Run migrations as normal:
python manage.py migrate -
Use latitude/longitude fields for coordinates:
park = Park.objects.create( name="Test Park", latitude=40.7128, longitude=-74.0060 )
Production (PostgreSQL with PostGIS)
-
Install PostGIS extension in PostgreSQL:
CREATE EXTENSION postgis; -
Configure production settings:
# django/config/settings/production.py DATABASES = { 'default': { 'ENGINE': 'django.contrib.gis.db.backends.postgis', 'NAME': 'thrillwiki', 'USER': 'your_user', 'PASSWORD': 'your_password', 'HOST': 'your_host', 'PORT': '5432', } } -
Run migrations:
python manage.py migrateThis will create the
location_pointPointField in addition to the latitude/longitude fields. -
Use location_point for geographic queries:
from django.contrib.gis.geos import Point from django.contrib.gis.measure import D # Create park with PostGIS Point park = Park.objects.create( name="Test Park", location_point=Point(-118.2437, 34.0522, srid=4326) ) # Geographic queries (only in PostGIS mode) nearby_parks = Park.objects.filter( location_point__distance_lte=( Point(-118.2500, 34.0500, srid=4326), D(km=10) ) )
Migration Strategy
From SQLite to PostgreSQL
When migrating from local development (SQLite) to production (PostgreSQL):
- Export your data from SQLite
- Set up PostgreSQL with PostGIS
- Run migrations (will create location_point field)
- Import your data (latitude/longitude fields will be populated)
- Run a data migration to populate location_point from lat/lng:
# Example data migration
from django.contrib.gis.geos import Point
for park in Park.objects.filter(latitude__isnull=False, longitude__isnull=False):
if not park.location_point:
park.location_point = Point(
float(park.longitude),
float(park.latitude),
srid=4326
)
park.save(update_fields=['location_point'])
Benefits
- Easy Local Development: No need to install PostGIS or SpatiaLite for local development
- Production Power: Full GIS capabilities in production with PostGIS
- Backward Compatible: Keeps latitude/longitude fields for compatibility
- Unified API: Helper methods work the same in both modes
- Gradual Migration: Can migrate from SQLite to PostGIS without data loss
Limitations
In SQLite Mode (Local Development)
-
No spatial queries: Cannot use PostGIS query features like:
distance_lte,distance_gte(distance-based searches)dwithin(within distance)contains,intersects(geometric operations)- Geographic indexing for performance
-
Workarounds for local development:
- Use simple filters on latitude/longitude ranges
- Implement basic distance calculations in Python if needed
- Most development work doesn't require spatial queries
In PostGIS Mode (Production)
- Use location_point for queries: Always use the
location_pointfield for geographic queries, not lat/lng - Sync fields: If updating location_point directly, remember to sync to lat/lng if needed for compatibility
Testing
Test in SQLite (Local)
cd django
python manage.py shell
# Test basic CRUD
from apps.entities.models import Park
from decimal import Decimal
park = Park.objects.create(
name="Test Park",
park_type="theme_park",
latitude=Decimal("40.7128"),
longitude=Decimal("-74.0060")
)
print(park.coordinates) # Should work
print(park.latitude_value) # Should work
Test in PostGIS (Production)
cd django
python manage.py shell
# Test GIS features
from apps.entities.models import Park
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import D
park = Park.objects.create(
name="Test Park",
park_type="theme_park",
location_point=Point(-118.2437, 34.0522, srid=4326)
)
# Test distance query
nearby = Park.objects.filter(
location_point__distance_lte=(
Point(-118.2500, 34.0500, srid=4326),
D(km=10)
)
)
Future Considerations
-
Remove Legacy Fields: Once fully migrated to PostGIS in production and all code uses location_point, the latitude/longitude fields can be deprecated and eventually removed
-
Add Spatial Indexes: In production, add spatial indexes for better query performance:
class Meta: indexes = [ models.Index(fields=['location_point']), # Spatial index ] -
Geographic Search API: Build geographic search endpoints that work differently based on backend:
- SQLite: Simple bounding box searches
- PostGIS: Advanced spatial queries with distance calculations
Troubleshooting
"AttributeError: 'DatabaseOperations' object has no attribute 'geo_db_type'"
This error occurs when trying to use PostGIS PointField with regular SQLite. Solution:
- Ensure you're using the local.py settings which uses regular SQLite
- Make sure migrations were created with SQLite active (no location_point field)
"No such column: location_point"
This occurs when:
- Code tries to access location_point in SQLite mode
- Solution: Use the helper methods (coordinates, latitude_value, longitude_value) instead
"GDAL library not found"
This occurs when django.contrib.gis is loaded but GDAL is not installed:
- Even with SQLite, GDAL libraries must be available because django.contrib.gis is in INSTALLED_APPS
- Install GDAL via Homebrew:
brew install gdal geos - Configure paths in settings if needed