from django.db import models from django.core.validators import MinValueValidator from igny8_core.auth.models import SiteSectorBaseModel from igny8_core.modules.planner.models import Clusters, ContentIdeas, Keywords class Tasks(SiteSectorBaseModel): """Tasks model for content generation queue""" STATUS_CHOICES = [ ('queued', 'Queued'), ('completed', 'Completed'), ] CONTENT_STRUCTURE_CHOICES = [ ('cluster_hub', 'Cluster Hub'), ('landing_page', 'Landing Page'), ('pillar_page', 'Pillar Page'), ('supporting_page', 'Supporting Page'), ] CONTENT_TYPE_CHOICES = [ ('blog_post', 'Blog Post'), ('article', 'Article'), ('guide', 'Guide'), ('tutorial', 'Tutorial'), ] title = models.CharField(max_length=255, db_index=True) description = models.TextField(blank=True, null=True) keywords = models.CharField(max_length=500, blank=True) # Comma-separated keywords (legacy) cluster = models.ForeignKey( Clusters, on_delete=models.SET_NULL, null=True, blank=True, related_name='tasks', limit_choices_to={'sector': models.F('sector')} ) keyword_objects = models.ManyToManyField( Keywords, blank=True, related_name='tasks', help_text="Individual keywords linked to this task" ) idea = models.ForeignKey( ContentIdeas, on_delete=models.SET_NULL, null=True, blank=True, related_name='tasks' ) content_structure = models.CharField(max_length=50, choices=CONTENT_STRUCTURE_CHOICES, default='blog_post') content_type = models.CharField(max_length=50, choices=CONTENT_TYPE_CHOICES, default='blog_post') status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='queued') # Content fields content = models.TextField(blank=True, null=True) # Generated content word_count = models.IntegerField(default=0) # SEO fields meta_title = models.CharField(max_length=255, blank=True, null=True) meta_description = models.TextField(blank=True, null=True) # WordPress integration assigned_post_id = models.IntegerField(null=True, blank=True) # WordPress post ID if published post_url = models.URLField(blank=True, null=True) # WordPress post URL created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'igny8_tasks' ordering = ['-created_at'] verbose_name = 'Task' verbose_name_plural = 'Tasks' indexes = [ models.Index(fields=['title']), models.Index(fields=['status']), models.Index(fields=['cluster']), models.Index(fields=['content_type']), models.Index(fields=['site', 'sector']), ] def __str__(self): return self.title class Content(SiteSectorBaseModel): """ Content model for storing final AI-generated article content. Separated from Task for content versioning and storage optimization. """ task = models.OneToOneField( Tasks, on_delete=models.CASCADE, related_name='content_record', help_text="The task this content belongs to" ) html_content = models.TextField(help_text="Final AI-generated HTML content") word_count = models.IntegerField(default=0, validators=[MinValueValidator(0)]) metadata = models.JSONField(default=dict, help_text="Additional metadata (SEO, structure, etc.)") title = models.CharField(max_length=255, blank=True, null=True) meta_title = models.CharField(max_length=255, blank=True, null=True) meta_description = models.TextField(blank=True, null=True) primary_keyword = models.CharField(max_length=255, blank=True, null=True) secondary_keywords = models.JSONField(default=list, blank=True, help_text="List of secondary keywords") tags = models.JSONField(default=list, blank=True, help_text="List of tags") categories = models.JSONField(default=list, blank=True, help_text="List of categories") STATUS_CHOICES = [ ('draft', 'Draft'), ('review', 'Review'), ('publish', 'Publish'), ] status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='draft', help_text="Content workflow status (draft, review, publish)") generated_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'igny8_content' ordering = ['-generated_at'] verbose_name = 'Content' verbose_name_plural = 'Contents' indexes = [ models.Index(fields=['task']), models.Index(fields=['generated_at']), ] def save(self, *args, **kwargs): """Automatically set account, site, and sector from task""" if self.task: self.account = self.task.account self.site = self.task.site self.sector = self.task.sector super().save(*args, **kwargs) def __str__(self): return f"Content for {self.task.title}" class Images(SiteSectorBaseModel): """Images model for content-related images (featured, desktop, mobile, in-article)""" IMAGE_TYPE_CHOICES = [ ('featured', 'Featured Image'), ('desktop', 'Desktop Image'), ('mobile', 'Mobile Image'), ('in_article', 'In-Article Image'), ] content = models.ForeignKey( Content, on_delete=models.CASCADE, related_name='images', null=True, blank=True, help_text="The content this image belongs to (preferred)" ) task = models.ForeignKey( Tasks, on_delete=models.CASCADE, related_name='images', null=True, blank=True, help_text="The task this image belongs to (legacy, use content instead)" ) image_type = models.CharField(max_length=50, choices=IMAGE_TYPE_CHOICES, default='featured') image_url = models.CharField(max_length=500, blank=True, null=True, help_text="URL of the generated/stored image") image_path = models.CharField(max_length=500, blank=True, null=True, help_text="Local path if stored locally") prompt = models.TextField(blank=True, null=True, help_text="Image generation prompt used") status = models.CharField(max_length=50, default='pending', help_text="Status: pending, generated, failed") position = models.IntegerField(default=0, help_text="Position for in-article images ordering") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'igny8_images' ordering = ['content', 'position', '-created_at'] verbose_name = 'Image' verbose_name_plural = 'Images' indexes = [ models.Index(fields=['content', 'image_type']), models.Index(fields=['task', 'image_type']), models.Index(fields=['status']), models.Index(fields=['content', 'position']), models.Index(fields=['task', 'position']), ] def save(self, *args, **kwargs): """Automatically set account, site, and sector from content or task""" # Prefer content over task if self.content: self.account = self.content.account self.site = self.content.site self.sector = self.content.sector elif self.task: self.account = self.task.account self.site = self.task.site self.sector = self.task.sector super().save(*args, **kwargs) def __str__(self): content_title = self.content.title if self.content else None task_title = self.task.title if self.task else None title = content_title or task_title or 'Unknown' return f"{title} - {self.image_type}"