Add comments app with models, views, and tests; integrate comments into existing models

This commit is contained in:
pacnpal
2025-02-07 21:58:02 -05:00
parent 86ae24bbac
commit 75f5b07129
18 changed files with 314 additions and 15 deletions

0
comments/__init__.py Normal file
View File

3
comments/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
comments/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class CommentsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "comments"

View File

73
comments/models.py Normal file
View File

@@ -0,0 +1,73 @@
from django.db import models
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
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.
"""
content_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
related_name='comment_threads'
)
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)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name='created_comment_threads'
)
is_locked = models.BooleanField(default=False)
is_hidden = models.BooleanField(default=False)
class Meta:
indexes = [
models.Index(fields=['content_type', 'object_id']),
]
ordering = ['-created_at']
def __str__(self):
return f"Comment Thread on {self.content_object} - {self.title}"
class Comment(models.Model):
"""
Individual comment within a comment thread.
"""
thread = models.ForeignKey(
CommentThread,
on_delete=models.CASCADE,
related_name='comments'
)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name='comments'
)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_edited = models.BooleanField(default=False)
is_hidden = models.BooleanField(default=False)
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='replies'
)
class Meta:
ordering = ['created_at']
def __str__(self):
return f"Comment by {self.author} on {self.created_at}"

3
comments/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
comments/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -2,6 +2,7 @@ from django.db import models
from django.utils.text import slugify
from django.urls import reverse
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericRelation
from typing import Tuple, Optional, ClassVar, TYPE_CHECKING
from history_tracking.models import HistoricalModel, VersionBranch, ChangeSet
from history_tracking.signals import get_current_branch, ChangesetContextManager
@@ -19,6 +20,10 @@ class Company(HistoricalModel):
total_rides = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
comments = GenericRelation('comments.CommentThread',
related_name='company_threads',
related_query_name='comments_thread'
)
objects: ClassVar[models.Manager['Company']]
@@ -101,6 +106,10 @@ class Manufacturer(HistoricalModel):
total_roller_coasters = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
comments = GenericRelation('comments.CommentThread',
related_name='manufacturer_threads',
related_query_name='comments_thread'
)
objects: ClassVar[models.Manager['Manufacturer']]
@@ -181,6 +190,10 @@ class Designer(HistoricalModel):
total_roller_coasters = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
comments = GenericRelation('comments.CommentThread',
related_name='designer_threads',
related_query_name='comments_thread'
)
objects: ClassVar[models.Manager['Designer']]

View File

@@ -5,7 +5,7 @@ from django.http import HttpRequest, HttpResponse, Http404
from django.template.loader import render_to_string
from django.core.exceptions import PermissionDenied
from .models import ChangeSet, CommentThread, Comment
from .models import ChangeSet, HistoricalCommentThread, Comment
from .notifications import NotificationDispatcher
from .state_machine import ApprovalStateMachine
@@ -16,7 +16,7 @@ def get_comments(request: HttpRequest) -> HttpResponse:
if not anchor:
raise Http404("Anchor parameter is required")
thread = CommentThread.objects.filter(anchor__id=anchor).first()
thread = HistoricalCommentThread.objects.filter(anchor__id=anchor).first()
comments = thread.comments.all() if thread else []
return render(request, 'history_tracking/partials/comments_list.html', {
@@ -44,7 +44,7 @@ def add_comment(request: HttpRequest) -> HttpResponse:
if not content:
return HttpResponse("Comment content is required", status=400)
thread, created = CommentThread.objects.get_or_create(
thread, created = HistoricalCommentThread.objects.get_or_create(
anchor={'id': anchor},
defaults={'created_by': request.user}
)

View File

@@ -18,7 +18,8 @@ class HistoricalModel(models.Model):
id = models.BigAutoField(primary_key=True)
history: HistoricalRecords = HistoricalRecords(
inherit=True,
bases=(HistoricalChangeMixin,)
bases=(HistoricalChangeMixin,),
excluded_fields=['comments', 'photos', 'reviews'] # Exclude all generic relations
)
class Meta:
@@ -116,8 +117,8 @@ class VersionTag(models.Model):
def __str__(self) -> str:
return f"{self.name} ({self.branch.name})"
class CommentThread(models.Model):
"""Represents a thread of comments on a historical record"""
class HistoricalCommentThread(models.Model):
"""Represents a thread of comments specific to historical records and version control"""
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
@@ -149,7 +150,7 @@ class CommentThread(models.Model):
class Comment(models.Model):
"""Individual comment within a thread"""
thread = models.ForeignKey(CommentThread, on_delete=models.CASCADE, related_name='comments')
thread = models.ForeignKey(HistoricalCommentThread, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)

View File

@@ -9,7 +9,7 @@ from django.views.decorators.http import require_http_methods
from django.core.exceptions import PermissionDenied
from typing import Dict, Any
from .models import VersionBranch, ChangeSet, VersionTag, CommentThread
from .models import VersionBranch, ChangeSet, VersionTag, HistoricalCommentThread
from .managers import ChangeTracker
from .comparison import ComparisonEngine
from .state_machine import ApprovalStateMachine
@@ -42,7 +42,7 @@ def version_comparison(request: HttpRequest) -> HttpResponse:
# Add comments to changes
for change in diff_result['changes']:
anchor_id = change['metadata']['comment_anchor_id']
change['comments'] = CommentThread.objects.filter(
change['comments'] = HistoricalCommentThread.objects.filter(
anchor__contains={'id': anchor_id}
).prefetch_related('comments')

View File

@@ -58,6 +58,10 @@ class Park(HistoricalModel):
Company, on_delete=models.SET_NULL, null=True, blank=True, related_name="parks"
)
photos = GenericRelation(Photo, related_query_name="park")
comments = GenericRelation('comments.CommentThread',
related_name='park_threads',
related_query_name='comments_thread'
)
areas: models.Manager['ParkArea'] # Type hint for reverse relation
rides: models.Manager['Ride'] # Type hint for reverse relation from rides app
@@ -164,6 +168,12 @@ class ParkArea(HistoricalModel):
opening_date = models.DateField(null=True, blank=True)
closing_date = models.DateField(null=True, blank=True)
# Relationships
comments = GenericRelation('comments.CommentThread',
related_name='park_area_threads',
related_query_name='comments_thread'
)
# Metadata
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True)

View File

@@ -55,5 +55,7 @@ dependencies = [
"django-simple-history>=3.5.0",
"django-tailwind-cli>=2.21.1",
"playwright>=1.41.0",
"pytest-playwright>=0.4.3"
"pytest-playwright>=0.4.3",
"celery>=5.4.0",
"django-redis>=5.4.0",
]

View File

@@ -13,6 +13,7 @@ pyjwt==2.10.1
# Database
psycopg2-binary==2.9.10
dj-database-url==2.3.0
django-redis==5.4.0
# Email
requests==2.32.3 # For ForwardEmail.net API

View File

@@ -1,6 +1,6 @@
from django.db import models
from django.urls import reverse
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.validators import MinValueValidator, MaxValueValidator
from history_tracking.models import HistoricalModel, VersionBranch, ChangeSet
@@ -40,6 +40,12 @@ class Review(HistoricalModel):
related_name='moderated_reviews'
)
moderated_at = models.DateTimeField(null=True, blank=True)
# Comments
comments = GenericRelation('comments.CommentThread',
related_name='review_threads',
related_query_name='comments_thread'
)
class Meta:
ordering = ['-created_at']

View File

@@ -41,6 +41,10 @@ class RideModel(HistoricalModel):
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
comments = GenericRelation('comments.CommentThread',
related_name='ride_model_threads',
related_query_name='comments_thread'
)
class Meta:
ordering = ['manufacturer', 'name']
@@ -179,6 +183,10 @@ class Ride(HistoricalModel):
updated_at = models.DateTimeField(auto_now=True)
photos = GenericRelation('media.Photo')
reviews = GenericRelation('reviews.Review')
comments = GenericRelation('comments.CommentThread',
related_name='ride_threads',
related_query_name='comments_thread'
)
class Meta:
ordering = ['name']

View File

@@ -53,6 +53,7 @@ INSTALLED_APPS = [
"designers",
"analytics",
"location",
"comments",
]
MIDDLEWARE = [
@@ -109,14 +110,22 @@ DATABASES = {
# Cache settings
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "unique-snowflake",
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://192.168.86.3:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"SOCKET_CONNECT_TIMEOUT": 5,
"SOCKET_TIMEOUT": 5,
"RETRY_ON_TIMEOUT": True,
"MAX_CONNECTIONS": 1000,
"PARSER_CLASS": "redis.connection.HiredisParser",
},
"KEY_PREFIX": "thrillwiki",
"TIMEOUT": 300, # 5 minutes
"OPTIONS": {"MAX_ENTRIES": 1000},
}
}
CACHE_MIDDLEWARE_SECONDS = 1 # 5 minutes
CACHE_MIDDLEWARE_SECONDS = 300 # 5 minutes
CACHE_MIDDLEWARE_KEY_PREFIX = "thrillwiki"
# Password validation

161
uv.lock generated
View File

@@ -1,6 +1,18 @@
version = 1
requires-python = ">=3.13"
[[package]]
name = "amqp"
version = "5.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "vine" },
]
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]amqp-5.3.1.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]de74d170a6f10ab044739432", size = 129013 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]amqp-5.3.1-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]32d479b98661a95f117880a2", size = 50944 },
]
[[package]]
name = "asgiref"
version = "3.8.1"
@@ -43,6 +55,15 @@ wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]Automat-24.8.1-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]0ab63ff808566ce90551e02a", size = 42585 },
]
[[package]]
name = "billiard"
version = "4.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]billiard-4.2.1.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]33c7c1fcd66a7e677c4fb36f", size = 155031 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]billiard-4.2.1-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]227baffd928c644d15d8f3cb", size = 86766 },
]
[[package]]
name = "black"
version = "24.10.0"
@@ -63,6 +84,26 @@ wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]black-24.10.0-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]9853e47a294a3dd963c1dd7d", size = 206898 },
]
[[package]]
name = "celery"
version = "5.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "billiard" },
{ name = "click" },
{ name = "click-didyoumean" },
{ name = "click-plugins" },
{ name = "click-repl" },
{ name = "kombu" },
{ name = "python-dateutil" },
{ name = "tzdata" },
{ name = "vine" },
]
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]celery-5.4.0.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]89d85f94756762d8bca7e706", size = 1575692 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]celery-5.4.0-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]37edde2dfc0dacd73ed97f64", size = 425983 },
]
[[package]]
name = "certifi"
version = "2024.12.14"
@@ -156,6 +197,43 @@ wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]click-8.1.8-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]d329b7903a866228027263b2", size = 98188 },
]
[[package]]
name = "click-didyoumean"
version = "0.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
]
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]click_didyoumean-0.3.1.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]05aa09cbfc07c9d7fbb5a463", size = 3089 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]957e63d0fac41c10e7c3117c", size = 3631 },
]
[[package]]
name = "click-plugins"
version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
]
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]click-plugins-1.1.1.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]df9a3a3742e9ed63645f264b", size = 8164 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]dc815c06b442aa3c02889fc8", size = 7497 },
]
[[package]]
name = "click-repl"
version = "0.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "prompt-toolkit" },
]
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]click-repl-0.3.0.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]0fe37474f3feebb69ced26a9", size = 10449 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]click_repl-0.3.0-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]4c6899382da7feeeeb51b812", size = 10289 },
]
[[package]]
name = "colorama"
version = "0.4.6"
@@ -328,6 +406,19 @@ wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]django_oauth_toolkit-3.0.1-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]221bbb1cffcb50b8932e55ed", size = 77299 },
]
[[package]]
name = "django-redis"
version = "5.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "redis" },
]
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]django-redis-5.4.0.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]50b2db93602e6cb292818c42", size = 52567 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]django_redis-5.4.0-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]319df4c6da6ca9a2942edd5b", size = 31119 },
]
[[package]]
name = "django-simple-history"
version = "3.7.0"
@@ -483,6 +574,20 @@ wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]8be71f67f03566692fd55789", size = 92520 },
]
[[package]]
name = "kombu"
version = "5.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "amqp" },
{ name = "tzdata" },
{ name = "vine" },
]
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]kombu-5.4.2.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]ff31e7fba89138cdb406f2cf", size = 442858 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]kombu-5.4.2-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]8c49ea866884047d66e14763", size = 201349 },
]
[[package]]
name = "mccabe"
version = "0.7.0"
@@ -610,6 +715,18 @@ wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]pluggy-1.5.0-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]4d554740532335b9d75ea669", size = 20556 },
]
[[package]]
name = "prompt-toolkit"
version = "3.0.50"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "wcwidth" },
]
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]prompt_toolkit-3.0.50.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]c9728359013f79877fc89bab", size = 429087 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]89e6d88ebbb1b509d9779198", size = 387816 },
]
[[package]]
name = "psycopg2-binary"
version = "2.9.10"
@@ -626,6 +743,7 @@ wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:[AWS-SECRET-REMOVED]07c6df12b7737febc40f0909", size = 2822712 },
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:[AWS-SECRET-REMOVED]860ff3bbe1384130828714b1", size = 2920155 },
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:[AWS-SECRET-REMOVED]998122abe1dce6428bd86567", size = 2959356 },
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:[AWS-SECRET-REMOVED]9b46e6fd07c3eb46e4535142", size = 2569224 },
]
[[package]]
@@ -773,6 +891,18 @@ wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]pytest_playwright-0.6.2-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]1f8b3acf575beed84e7e9043", size = 16436 },
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]f90fb72437e91a35459a0ad3", size = 342432 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]6dfe83f850eea9a5f7470427", size = 229892 },
]
[[package]]
name = "python-dotenv"
version = "1.0.1"
@@ -851,6 +981,15 @@ wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]56c89140852d1120324e8686", size = 9755 },
]
[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]six-1.17.0.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]63acc5171de367e834932a81", size = 34031 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]six-1.17.0-py2.py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]b2e1e8eda2be8970586c3274", size = 11050 },
]
[[package]]
name = "sqlparse"
version = "0.5.3"
@@ -875,6 +1014,7 @@ version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "black" },
{ name = "celery" },
{ name = "channels" },
{ name = "channels-redis" },
{ name = "daphne" },
@@ -887,6 +1027,7 @@ dependencies = [
{ name = "django-filter" },
{ name = "django-htmx" },
{ name = "django-oauth-toolkit" },
{ name = "django-redis" },
{ name = "django-simple-history" },
{ name = "django-tailwind-cli" },
{ name = "django-webpack-loader" },
@@ -908,6 +1049,7 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "black", specifier = ">=24.1.0" },
{ name = "celery", specifier = ">=5.4.0" },
{ name = "channels", specifier = ">=4.2.0" },
{ name = "channels-redis", specifier = ">=4.2.1" },
{ name = "daphne", specifier = ">=4.1.2" },
@@ -920,6 +1062,7 @@ requires-dist = [
{ name = "django-filter", specifier = ">=23.5" },
{ name = "django-htmx", specifier = ">=1.17.2" },
{ name = "django-oauth-toolkit", specifier = ">=3.0.1" },
{ name = "django-redis", specifier = ">=5.4.0" },
{ name = "django-simple-history", specifier = ">=3.5.0" },
{ name = "django-tailwind-cli", specifier = ">=2.21.1" },
{ name = "django-webpack-loader", specifier = ">=3.1.1" },
@@ -1012,6 +1155,24 @@ wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]urllib3-2.3.0-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]710050facf0dd6911440e3df", size = 128369 },
]
[[package]]
name = "vine"
version = "5.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]vine-5.1.0.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]15bb196ce38aefd6799e61e0", size = 48980 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]vine-5.1.0-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]810050a728bd7413811fb1dc", size = 9636 },
]
[[package]]
name = "wcwidth"
version = "0.2.13"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]wcwidth-0.2.13.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]6ce63e9b4e64fc18303972b5", size = 101301 }
wheels = [
{ url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]6bdb8d6102117aac784f6859", size = 34166 },
]
[[package]]
name = "whitenoise"
version = "6.8.2"