HH/apps/social/models.py
2026-02-12 15:09:48 +03:00

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}"