mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 04:51:11 -05:00
- 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.
298 lines
8.6 KiB
Markdown
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/)
|