234 lines
8.4 KiB
Python
234 lines
8.4 KiB
Python
# social/models.py
|
|
from django.db import models
|
|
from django.conf import settings
|
|
from django.utils import timezone
|
|
from django.contrib.auth import get_user_model
|
|
|
|
# Get the custom User model lazily
|
|
User = get_user_model()
|
|
|
|
# ============================================================================
|
|
# MODEL 1: SocialAccount - One model for all platform accounts
|
|
# ============================================================================
|
|
|
|
class SocialAccount(models.Model):
|
|
"""Unified account model for all social platforms"""
|
|
|
|
# FIX: Renamed 'user' to 'owner' to match the logic in views.py
|
|
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='social_accounts')
|
|
|
|
PLATFORM_CHOICES = [
|
|
('LI', 'LinkedIn'),
|
|
('GO', 'Google'),
|
|
('META', 'Meta (Facebook/Instagram)'),
|
|
('TT', 'TikTok'),
|
|
('X', 'X/Twitter'),
|
|
('YT', 'YouTube'),
|
|
]
|
|
|
|
platform_type = models.CharField(max_length=4, choices=PLATFORM_CHOICES)
|
|
platform_id = models.CharField(max_length=255, help_text="Platform-specific account ID")
|
|
name = models.CharField(max_length=255, help_text="Account name or display name")
|
|
|
|
# Flexible credentials storage
|
|
access_token = models.TextField(blank=True, null=True)
|
|
refresh_token = models.TextField(blank=True, null=True)
|
|
credentials_json = models.JSONField(default=dict, blank=True)
|
|
|
|
# Token management
|
|
expires_at = models.DateTimeField(null=True, blank=True)
|
|
is_permanent = models.BooleanField(default=False)
|
|
|
|
# Sync tracking
|
|
is_active = models.BooleanField(default=True)
|
|
last_synced_at = models.DateTimeField(null=True, blank=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
unique_together = [['platform_type', 'platform_id']]
|
|
ordering = ['-created_at']
|
|
|
|
def __str__(self):
|
|
return f"{self.get_platform_type_display()}: {self.name}"
|
|
|
|
def is_token_expired(self):
|
|
"""Check if token is expired or needs refresh"""
|
|
if self.is_permanent:
|
|
return False
|
|
if not self.expires_at:
|
|
return True
|
|
# Consider expired if within 24 hours of expiration
|
|
return timezone.now() >= (self.expires_at - timezone.timedelta(hours=24))
|
|
|
|
|
|
# ============================================================================
|
|
# MODEL 2: SocialContent - One model for posts/videos/tweets
|
|
# ============================================================================
|
|
|
|
class SocialContent(models.Model):
|
|
"""Unified content model for posts, videos, tweets"""
|
|
|
|
account = models.ForeignKey(SocialAccount, on_delete=models.CASCADE, related_name='contents')
|
|
platform_type = models.CharField(max_length=4)
|
|
source_platform = models.CharField(
|
|
max_length=4,
|
|
blank=True,
|
|
null=True,
|
|
help_text="Actual source platform for Meta (FB/IG)"
|
|
)
|
|
|
|
content_id = models.CharField(max_length=255, unique=True, db_index=True, help_text="Platform-specific content ID")
|
|
|
|
# Content data
|
|
title = models.CharField(max_length=255, blank=True, help_text="For videos/titles")
|
|
text = models.TextField(blank=True, help_text="For posts/tweets")
|
|
|
|
# Delta sync bookmark - CRITICAL for incremental updates
|
|
last_comment_sync_at = models.DateTimeField(default=timezone.now)
|
|
|
|
# Sync state
|
|
is_syncing = models.BooleanField(default=False, help_text="Is full sync in progress?")
|
|
|
|
# Platform-specific data
|
|
content_data = models.JSONField(default=dict)
|
|
|
|
# Timestamps
|
|
created_at = models.DateTimeField(help_text="Actual content creation time")
|
|
added_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['account', '-created_at']),
|
|
models.Index(fields=['platform_type', '-created_at']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.platform_type} Content: {self.content_id}"
|
|
|
|
|
|
# ============================================================================
|
|
# MODEL 3: SocialComment - One model for comments/reviews (original comments only)
|
|
# ============================================================================
|
|
|
|
class SocialComment(models.Model):
|
|
"""Unified comment model for comments, reviews (original comments only)"""
|
|
|
|
account = models.ForeignKey(SocialAccount, on_delete=models.CASCADE, related_name='comments')
|
|
content = models.ForeignKey(SocialContent, on_delete=models.CASCADE, related_name='comments')
|
|
platform_type = models.CharField(max_length=4)
|
|
source_platform = models.CharField(
|
|
max_length=4,
|
|
blank=True,
|
|
null=True,
|
|
help_text="Actual source platform for Meta (FB/IG)"
|
|
)
|
|
|
|
comment_id = models.CharField(max_length=255, unique=True, db_index=True, help_text="Platform-specific comment ID")
|
|
|
|
# Author information
|
|
author_name = models.CharField(max_length=255)
|
|
author_id = models.CharField(max_length=255, blank=True, null=True)
|
|
|
|
# Comment data
|
|
text = models.TextField()
|
|
|
|
# Platform-specific data
|
|
comment_data = models.JSONField(default=dict)
|
|
|
|
# --- Engagement Metrics ---
|
|
like_count = models.IntegerField(default=0, help_text="Number of likes")
|
|
reply_count = models.IntegerField(default=0, help_text="Number of replies")
|
|
rating = models.IntegerField(
|
|
null=True,
|
|
blank=True,
|
|
db_index=True,
|
|
help_text="Star rating (1-5) for review platforms like Google Reviews"
|
|
)
|
|
|
|
# --- Media ---
|
|
media_url = models.URLField(
|
|
max_length=500,
|
|
null=True,
|
|
blank=True,
|
|
help_text="URL to associated media (images/videos)"
|
|
)
|
|
|
|
# --- AI Bilingual Analysis ---
|
|
ai_analysis = models.JSONField(
|
|
default=dict,
|
|
blank=True,
|
|
db_index=True,
|
|
help_text="Complete AI analysis in bilingual format (en/ar) with sentiment, summaries, keywords, topics, entities, and emotions"
|
|
)
|
|
|
|
# Timestamps
|
|
created_at = models.DateTimeField(db_index=True)
|
|
added_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
# Webhook support
|
|
synced_via_webhook = models.BooleanField(default=False)
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['account', '-created_at']),
|
|
models.Index(fields=['content', '-created_at']),
|
|
models.Index(fields=['platform_type', '-created_at']),
|
|
models.Index(fields=['ai_analysis'], name='idx_comment_ai_analysis'),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.platform_type} Comment by {self.author_name}"
|
|
|
|
@property
|
|
def is_analyzed(self):
|
|
"""Check if comment has been AI analyzed"""
|
|
return bool(self.ai_analysis)
|
|
|
|
|
|
# ============================================================================
|
|
# MODEL 4: SocialReply - Separate model for replies to comments
|
|
# ============================================================================
|
|
|
|
class SocialReply(models.Model):
|
|
"""Unified reply model for replies to comments"""
|
|
|
|
account = models.ForeignKey(SocialAccount, on_delete=models.CASCADE, related_name='replies')
|
|
comment = models.ForeignKey(SocialComment, on_delete=models.CASCADE, related_name='replies')
|
|
platform_type = models.CharField(max_length=4)
|
|
source_platform = models.CharField(
|
|
max_length=4,
|
|
blank=True,
|
|
null=True,
|
|
help_text="Actual source platform for Meta (FB/IG)"
|
|
)
|
|
|
|
reply_id = models.CharField(max_length=255, unique=True, db_index=True, help_text="Platform-specific reply ID")
|
|
|
|
# Author information
|
|
author_name = models.CharField(max_length=255)
|
|
author_id = models.CharField(max_length=255, blank=True, null=True)
|
|
|
|
# Reply data
|
|
text = models.TextField()
|
|
|
|
# Platform-specific data
|
|
reply_data = models.JSONField(default=dict)
|
|
|
|
# Timestamps
|
|
created_at = models.DateTimeField(db_index=True)
|
|
added_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['comment', '-created_at']),
|
|
models.Index(fields=['account', '-created_at']),
|
|
models.Index(fields=['platform_type', '-created_at']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"Reply by {self.author_name} to {self.comment}"
|