Files
thrilltrack-explorer/django/POSTGIS_SETUP.md
pacnpal d6ff4cc3a3 Add email templates for user notifications and account management
- 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.
2025-11-08 15:34:04 -05:00

298 lines
8.6 KiB
Markdown

# 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 `latitude` and `longitude` DecimalFields
- No spatial query capabilities
- Simpler setup, easier for local development
- **Production**: Uses PostgreSQL with PostGIS extension
- Geographic coordinates stored in `location_point` PointField (PostGIS)
- Full spatial query capabilities (distance calculations, geographic searches, etc.)
- Automatically syncs with legacy `latitude`/`longitude` fields
### Model Implementation
The `Park` model uses conditional field definition:
```python
# 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 storage
- `longitude` (DecimalField) - Primary coordinate storage
**Fields in PostGIS mode:**
- `location_point` (PointField) - Primary coordinate storage with GIS capabilities
- `latitude` (DecimalField) - Deprecated, kept for backward compatibility
- `longitude` (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
```python
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)
```python
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)
1. **No special setup required!** Just use the standard SQLite database:
```python
# django/config/settings/local.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
```
2. Run migrations as normal:
```bash
python manage.py migrate
```
3. Use latitude/longitude fields for coordinates:
```python
park = Park.objects.create(
name="Test Park",
latitude=40.7128,
longitude=-74.0060
)
```
### Production (PostgreSQL with PostGIS)
1. **Install PostGIS extension in PostgreSQL:**
```sql
CREATE EXTENSION postgis;
```
2. **Configure production settings:**
```python
# 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',
}
}
```
3. **Run migrations:**
```bash
python manage.py migrate
```
This will create the `location_point` PointField in addition to the latitude/longitude fields.
4. **Use location_point for geographic queries:**
```python
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):
1. Export your data from SQLite
2. Set up PostgreSQL with PostGIS
3. Run migrations (will create location_point field)
4. Import your data (latitude/longitude fields will be populated)
5. Run a data migration to populate location_point from lat/lng:
```python
# 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
1. **Easy Local Development**: No need to install PostGIS or SpatiaLite for local development
2. **Production Power**: Full GIS capabilities in production with PostGIS
3. **Backward Compatible**: Keeps latitude/longitude fields for compatibility
4. **Unified API**: Helper methods work the same in both modes
5. **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_point` field 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)
```bash
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)
```bash
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
1. **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
2. **Add Spatial Indexes**: In production, add spatial indexes for better query performance:
```python
class Meta:
indexes = [
models.Index(fields=['location_point']), # Spatial index
]
```
3. **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
## References
- [Django GIS Documentation](https://docs.djangoproject.com/en/stable/ref/contrib/gis/)
- [PostGIS Documentation](https://postgis.net/documentation/)
- [GeoDjango Tutorial](https://docs.djangoproject.com/en/stable/ref/contrib/gis/tutorial/)