Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/backend/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .celery import app as celery_app
__all__ = ('celery_app',)
8 changes: 8 additions & 0 deletions backend/backend/celery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')

app = Celery('backend')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
39 changes: 35 additions & 4 deletions backend/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

import os
from pathlib import Path

from dotenv import load_dotenv

load_dotenv()
Expand Down Expand Up @@ -47,8 +46,23 @@
"authentication",
"chat",
"gpt",
"django_crontab",
"django_celery_beat",
]

# Crontab Configuration
CRONJOBS = [
('0 0 * * *', 'django.core.management.call_command', ['cleanup_conversations']),
]

INSTALLED_APPS += ['rest_framework.authtoken']

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
}

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
Expand Down Expand Up @@ -84,10 +98,15 @@
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases


DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'Demo',
'USER': 'postgres',
'PASSWORD': 'Pakeer@124',
'HOST': 'localhost',
'PORT': '5432',
}
}

Expand Down Expand Up @@ -131,6 +150,7 @@

STATIC_ROOT = BASE_DIR / "static"
STATIC_URL = "/static/"
OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY', '')

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
Expand All @@ -149,3 +169,14 @@
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = "None"
# Media files configuration
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# File upload settings
FILE_UPLOAD_MAX_MEMORY_SIZE = 10485760 # 10MB
DATA_UPLOAD_MAX_MEMORY_SIZE = 10485760 # 10MB

CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
60 changes: 57 additions & 3 deletions backend/chat/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from django.utils import timezone
from nested_admin.nested import NestedModelAdmin, NestedStackedInline, NestedTabularInline

from chat.models import Conversation, Message, Role, Version

from chat.models import Conversation, Message, Role, Version,UploadedFile
from django.utils.html import format_html

class RoleAdmin(NestedModelAdmin):
list_display = ["id", "name"]
Expand Down Expand Up @@ -51,9 +51,20 @@ def queryset(self, request, queryset):
class ConversationAdmin(NestedModelAdmin):
actions = ["undelete_selected", "soft_delete_selected"]
inlines = [VersionInline]
list_display = ("title", "id", "created_at", "modified_at", "deleted_at", "version_count", "is_deleted", "user")
list_display = ("title", "id", "created_at", "modified_at","short_summary", "deleted_at", "version_count", "is_deleted", "user")
list_filter = (DeletedListFilter,)
ordering = ("-modified_at",)
readonly_fields = ("summary",)
fields = (
"title",
"summary",
"user",
"deleted_at",
)
def short_summary(self, obj):
return (obj.summary[:50] + "...") if obj.summary else "— No summary generated "
short_summary.short_description = "Summary"


def undelete_selected(self, request, queryset):
queryset.update(deleted_at=None)
Expand Down Expand Up @@ -86,7 +97,50 @@ class VersionAdmin(NestedModelAdmin):
list_display = ("id", "conversation", "parent_version", "root_message")



# NEW: UploadedFile Admin
class UploadedFileAdmin(admin.ModelAdmin):
list_display = ('original_filename', 'user', 'file_size_display', 'file_type', 'uploaded_at', 'file_link')
list_filter = ('file_type', 'uploaded_at')
search_fields = ('original_filename', 'user__username', 'user__email', 'file_hash')
readonly_fields = ('id', 'file_hash', 'file_size', 'file_type', 'uploaded_at', 'file_preview')

fieldsets = (
('File Information', {
'fields': ('id', 'file', 'file_preview', 'original_filename', 'file_type')
}),
('Metadata', {
'fields': ('file_size', 'file_hash', 'uploaded_at', 'user')
}),
)

@admin.display(description="File Size")
def file_size_display(self, obj):
"""Display file size in human-readable format"""
size = obj.file_size
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024.0:
return f"{size:.2f} {unit}"
size /= 1024.0
return f"{size:.2f} TB"

@admin.display(description="File Link")
def file_link(self, obj):
"""Display download link for file"""
if obj.file:
return format_html('<a href="{}" target="_blank">Download</a>', obj.file.url)
return "No file"

@admin.display(description="Preview")
def file_preview(self, obj):
"""Display image preview if file is an image"""
if obj.file and obj.file_type.startswith('image/'):
return format_html('<img src="{}" style="max-width: 200px; max-height: 200px;" />', obj.file.url)
return "No preview available"


admin.site.register(Role, RoleAdmin)
admin.site.register(Message, MessageAdmin)
admin.site.register(Conversation, ConversationAdmin)
admin.site.register(Version, VersionAdmin)
admin.site.register(UploadedFile, UploadedFileAdmin)
23 changes: 23 additions & 0 deletions backend/chat/management/commands/cleanup_conversations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from datetime import timedelta

from chat.models import Conversation

class Command(BaseCommand):
help = "Delete conversations older than 30 days"

def handle(self, *args, **kwargs):
cutoff_date = timezone.now() - timedelta(days=30)
old_conversations = Conversation.objects.filter(
created_at__lt=cutoff_date
)

count = old_conversations.count()
old_conversations.delete()

self.stdout.write(
self.style.SUCCESS(
f"Deleted {count} old conversations"
)
)
18 changes: 18 additions & 0 deletions backend/chat/migrations/0002_conversation_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-01-30 16:04

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chat', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='conversation',
name='summary',
field=models.TextField(blank=True, help_text='Auto-generated conversation summary', null=True),
),
]
18 changes: 18 additions & 0 deletions backend/chat/migrations/0003_alter_conversation_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-01-30 18:33

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chat', '0002_conversation_summary'),
]

operations = [
migrations.AlterField(
model_name='conversation',
name='summary',
field=models.TextField(blank=True, null=True),
),
]
34 changes: 34 additions & 0 deletions backend/chat/migrations/0004_uploadedfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 6.0.1 on 2026-01-30 20:02

import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chat', '0003_alter_conversation_summary'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='UploadedFile',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('file', models.FileField(upload_to='uploads/%Y/%m/%d/')),
('original_filename', models.CharField(max_length=255)),
('file_size', models.BigIntegerField(help_text='File size in bytes')),
('file_type', models.CharField(max_length=100)),
('file_hash', models.CharField(help_text='SHA-256 hash for duplicate detection', max_length=64, unique=True)),
('uploaded_at', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='uploaded_files', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-uploaded_at'],
'indexes': [models.Index(fields=['file_hash'], name='chat_upload_file_ha_43c6d5_idx'), models.Index(fields=['user', '-uploaded_at'], name='chat_upload_user_id_618957_idx')],
},
),
]
Loading