feat: Refactor rides app with unique constraints, mixins, and enhanced documentation

- Added migration to convert unique_together constraints to UniqueConstraint for RideModel.
- Introduced RideFormMixin for handling entity suggestions in ride forms.
- Created comprehensive code standards documentation outlining formatting, docstring requirements, complexity guidelines, and testing requirements.
- Established error handling guidelines with a structured exception hierarchy and best practices for API and view error handling.
- Documented view pattern guidelines, emphasizing the use of CBVs, FBVs, and ViewSets with examples.
- Implemented a benchmarking script for query performance analysis and optimization.
- Developed security documentation detailing measures, configurations, and a security checklist.
- Compiled a database optimization guide covering indexing strategies, query optimization patterns, and computed fields.
This commit is contained in:
pacnpal
2025-12-22 11:17:31 -05:00
parent 45d97b6e68
commit 2e35f8c5d9
71 changed files with 8036 additions and 1462 deletions

View File

@@ -3,16 +3,21 @@ Services for park-related business logic.
Following Django styleguide pattern for business logic encapsulation.
"""
from typing import Optional, Dict, Any, TYPE_CHECKING
import logging
from typing import Optional, Dict, Any, List, TYPE_CHECKING
from django.db import transaction
from django.db.models import Q
from django.core.files.uploadedfile import UploadedFile
if TYPE_CHECKING:
from django.contrib.auth.models import AbstractUser
from ..models import Park, ParkArea
from ..models import Park, ParkArea, ParkPhoto
from ..models.location import ParkLocation
from .location_service import ParkLocationService
logger = logging.getLogger(__name__)
class ParkService:
"""Service for managing park operations."""
@@ -226,3 +231,282 @@ class ParkService:
park.save()
return park
@staticmethod
def create_park_with_moderation(
*,
changes: Dict[str, Any],
submitter: "AbstractUser",
reason: str = "",
source: str = "",
) -> Dict[str, Any]:
"""
Create a park through the moderation system.
Args:
changes: Dictionary of park data
submitter: User submitting the park
reason: Reason for submission
source: Source of information
Returns:
Dictionary with status and created object (if auto-approved)
"""
from apps.moderation.services import ModerationService
return ModerationService.create_edit_submission_with_queue(
content_object=None,
changes=changes,
submitter=submitter,
submission_type="CREATE",
reason=reason,
source=source,
)
@staticmethod
def update_park_with_moderation(
*,
park: Park,
changes: Dict[str, Any],
submitter: "AbstractUser",
reason: str = "",
source: str = "",
) -> Dict[str, Any]:
"""
Update a park through the moderation system.
Args:
park: Park instance to update
changes: Dictionary of changes
submitter: User submitting the update
reason: Reason for submission
source: Source of information
Returns:
Dictionary with status and updated object (if auto-approved)
"""
from apps.moderation.services import ModerationService
return ModerationService.create_edit_submission_with_queue(
content_object=park,
changes=changes,
submitter=submitter,
submission_type="EDIT",
reason=reason,
source=source,
)
@staticmethod
def create_or_update_location(
*,
park: Park,
latitude: Optional[float],
longitude: Optional[float],
street_address: str = "",
city: str = "",
state: str = "",
country: str = "USA",
postal_code: str = "",
) -> Optional[ParkLocation]:
"""
Create or update a park's location.
Args:
park: Park instance
latitude: Latitude coordinate
longitude: Longitude coordinate
street_address: Street address
city: City name
state: State/region
country: Country (default: USA)
postal_code: Postal/ZIP code
Returns:
ParkLocation instance or None if no coordinates provided
"""
if not latitude or not longitude:
return None
try:
park_location = park.location
# Update existing location
park_location.street_address = street_address
park_location.city = city
park_location.state = state
park_location.country = country or "USA"
park_location.postal_code = postal_code
park_location.set_coordinates(float(latitude), float(longitude))
park_location.save()
return park_location
except ParkLocation.DoesNotExist:
# Create new location
park_location = ParkLocation.objects.create(
park=park,
street_address=street_address,
city=city,
state=state,
country=country or "USA",
postal_code=postal_code,
)
park_location.set_coordinates(float(latitude), float(longitude))
park_location.save()
return park_location
@staticmethod
def upload_photos(
*,
park: Park,
photos: List[UploadedFile],
uploaded_by: "AbstractUser",
) -> Dict[str, Any]:
"""
Upload multiple photos for a park.
Args:
park: Park instance
photos: List of uploaded photo files
uploaded_by: User uploading the photos
Returns:
Dictionary with uploaded_count and errors list
"""
from django.contrib.contenttypes.models import ContentType
uploaded_count = 0
errors: List[str] = []
for photo_file in photos:
try:
ParkPhoto.objects.create(
image=photo_file,
uploaded_by=uploaded_by,
park=park,
)
uploaded_count += 1
except Exception as e:
error_msg = f"Error uploading photo {photo_file.name}: {str(e)}"
errors.append(error_msg)
logger.warning(error_msg)
return {
"uploaded_count": uploaded_count,
"errors": errors,
}
@staticmethod
def handle_park_creation_result(
*,
result: Dict[str, Any],
form_data: Dict[str, Any],
photos: List[UploadedFile],
user: "AbstractUser",
) -> Dict[str, Any]:
"""
Handle the result of park creation through moderation.
Args:
result: Result from create_park_with_moderation
form_data: Cleaned form data containing location info
photos: List of uploaded photo files
user: User who submitted
Returns:
Dictionary with status, park (if created), uploaded_count, and errors
"""
response: Dict[str, Any] = {
"status": result["status"],
"park": None,
"uploaded_count": 0,
"errors": [],
}
if result["status"] == "auto_approved":
park = result["created_object"]
response["park"] = park
# Create location
ParkService.create_or_update_location(
park=park,
latitude=form_data.get("latitude"),
longitude=form_data.get("longitude"),
street_address=form_data.get("street_address", ""),
city=form_data.get("city", ""),
state=form_data.get("state", ""),
country=form_data.get("country", "USA"),
postal_code=form_data.get("postal_code", ""),
)
# Upload photos
if photos:
photo_result = ParkService.upload_photos(
park=park,
photos=photos,
uploaded_by=user,
)
response["uploaded_count"] = photo_result["uploaded_count"]
response["errors"] = photo_result["errors"]
elif result["status"] == "failed":
response["message"] = result.get("message", "Creation failed")
return response
@staticmethod
def handle_park_update_result(
*,
result: Dict[str, Any],
park: Park,
form_data: Dict[str, Any],
photos: List[UploadedFile],
user: "AbstractUser",
) -> Dict[str, Any]:
"""
Handle the result of park update through moderation.
Args:
result: Result from update_park_with_moderation
park: Original park instance (for queued submissions)
form_data: Cleaned form data containing location info
photos: List of uploaded photo files
user: User who submitted
Returns:
Dictionary with status, park, uploaded_count, and errors
"""
response: Dict[str, Any] = {
"status": result["status"],
"park": park,
"uploaded_count": 0,
"errors": [],
}
if result["status"] == "auto_approved":
updated_park = result["created_object"]
response["park"] = updated_park
# Update location
ParkService.create_or_update_location(
park=updated_park,
latitude=form_data.get("latitude"),
longitude=form_data.get("longitude"),
street_address=form_data.get("street_address", ""),
city=form_data.get("city", ""),
state=form_data.get("state", ""),
country=form_data.get("country", ""),
postal_code=form_data.get("postal_code", ""),
)
# Upload photos
if photos:
photo_result = ParkService.upload_photos(
park=updated_park,
photos=photos,
uploaded_by=user,
)
response["uploaded_count"] = photo_result["uploaded_count"]
response["errors"] = photo_result["errors"]
elif result["status"] == "failed":
response["message"] = result.get("message", "Update failed")
return response