mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-21 03:11:11 -05:00
Refactor test utilities and enhance ASGI settings
- Cleaned up and standardized assertions in ApiTestMixin for API response validation. - Updated ASGI settings to use os.environ for setting the DJANGO_SETTINGS_MODULE. - Removed unused imports and improved formatting in settings.py. - Refactored URL patterns in urls.py for better readability and organization. - Enhanced view functions in views.py for consistency and clarity. - Added .flake8 configuration for linting and style enforcement. - Introduced type stubs for django-environ to improve type checking with Pylance.
This commit is contained in:
@@ -1,19 +1,18 @@
|
||||
from typing import Any, Dict, Optional, Type, Union, cast
|
||||
from typing import Any, Dict, Optional, Type, Union
|
||||
from django.db import models
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import ObjectDoesNotExist, FieldDoesNotExist
|
||||
from django.contrib.auth.base_user import AbstractBaseUser
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.utils.text import slugify
|
||||
import pghistory
|
||||
from core.history import TrackedModel
|
||||
|
||||
UserType = Union[AbstractBaseUser, AnonymousUser]
|
||||
|
||||
|
||||
@pghistory.track() # Track all changes by default
|
||||
class EditSubmission(TrackedModel):
|
||||
STATUS_CHOICES = [
|
||||
@@ -51,12 +50,12 @@ class EditSubmission(TrackedModel):
|
||||
changes = models.JSONField(
|
||||
help_text="JSON representation of the changes or new object data"
|
||||
)
|
||||
|
||||
|
||||
# Moderator's edited version of changes before approval
|
||||
moderator_changes = models.JSONField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Moderator's edited version of the changes before approval"
|
||||
help_text="Moderator's edited version of the changes before approval",
|
||||
)
|
||||
|
||||
# Metadata
|
||||
@@ -104,7 +103,11 @@ class EditSubmission(TrackedModel):
|
||||
|
||||
for field_name, value in data.items():
|
||||
try:
|
||||
if (field := model_class._meta.get_field(field_name)) and isinstance(field, models.ForeignKey) and value is not None:
|
||||
if (
|
||||
(field := model_class._meta.get_field(field_name))
|
||||
and isinstance(field, models.ForeignKey)
|
||||
and value is not None
|
||||
):
|
||||
if related_model := field.related_model:
|
||||
resolved_data[field_name] = related_model.objects.get(id=value)
|
||||
except (FieldDoesNotExist, ObjectDoesNotExist):
|
||||
@@ -112,28 +115,33 @@ class EditSubmission(TrackedModel):
|
||||
|
||||
return resolved_data
|
||||
|
||||
def _prepare_model_data(self, data: Dict[str, Any], model_class: Type[models.Model]) -> Dict[str, Any]:
|
||||
def _prepare_model_data(
|
||||
self, data: Dict[str, Any], model_class: Type[models.Model]
|
||||
) -> Dict[str, Any]:
|
||||
"""Prepare data for model creation/update by filtering out auto-generated fields"""
|
||||
prepared_data = data.copy()
|
||||
|
||||
# Remove fields that are auto-generated or handled by the model's save method
|
||||
auto_fields = {'created_at', 'updated_at', 'slug'}
|
||||
|
||||
# Remove fields that are auto-generated or handled by the model's save
|
||||
# method
|
||||
auto_fields = {"created_at", "updated_at", "slug"}
|
||||
for field in auto_fields:
|
||||
prepared_data.pop(field, None)
|
||||
|
||||
|
||||
# Set default values for required fields if not provided
|
||||
for field in model_class._meta.fields:
|
||||
if not field.auto_created and not field.blank and not field.null:
|
||||
if field.name not in prepared_data and field.has_default():
|
||||
prepared_data[field.name] = field.get_default()
|
||||
|
||||
|
||||
return prepared_data
|
||||
|
||||
def _check_duplicate_name(self, model_class: Type[models.Model], name: str) -> Optional[models.Model]:
|
||||
def _check_duplicate_name(
|
||||
self, model_class: Type[models.Model], name: str
|
||||
) -> Optional[models.Model]:
|
||||
"""Check if an object with the same name already exists"""
|
||||
try:
|
||||
return model_class.objects.filter(name=name).first()
|
||||
except:
|
||||
except BaseException:
|
||||
return None
|
||||
|
||||
def approve(self, user: UserType) -> Optional[models.Model]:
|
||||
@@ -142,19 +150,29 @@ class EditSubmission(TrackedModel):
|
||||
raise ValueError("Could not resolve model class")
|
||||
|
||||
try:
|
||||
# Use moderator_changes if available, otherwise use original changes
|
||||
changes_to_apply = self.moderator_changes if self.moderator_changes is not None else self.changes
|
||||
|
||||
# Use moderator_changes if available, otherwise use original
|
||||
# changes
|
||||
changes_to_apply = (
|
||||
self.moderator_changes
|
||||
if self.moderator_changes is not None
|
||||
else self.changes
|
||||
)
|
||||
|
||||
resolved_data = self._resolve_foreign_keys(changes_to_apply)
|
||||
prepared_data = self._prepare_model_data(resolved_data, model_class)
|
||||
|
||||
# For CREATE submissions, check for duplicates by name
|
||||
if self.submission_type == "CREATE" and "name" in prepared_data:
|
||||
if existing_obj := self._check_duplicate_name(model_class, prepared_data["name"]):
|
||||
if existing_obj := self._check_duplicate_name(
|
||||
model_class, prepared_data["name"]
|
||||
):
|
||||
self.status = "REJECTED"
|
||||
self.handled_by = user # type: ignore
|
||||
self.handled_at = timezone.now()
|
||||
self.notes = f"A {model_class.__name__} with the name '{prepared_data['name']}' already exists (ID: {existing_obj.id})"
|
||||
self.notes = f"A {
|
||||
model_class.__name__} with the name '{
|
||||
prepared_data['name']}' already exists (ID: {
|
||||
existing_obj.id})"
|
||||
self.save()
|
||||
raise ValueError(self.notes)
|
||||
|
||||
@@ -185,7 +203,9 @@ class EditSubmission(TrackedModel):
|
||||
self.save()
|
||||
return obj
|
||||
except Exception as e:
|
||||
if self.status != "REJECTED": # Don't override if already rejected due to duplicate
|
||||
if (
|
||||
self.status != "REJECTED"
|
||||
): # Don't override if already rejected due to duplicate
|
||||
self.status = "PENDING" # Reset status if approval failed
|
||||
self.save()
|
||||
raise ValueError(f"Error approving submission: {str(e)}") from e
|
||||
@@ -204,6 +224,7 @@ class EditSubmission(TrackedModel):
|
||||
self.handled_at = timezone.now()
|
||||
self.save()
|
||||
|
||||
|
||||
@pghistory.track() # Track all changes by default
|
||||
class PhotoSubmission(TrackedModel):
|
||||
STATUS_CHOICES = [
|
||||
@@ -244,7 +265,8 @@ class PhotoSubmission(TrackedModel):
|
||||
)
|
||||
handled_at = models.DateTimeField(null=True, blank=True)
|
||||
notes = models.TextField(
|
||||
blank=True, help_text="Notes from the moderator about this photo submission"
|
||||
blank=True,
|
||||
help_text="Notes from the moderator about this photo submission",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -255,7 +277,9 @@ class PhotoSubmission(TrackedModel):
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Photo submission by {self.user.username} for {self.content_object}"
|
||||
return f"Photo submission by {
|
||||
self.user.username} for {
|
||||
self.content_object}"
|
||||
|
||||
def approve(self, moderator: UserType, notes: str = "") -> None:
|
||||
"""Approve the photo submission"""
|
||||
@@ -285,12 +309,12 @@ class PhotoSubmission(TrackedModel):
|
||||
self.handled_at = timezone.now()
|
||||
self.notes = notes
|
||||
self.save()
|
||||
|
||||
|
||||
def auto_approve(self) -> None:
|
||||
"""Auto-approve submissions from moderators"""
|
||||
# Get user role safely
|
||||
user_role = getattr(self.user, "role", None)
|
||||
|
||||
|
||||
# If user is moderator or above, auto-approve
|
||||
if user_role in ["MODERATOR", "ADMIN", "SUPERUSER"]:
|
||||
self.approve(self.user)
|
||||
|
||||
Reference in New Issue
Block a user