mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-23 06:31:08 -05:00
Refactor comments app to use mixins for comment functionality; update admin interfaces and add historical model fixes
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
from django.db.models.signals import class_prepared, post_init
|
||||
|
||||
class CommentsConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "comments"
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'comments'
|
||||
|
||||
def ready(self):
|
||||
"""Set up comment system when the app is ready."""
|
||||
pass
|
||||
|
||||
71
comments/managers.py
Normal file
71
comments/managers.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
class CommentThreadManager(models.Manager):
|
||||
"""Manager for handling comment threads on both regular and historical models."""
|
||||
|
||||
def for_instance(self, instance):
|
||||
"""Get comment threads for any model instance."""
|
||||
# Get the base model class if this is a historical instance
|
||||
if instance.__class__.__name__.startswith('Historical'):
|
||||
model_class = instance.instance.__class__
|
||||
instance_id = instance.instance.pk
|
||||
else:
|
||||
model_class = instance.__class__
|
||||
instance_id = instance.pk
|
||||
|
||||
ct = ContentType.objects.get_for_model(model_class)
|
||||
return self.filter(content_type=ct, object_id=instance_id)
|
||||
|
||||
def create_for_instance(self, instance, **kwargs):
|
||||
"""Create a comment thread for any model instance."""
|
||||
# Get the base model class if this is a historical instance
|
||||
if instance.__class__.__name__.startswith('Historical'):
|
||||
model_class = instance.instance.__class__
|
||||
instance_id = instance.instance.pk
|
||||
else:
|
||||
model_class = instance.__class__
|
||||
instance_id = instance.pk
|
||||
|
||||
ct = ContentType.objects.get_for_model(model_class)
|
||||
return self.create(content_type=ct, object_id=instance_id, **kwargs)
|
||||
|
||||
class ThreadedModelManager(models.Manager):
|
||||
"""Manager for models that have comment threads."""
|
||||
|
||||
"""Manager for models that have comment threads."""
|
||||
|
||||
def get_comment_threads(self, instance):
|
||||
"""Get comment threads for this instance."""
|
||||
from comments.models import CommentThread
|
||||
if not instance.pk:
|
||||
return CommentThread.objects.none()
|
||||
return CommentThread.objects.for_instance(instance)
|
||||
|
||||
def add_comment_thread(self, instance, **kwargs):
|
||||
"""Create a comment thread for this instance."""
|
||||
from comments.models import CommentThread
|
||||
if not instance.pk:
|
||||
raise ObjectDoesNotExist("Cannot create comment thread for unsaved instance")
|
||||
return CommentThread.objects.create_for_instance(instance, **kwargs)
|
||||
|
||||
def with_comment_threads(self):
|
||||
"""Get all instances with their comment threads."""
|
||||
from comments.models import CommentThread
|
||||
qs = self.get_queryset()
|
||||
content_type = ContentType.objects.get_for_model(self.model)
|
||||
|
||||
# Get comment threads through a subquery
|
||||
threads = CommentThread.objects.filter(
|
||||
content_type=content_type,
|
||||
object_id=models.OuterRef('pk')
|
||||
)
|
||||
return qs.annotate(
|
||||
comment_count=models.Subquery(
|
||||
threads.values('object_id')
|
||||
.annotate(count=models.Count('id'))
|
||||
.values('count'),
|
||||
output_field=models.IntegerField()
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
17
comments/mixins.py
Normal file
17
comments/mixins.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
|
||||
from .models import get_comment_threads
|
||||
|
||||
class CommentableMixin:
|
||||
"""
|
||||
Mixin for models that should have comment functionality.
|
||||
Uses composition instead of inheritance to avoid historical model issues.
|
||||
"""
|
||||
|
||||
@property
|
||||
def comments(self):
|
||||
"""Get comments helper for this instance."""
|
||||
if self.__class__.__name__.startswith('Historical'):
|
||||
# Historical models delegate to their current instance
|
||||
return self.instance.comments
|
||||
return get_comment_threads(self)
|
||||
@@ -2,20 +2,17 @@ from django.db import models
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from .managers import CommentThreadManager, ThreadedModelManager
|
||||
|
||||
class CommentThread(models.Model):
|
||||
"""
|
||||
A generic comment thread that can be attached to any model instance.
|
||||
Used for tracking discussions on various objects across the platform.
|
||||
A thread of comments that can be attached to any model instance,
|
||||
including historical versions.
|
||||
"""
|
||||
content_type = models.ForeignKey(
|
||||
ContentType,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='comment_threads'
|
||||
)
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
|
||||
|
||||
title = models.CharField(max_length=255, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
@@ -28,6 +25,8 @@ class CommentThread(models.Model):
|
||||
is_locked = models.BooleanField(default=False)
|
||||
is_hidden = models.BooleanField(default=False)
|
||||
|
||||
objects = CommentThreadManager()
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
models.Index(fields=['content_type', 'object_id']),
|
||||
@@ -37,11 +36,57 @@ class CommentThread(models.Model):
|
||||
def __str__(self):
|
||||
return f"Comment Thread on {self.content_object} - {self.title}"
|
||||
|
||||
class CommentThreads:
|
||||
"""
|
||||
Helper class to manage comment threads for a model instance.
|
||||
This is used instead of direct inheritance to avoid historical model issues.
|
||||
"""
|
||||
def __init__(self, instance):
|
||||
self.instance = instance
|
||||
self._info = {}
|
||||
|
||||
def get_info(self):
|
||||
"""Get or compute comment thread information."""
|
||||
if not self._info:
|
||||
ct = ContentType.objects.get_for_model(self.instance.__class__)
|
||||
self._info = {
|
||||
'count': CommentThread.objects.filter(
|
||||
content_type=ct,
|
||||
object_id=self.instance.pk
|
||||
).count(),
|
||||
'content_type': ct,
|
||||
'object_id': self.instance.pk
|
||||
}
|
||||
return self._info
|
||||
|
||||
def get_threads(self):
|
||||
"""Get comment threads for this instance."""
|
||||
info = self.get_info()
|
||||
return CommentThread.objects.filter(
|
||||
content_type=info['content_type'],
|
||||
object_id=info['object_id']
|
||||
)
|
||||
|
||||
def add_thread(self, title='', created_by=None):
|
||||
"""Create a new comment thread for this instance."""
|
||||
info = self.get_info()
|
||||
thread = CommentThread.objects.create(
|
||||
content_type=info['content_type'],
|
||||
object_id=info['object_id'],
|
||||
title=title,
|
||||
created_by=created_by
|
||||
)
|
||||
self._info = {} # Clear cache
|
||||
return thread
|
||||
|
||||
def get_comment_threads(instance):
|
||||
"""Get or create a CommentThreads helper for a model instance."""
|
||||
if not hasattr(instance, '_comment_threads'):
|
||||
instance._comment_threads = CommentThreads(instance)
|
||||
return instance._comment_threads
|
||||
|
||||
class Comment(models.Model):
|
||||
"""
|
||||
Individual comment within a comment thread.
|
||||
"""
|
||||
"""Individual comment within a thread."""
|
||||
thread = models.ForeignKey(
|
||||
CommentThread,
|
||||
on_delete=models.CASCADE,
|
||||
|
||||
1
comments/signals.py
Normal file
1
comments/signals.py
Normal file
@@ -0,0 +1 @@
|
||||
# This file intentionally left empty - signals have been replaced with direct mixin configuration
|
||||
Reference in New Issue
Block a user