from odoo import api, fields, models, _ class OtkSeoTemplate(models.Model): _name = 'otk.seo.template' _description = 'SEO Content Template' _order = 'sequence, name' name = fields.Char('Template Name', required=True, translate=True, help='A descriptive name for this template (e.g., "How-To Guide", "Product Review").') sequence = fields.Integer('Sequence', default=10, help='Order in which templates appear in selection lists. Lower numbers appear first.') active = fields.Boolean('Active', default=True, help='If unchecked, this template will not be available for selection.') content_type = fields.Selection([ ('blog_post', 'Blog Post'), ('product_desc', 'Product Description'), ('category_desc', 'Category Description'), ('landing_page', 'Landing Page Content'), ], string='Content Type', required=True, default='blog_post', help='The type of content this template is designed for. Templates only appear for matching content types.') description = fields.Text('Description', translate=True, help='Explanation of when and how to use this template. Shown to users when selecting a template.') # === Prompt Configuration === system_prompt = fields.Text('System Prompt', help='Background instructions for the AI defining its role, expertise, and constraints. This shapes the overall writing style and approach.') content_prompt_template = fields.Text('Content Prompt Template', help='The main prompt template sent to the AI. Use placeholders: {keywords}, {topic}, {product_name}, {tone}, {word_count}, {language}. These will be replaced with actual values.') title_prompt_template = fields.Text('Title Prompt Template', help='Template for generating the content title. Use placeholders to customize based on keywords or topic.') meta_prompt_template = fields.Text('Meta Description Prompt Template', help='Template for generating the SEO meta description. Should produce text between 120-160 characters.') # === Defaults === default_tone = fields.Selection([ ('professional', 'Professional'), ('casual', 'Casual'), ('technical', 'Technical'), ('persuasive', 'Persuasive'), ('conversational', 'Conversational'), ], string='Default Tone', default='professional', help='The default writing tone when using this template. Users can override this when generating content.') default_word_count = fields.Selection([ ('short', 'Short (300-500 words)'), ('medium', 'Medium (500-1000 words)'), ('long', 'Long (1000-2000 words)'), ], string='Default Length', default='medium', help='The default target word count when using this template. Users can override this when generating content.') # === Image Generation === include_images = fields.Boolean('Include Image Generation', default=True, help='If enabled, image generation will be included by default when using this template.') default_image_count = fields.Integer('Default Image Count', default=1, help='Default number of images to generate when using this template.') image_prompt_template = fields.Text('Image Prompt Template', help='Template for generating image prompts. Use placeholders: {title}, {keywords}, {section}. The AI will use this to create relevant images.') default_image_style = fields.Selection([ ('photorealistic', 'Photorealistic'), ('illustration', 'Illustration'), ('digital_art', 'Digital Art'), ('watercolor', 'Watercolor'), ('sketch', 'Sketch'), ('3d_render', '3D Render'), ], string='Default Image Style', default='photorealistic', help='The default visual style for generated images when using this template.') # === Formatting Options === include_toc = fields.Boolean('Include Table of Contents', help='If enabled, the AI will generate a table of contents at the beginning of the content. Best for longer articles.') include_faq = fields.Boolean('Include FAQ Section', help='If enabled, the AI will add a FAQ section at the end. Good for SEO and covering common questions.') include_cta = fields.Boolean('Include Call-to-Action', help='If enabled, the AI will include a call-to-action at the end of the content.') header_structure = fields.Selection([ ('h2_only', 'H2 Headers Only'), ('h2_h3', 'H2 and H3 Headers'), ('h2_h3_h4', 'H2, H3, and H4 Headers'), ], string='Header Structure', default='h2_h3', help='Heading hierarchy to use in the content. H2/H3 is recommended for most content; simpler structure for short content, deeper nesting for comprehensive guides.') # === Reference Library === reference_ids = fields.Many2many('otk.seo.reference', 'otk_seo_template_reference_rel', 'template_id', 'reference_id', string='Default References', domain="['|', ('content_type', '=', content_type), ('content_type', '=', 'all')]", help='References to include by default when using this template. Users can add or remove references when generating content.') # === Statistics === usage_count = fields.Integer('Times Used', compute='_compute_usage_count', help='Number of content items created using this template.') def _compute_usage_count(self): if not self.ids: return data = self.env['otk.seo.content']._read_group( [('template_id', 'in', self.ids)], ['template_id'], ['__count'], ) counts = {template.id: count for template, count in data} for record in self: record.usage_count = counts.get(record.id, 0) def action_view_contents(self): """View contents created with this template.""" self.ensure_one() return { 'type': 'ir.actions.act_window', 'res_model': 'otk.seo.content', 'view_mode': 'list,form', 'domain': [('template_id', '=', self.id)], 'name': _('Contents using %s') % self.name, }