- Introduced STATUS_CHOICES in the Content model to restrict the status field to 'draft', 'review', and 'published'. - Created a new migration to reflect the updated status choices without altering existing data. - Removed 'completed' status from the frontend status color mapping for consistency.
187 lines
7.0 KiB
Python
187 lines
7.0 KiB
Python
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'),
|
|
('published', 'Published'),
|
|
]
|
|
status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='draft', help_text="Content workflow status (draft, review, published)")
|
|
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 task-related images (featured, desktop, mobile)"""
|
|
|
|
IMAGE_TYPE_CHOICES = [
|
|
('featured', 'Featured Image'),
|
|
('desktop', 'Desktop Image'),
|
|
('mobile', 'Mobile Image'),
|
|
('in_article', 'In-Article Image'),
|
|
]
|
|
|
|
task = models.ForeignKey(
|
|
Tasks,
|
|
on_delete=models.CASCADE,
|
|
related_name='images',
|
|
help_text="The task this image belongs to"
|
|
)
|
|
image_type = models.CharField(max_length=50, choices=IMAGE_TYPE_CHOICES, default='featured')
|
|
image_url = models.URLField(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 = ['task', 'position', '-created_at']
|
|
verbose_name = 'Image'
|
|
verbose_name_plural = 'Images'
|
|
indexes = [
|
|
models.Index(fields=['task', 'image_type']),
|
|
models.Index(fields=['status']),
|
|
models.Index(fields=['task', 'position']),
|
|
]
|
|
|
|
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"{self.task.title} - {self.image_type}"
|
|
|