docs cleanup

This commit is contained in:
alorig
2025-11-28 09:43:40 +05:00
parent d042f565ba
commit 00096ad884
31 changed files with 8 additions and 12483 deletions

View File

@@ -1,406 +0,0 @@
# Admin & Views Update Summary
**Date**: November 21, 2025
**Status**: ✅ **COMPLETED**
---
## Overview
Updated all Django admin interfaces, API views, filters, and serializers to use the new unified content architecture.
---
## ✅ Writer Module Updates
### Admin (`igny8_core/modules/writer/admin.py`)
#### 1. **TasksAdmin** - Simplified & Deprecated Fields Marked
```python
list_display = ['title', 'site', 'sector', 'status', 'cluster', 'created_at']
list_filter = ['status', 'site', 'sector', 'cluster']
readonly_fields = ['content_type', 'content_structure', 'entity_type', 'cluster_role', 'assigned_post_id', 'post_url']
```
**Changes:**
- Removed `content_type` and `word_count` from list display
- Added fieldsets with "Deprecated Fields" section (collapsed)
- Marked 6 deprecated fields as read-only
#### 2. **ContentAdmin** - Enhanced with New Structure
```python
list_display = ['title', 'entity_type', 'content_format', 'cluster_role', 'site', 'sector', 'source', 'sync_status', 'word_count', 'generated_at']
list_filter = ['entity_type', 'content_format', 'cluster_role', 'source', 'sync_status', 'status', 'site', 'sector', 'generated_at']
filter_horizontal = ['taxonomies']
readonly_fields = ['categories', 'tags']
```
**Changes:**
- Added `entity_type`, `content_format`, `cluster_role` to list display
- Added `source`, `sync_status` filters
- Added `taxonomies` M2M widget (filter_horizontal)
- Organized into 7 fieldsets:
- Basic Info
- Content Classification
- Content
- SEO
- Taxonomies & Attributes
- WordPress Sync
- Optimization
- Deprecated Fields (collapsed)
#### 3. **ContentTaxonomyAdmin** - NEW
```python
list_display = ['name', 'taxonomy_type', 'slug', 'parent', 'external_id', 'external_taxonomy', 'sync_status', 'count', 'site', 'sector']
list_filter = ['taxonomy_type', 'sync_status', 'site', 'sector', 'parent']
filter_horizontal = ['clusters']
```
**Features:**
- Full CRUD for categories, tags, product attributes
- WordPress sync fields visible
- Semantic cluster mapping via M2M widget
- Hierarchical taxonomy support (parent field)
#### 4. **ContentAttributeAdmin** - NEW
```python
list_display = ['name', 'value', 'attribute_type', 'content', 'cluster', 'external_id', 'source', 'site', 'sector']
list_filter = ['attribute_type', 'source', 'site', 'sector']
```
**Features:**
- Product specs, service modifiers, semantic facets
- WordPress/WooCommerce sync fields
- Link to content or cluster
---
### Views (`igny8_core/modules/writer/views.py`)
#### 1. **TasksViewSet** - Simplified Filters
```python
filterset_fields = ['status', 'cluster_id'] # Removed deprecated fields
```
#### 2. **ContentViewSet** - Enhanced Filters
```python
queryset = Content.objects.select_related('task', 'site', 'sector', 'cluster').prefetch_related('taxonomies', 'attributes')
filterset_fields = [
'task_id',
'status',
'entity_type', # NEW
'content_format', # NEW
'cluster_role', # NEW
'source', # NEW
'sync_status', # NEW
'cluster',
'external_type', # NEW
]
search_fields = ['title', 'meta_title', 'primary_keyword', 'external_url'] # Added external_url
ordering_fields = ['generated_at', 'updated_at', 'word_count', 'status', 'entity_type', 'content_format']
```
**Changes:**
- Added 5 new filter fields for unified structure
- Optimized queryset with select_related & prefetch_related
- Added external_url to search fields
#### 3. **ContentTaxonomyViewSet** - NEW
```python
Endpoint: /api/v1/writer/taxonomies/
Methods: GET, POST, PUT, PATCH, DELETE
filterset_fields = ['taxonomy_type', 'sync_status', 'parent', 'external_id', 'external_taxonomy']
search_fields = ['name', 'slug', 'description', 'external_taxonomy']
ordering = ['taxonomy_type', 'name']
```
**Custom Actions:**
- `POST /api/v1/writer/taxonomies/{id}/map_to_cluster/` - Map taxonomy to semantic cluster
- `GET /api/v1/writer/taxonomies/{id}/contents/` - Get all content for taxonomy
#### 4. **ContentAttributeViewSet** - NEW
```python
Endpoint: /api/v1/writer/attributes/
Methods: GET, POST, PUT, PATCH, DELETE
filterset_fields = ['attribute_type', 'source', 'content', 'cluster', 'external_id']
search_fields = ['name', 'value', 'external_attribute_name', 'content__title']
ordering = ['attribute_type', 'name']
```
---
### URLs (`igny8_core/modules/writer/urls.py`)
**New Routes Added:**
```python
router.register(r'taxonomies', ContentTaxonomyViewSet, basename='taxonomy')
router.register(r'attributes', ContentAttributeViewSet, basename='attribute')
```
**Available Endpoints:**
- `/api/v1/writer/tasks/`
- `/api/v1/writer/images/`
- `/api/v1/writer/content/`
- `/api/v1/writer/taxonomies/` ✨ NEW
- `/api/v1/writer/attributes/` ✨ NEW
---
## ✅ Planner Module Updates
### Admin (`igny8_core/modules/planner/admin.py`)
#### **ContentIdeasAdmin** - Updated for New Structure
```python
list_display = ['idea_title', 'site', 'sector', 'description_preview', 'site_entity_type', 'cluster_role', 'status', 'keyword_cluster', 'estimated_word_count', 'created_at']
list_filter = ['status', 'site_entity_type', 'cluster_role', 'site', 'sector']
readonly_fields = ['content_structure', 'content_type']
```
**Changes:**
- Replaced `content_structure`, `content_type` with `site_entity_type`, `cluster_role` in display
- Marked old fields as read-only in collapsed fieldset
- Updated filters to use new fields
**Fieldsets:**
- Basic Info
- Content Planning (site_entity_type, cluster_role)
- Keywords & Clustering
- Deprecated Fields (collapsed)
---
### Views (`igny8_core/modules/planner/views.py`)
#### **ContentIdeasViewSet** - Updated Filters
```python
filterset_fields = ['status', 'keyword_cluster_id', 'site_entity_type', 'cluster_role'] # Updated
```
**Changes:**
- Replaced `content_structure`, `content_type` with `site_entity_type`, `cluster_role`
---
## 📊 New API Endpoints Summary
### Writer Taxonomies
```bash
GET /api/v1/writer/taxonomies/ # List all taxonomies
POST /api/v1/writer/taxonomies/ # Create taxonomy
GET /api/v1/writer/taxonomies/{id}/ # Get taxonomy
PUT /api/v1/writer/taxonomies/{id}/ # Update taxonomy
DELETE /api/v1/writer/taxonomies/{id}/ # Delete taxonomy
POST /api/v1/writer/taxonomies/{id}/map_to_cluster/ # Map to cluster
GET /api/v1/writer/taxonomies/{id}/contents/ # Get taxonomy contents
```
**Filters:**
- `?taxonomy_type=category` (category, tag, product_cat, product_tag, product_attr, service_cat)
- `?sync_status=imported` (native, imported, synced)
- `?parent=5` (hierarchical filtering)
- `?external_id=12` (WP term ID)
- `?external_taxonomy=category` (WP taxonomy name)
**Search:**
- `?search=SEO` (searches name, slug, description)
---
### Writer Attributes
```bash
GET /api/v1/writer/attributes/ # List all attributes
POST /api/v1/writer/attributes/ # Create attribute
GET /api/v1/writer/attributes/{id}/ # Get attribute
PUT /api/v1/writer/attributes/{id}/ # Update attribute
DELETE /api/v1/writer/attributes/{id}/ # Delete attribute
```
**Filters:**
- `?attribute_type=product_spec` (product_spec, service_modifier, semantic_facet)
- `?source=wordpress` (blueprint, manual, import, wordpress)
- `?content=42` (filter by content ID)
- `?cluster=8` (filter by cluster ID)
- `?external_id=101` (WP attribute term ID)
**Search:**
- `?search=Color` (searches name, value, external_attribute_name, content title)
---
### Enhanced Content Filters
```bash
GET /api/v1/writer/content/?entity_type=post
GET /api/v1/writer/content/?content_format=listicle
GET /api/v1/writer/content/?cluster_role=hub
GET /api/v1/writer/content/?source=wordpress
GET /api/v1/writer/content/?sync_status=imported
GET /api/v1/writer/content/?external_type=product
GET /api/v1/writer/content/?search=seo+tools
```
---
## 🔄 Backward Compatibility
### Deprecated Fields Still Work
**Tasks:**
- `content_type`, `content_structure` → Read-only in admin
- Still in database, marked with help text
**Content:**
- `categories`, `tags` (JSON) → Read-only in admin
- Data migrated to `taxonomies` M2M
- Old fields preserved for transition period
**ContentIdeas:**
- `content_structure`, `content_type` → Read-only in admin
- Replaced by `site_entity_type`, `cluster_role`
---
## 📝 Django Admin Features
### New Admin Capabilities
1. **Content Taxonomy Management**
- Create/edit categories, tags, product attributes
- Map to semantic clusters (M2M widget)
- View WordPress sync status
- Hierarchical taxonomy support
2. **Content Attribute Management**
- Create product specs (Color: Blue, Size: Large)
- Create service modifiers (Location: NYC)
- Create semantic facets (Target Audience: Enterprise)
- Link to content or clusters
3. **Enhanced Content Admin**
- Filter by entity_type, content_format, cluster_role
- Filter by source (igny8, wordpress, shopify)
- Filter by sync_status (native, imported, synced)
- Assign taxonomies via M2M widget
- View WordPress sync metadata
4. **Simplified Task Admin**
- Deprecated fields hidden in collapsed section
- Focus on core planning fields
- Read-only access to legacy data
---
## 🧪 Testing Checklist
### Admin Interface
- ✅ Tasks admin loads without errors
- ✅ Content admin shows new fields
- ✅ ContentTaxonomy admin registered
- ✅ ContentAttribute admin registered
- ✅ ContentIdeas admin updated
- ✅ All deprecated fields marked read-only
- ✅ Fieldsets organized properly
### API Endpoints
-`/api/v1/writer/taxonomies/` accessible
-`/api/v1/writer/attributes/` accessible
- ✅ Content filters work with new fields
- ✅ ContentIdeas filters updated
- ✅ No 500 errors on backend restart
### Database
- ✅ All migrations applied
- ✅ New tables exist
- ✅ New fields in Content table
- ✅ M2M relationships functional
---
## 📚 Usage Examples
### Create Taxonomy via API
```bash
POST /api/v1/writer/taxonomies/
{
"name": "SEO",
"slug": "seo",
"taxonomy_type": "category",
"description": "All about SEO",
"site_id": 5,
"sector_id": 3
}
```
### Create Product Attribute via API
```bash
POST /api/v1/writer/attributes/
{
"name": "Color",
"value": "Blue",
"attribute_type": "product_spec",
"content": 42,
"external_id": 101,
"external_attribute_name": "pa_color",
"source": "wordpress",
"site_id": 5,
"sector_id": 3
}
```
### Filter Content by New Fields
```bash
GET /api/v1/writer/content/?entity_type=post&content_format=listicle&cluster_role=hub
GET /api/v1/writer/content/?source=wordpress&sync_status=imported
GET /api/v1/writer/taxonomies/?taxonomy_type=category&sync_status=imported
GET /api/v1/writer/attributes/?attribute_type=product_spec&source=wordpress
```
---
## 🎯 Next Steps
### Ready for Frontend Integration
1. **Site Settings → Content Types Tab**
- Display taxonomies from `/api/v1/writer/taxonomies/`
- Show attributes from `/api/v1/writer/attributes/`
- Enable/disable sync per type
- Set fetch limits
2. **Content Management**
- Filter content by `entity_type`, `content_format`, `cluster_role`
- Display WordPress sync status
- Show assigned taxonomies
- View product attributes
3. **WordPress Import UI**
- Fetch structure from plugin
- Create ContentTaxonomy records
- Import content titles
- Map to clusters
---
## ✅ Summary
**All admin interfaces and API views updated to use unified content architecture.**
**Changes:**
- ✅ 3 new admin classes registered
- ✅ 2 new ViewSets added
- ✅ 7 new filter fields in Content
- ✅ 5 new filter fields in Taxonomies
- ✅ 5 new filter fields in Attributes
- ✅ All deprecated fields marked read-only
- ✅ Backward compatibility maintained
- ✅ Backend restart successful
- ✅ No linter errors
**New Endpoints:**
- `/api/v1/writer/taxonomies/` (full CRUD + custom actions)
- `/api/v1/writer/attributes/` (full CRUD)
**Status:** Production-ready, fully functional, WordPress integration prepared.

View File

@@ -1,482 +0,0 @@
# ✅ Cleanup Complete - Unified Content Architecture
**Date**: November 22, 2025
**Status**: ✅ **COMPLETE**
---
## Summary
Successfully cleaned up all redundant and deprecated fields from the IGNY8 backend, migrated data to the new unified content architecture, and created a Sites content types interface endpoint.
---
## What Was Completed
### 1. ✅ Removed Deprecated Fields from Models
**ContentIdeas Model** (`/backend/igny8_core/business/planning/models.py`):
- ❌ Removed: `content_structure` (replaced by `cluster_role`)
- ❌ Removed: `content_type` (replaced by `site_entity_type`)
- ✅ Kept: `site_entity_type` (post, page, product, service, taxonomy_term)
- ✅ Kept: `cluster_role` (hub, supporting, attribute)
**Tasks Model** (`/backend/igny8_core/business/content/models.py`):
- ❌ Removed: `content_structure` (replaced by `cluster_role`)
- ❌ Removed: `content_type` (replaced by `entity_type`)
- ❌ Removed: `content` (moved to Content model)
- ❌ Removed: `word_count` (moved to Content model)
- ❌ Removed: `meta_title` (moved to Content model)
- ❌ Removed: `meta_description` (moved to Content model)
- ❌ Removed: `assigned_post_id` (moved to Content model)
- ❌ Removed: `post_url` (moved to Content model)
- ✅ Kept: `entity_type` (post, page, product, service, taxonomy_term)
- ✅ Kept: `cluster_role` (hub, supporting, attribute)
**Content Model** (`/backend/igny8_core/business/content/models.py`):
- ❌ Removed: `categories` (JSON field, replaced by `taxonomies` M2M)
- ❌ Removed: `tags` (JSON field, replaced by `taxonomies` M2M)
- ✅ Kept: `entity_type` (post, page, product, service, taxonomy_term)
- ✅ Kept: `content_format` (article, listicle, guide, comparison, review, roundup)
- ✅ Kept: `cluster_role` (hub, supporting, attribute)
- ✅ Kept: `taxonomies` (M2M to ContentTaxonomy)
---
### 2. ✅ Updated Admin Interfaces
**ContentIdeas Admin** (`/backend/igny8_core/modules/planner/admin.py`):
- Removed deprecated fields from `readonly_fields`
- Removed "Deprecated Fields" fieldset
- Updated `list_display` to show only new fields
- Updated `list_filter` to use only new fields
**Tasks Admin** (`/backend/igny8_core/modules/writer/admin.py`):
- Added `entity_type` and `cluster_role` to `list_display`
- Added `entity_type` and `cluster_role` to `list_filter`
- Removed deprecated fields from fieldsets
- Added "Content Classification" fieldset with new fields
**Content Admin** (`/backend/igny8_core/modules/writer/admin.py`):
- Removed deprecated `categories` and `tags` from `readonly_fields`
- Removed "Deprecated Fields" fieldset
- All new fields properly displayed and filterable
---
### 3. ✅ Updated API Views
**ContentIdeasViewSet** (`/backend/igny8_core/modules/planner/views.py`):
- `filterset_fields`: Uses `site_entity_type` and `cluster_role` (no deprecated fields)
**TasksViewSet** (`/backend/igny8_core/modules/writer/views.py`):
- `filterset_fields`: Added `entity_type`, `cluster_role`
- `ordering_fields`: Removed `word_count` (no longer in model)
**ContentViewSet** (`/backend/igny8_core/modules/writer/views.py`):
- Already updated with all new fields
- Filters working correctly
---
### 4. ✅ Data Migration
**Migration**: `0006_cleanup_migrate_and_drop_deprecated_fields.py`
**Data Migration Logic**:
- Ensured all `Tasks` have default `entity_type` ('post') and `cluster_role` ('hub')
- Ensured all `Content` inherit `entity_type` and `cluster_role` from their related `Task`
- Set defaults for any `Content` without a task
**Database Changes**:
- Dropped `content_structure` column from `igny8_content_ideas`
- Dropped `content_type` column from `igny8_content_ideas`
- Dropped `content_structure` column from `igny8_tasks`
- Dropped `content_type` column from `igny8_tasks`
- Dropped `content` column from `igny8_tasks`
- Dropped `word_count` column from `igny8_tasks`
- Dropped `meta_title` column from `igny8_tasks`
- Dropped `meta_description` column from `igny8_tasks`
- Dropped `assigned_post_id` column from `igny8_tasks`
- Dropped `post_url` column from `igny8_tasks`
- Dropped `categories` column from `igny8_content`
- Dropped `tags` column from `igny8_content`
---
### 5. ✅ Created Sites Content Types Interface
**New Endpoint**: `GET /api/v1/integration/integrations/{id}/content-types/`
**Purpose**: Show WordPress synced content types with counts
**Response Format**:
```json
{
"success": true,
"data": {
"post_types": {
"post": {
"label": "Posts",
"count": 123,
"synced_count": 50,
"enabled": true,
"fetch_limit": 100,
"last_synced": "2025-11-22T10:00:00Z"
},
"page": {
"label": "Pages",
"count": 12,
"synced_count": 12,
"enabled": true,
"fetch_limit": 50,
"last_synced": "2025-11-22T10:00:00Z"
},
"product": {
"label": "Products",
"count": 456,
"synced_count": 200,
"enabled": true,
"fetch_limit": 200,
"last_synced": null
}
},
"taxonomies": {
"category": {
"label": "Categories",
"count": 25,
"synced_count": 25,
"enabled": true,
"fetch_limit": 100,
"last_synced": "2025-11-22T10:00:00Z"
},
"post_tag": {
"label": "Tags",
"count": 102,
"synced_count": 80,
"enabled": true,
"fetch_limit": 200,
"last_synced": "2025-11-22T10:00:00Z"
},
"product_cat": {
"label": "Product Categories",
"count": 15,
"synced_count": 15,
"enabled": false,
"fetch_limit": 50,
"last_synced": null
}
},
"last_structure_fetch": "2025-11-22T10:00:00Z",
"plugin_connection_enabled": true,
"two_way_sync_enabled": true
}
}
```
**Features**:
- Shows WP content type counts from plugin
- Shows synced counts from IGNY8 database
- Shows enabled/disabled status
- Shows fetch limits
- Shows last sync timestamps
---
## Unified Field Structure
### Entity Type (Standardized)
**Field**: `entity_type`
**Used In**: ContentIdeas (`site_entity_type`), Tasks, Content
**Values**:
- `post` - Blog posts, articles
- `page` - Static pages
- `product` - WooCommerce products
- `service` - Service pages
- `taxonomy_term` - Category/tag pages
### Content Format (For Posts Only)
**Field**: `content_format`
**Used In**: Content
**Values**:
- `article` - Standard article
- `listicle` - List-based content
- `guide` - How-to guide
- `comparison` - Comparison article
- `review` - Product/service review
- `roundup` - Roundup/collection
### Cluster Role
**Field**: `cluster_role`
**Used In**: ContentIdeas, Tasks, Content
**Values**:
- `hub` - Main cluster page
- `supporting` - Supporting content
- `attribute` - Attribute-focused page
---
## Database Schema (Final)
### igny8_content_ideas
```sql
- id
- idea_title
- description
- site_entity_type NEW (replaces content_structure + content_type)
- cluster_role NEW (replaces content_structure)
- keyword_cluster_id
- taxonomy_id
- status
- estimated_word_count
- site_id, sector_id, account_id
- created_at, updated_at
```
### igny8_tasks
```sql
- id
- title
- description
- keywords
- entity_type NEW (replaces content_type)
- cluster_role NEW (replaces content_structure)
- cluster_id
- idea_id
- taxonomy_id
- status
- site_id, sector_id, account_id
- created_at, updated_at
```
### igny8_content
```sql
- id
- task_id
- cluster_id
- title
- html_content
- word_count
- entity_type NEW
- content_format NEW
- cluster_role NEW
- external_type (WP post type)
- external_id, external_url
- source, sync_status
- meta_title, meta_description
- primary_keyword, secondary_keywords
- taxonomies (M2M via ContentTaxonomyRelation) NEW
- site_id, sector_id, account_id
- generated_at, updated_at
```
### igny8_content_taxonomies ✅ NEW
```sql
- id
- name, slug
- taxonomy_type (category, tag, product_cat, product_tag, product_attr)
- parent_id
- external_id, external_taxonomy
- sync_status
- count, description
- metadata
- site_id, sector_id, account_id
- created_at, updated_at
```
### igny8_content_attributes ✅ NEW
```sql
- id
- content_id, task_id, cluster_id
- attribute_type (product_spec, service_modifier, semantic_facet)
- name, value
- source (blueprint, manual, import, wordpress)
- metadata
- external_id, external_attribute_name
- site_id, sector_id, account_id
- created_at, updated_at
```
---
## API Endpoints (Updated)
### Planner Module
**ContentIdeas**:
- `GET /api/v1/planner/ideas/` - List (filters: `status`, `site_entity_type`, `cluster_role`)
- `POST /api/v1/planner/ideas/` - Create
- `GET /api/v1/planner/ideas/{id}/` - Retrieve
- `PATCH /api/v1/planner/ideas/{id}/` - Update
- `DELETE /api/v1/planner/ideas/{id}/` - Delete
### Writer Module
**Tasks**:
- `GET /api/v1/writer/tasks/` - List (filters: `status`, `entity_type`, `cluster_role`, `cluster_id`)
- `POST /api/v1/writer/tasks/` - Create
- `GET /api/v1/writer/tasks/{id}/` - Retrieve
- `PATCH /api/v1/writer/tasks/{id}/` - Update
- `DELETE /api/v1/writer/tasks/{id}/` - Delete
**Content**:
- `GET /api/v1/writer/content/` - List (filters: `entity_type`, `content_format`, `cluster_role`, `source`, `sync_status`, `external_type`)
- `POST /api/v1/writer/content/` - Create
- `GET /api/v1/writer/content/{id}/` - Retrieve
- `PATCH /api/v1/writer/content/{id}/` - Update
- `DELETE /api/v1/writer/content/{id}/` - Delete
**ContentTaxonomy** ✅ NEW:
- `GET /api/v1/writer/taxonomies/` - List
- `POST /api/v1/writer/taxonomies/` - Create
- `GET /api/v1/writer/taxonomies/{id}/` - Retrieve
- `PATCH /api/v1/writer/taxonomies/{id}/` - Update
- `DELETE /api/v1/writer/taxonomies/{id}/` - Delete
**ContentAttribute** ✅ NEW:
- `GET /api/v1/writer/attributes/` - List
- `POST /api/v1/writer/attributes/` - Create
- `GET /api/v1/writer/attributes/{id}/` - Retrieve
- `PATCH /api/v1/writer/attributes/{id}/` - Update
- `DELETE /api/v1/writer/attributes/{id}/` - Delete
### Integration Module ✅ NEW
**Content Types Summary**:
- `GET /api/v1/integration/integrations/{id}/content-types/` - Get synced content types with counts
---
## Frontend Integration
### Sites Settings - Content Types Tab
**URL**: `/sites/{site_id}/settings` → "Content Types" tab
**API Call**:
```javascript
// Get integration for site
const integration = await api.get(`/integration/integrations/?site_id=${siteId}&platform=wordpress`);
// Get content types summary
const summary = await api.get(`/integration/integrations/${integration.id}/content-types/`);
```
**Display**:
1. **Post Types Section**
- Show each post type with label, count, synced count
- Enable/disable toggle
- Fetch limit input
- Last synced timestamp
- Sync button
2. **Taxonomies Section**
- Show each taxonomy with label, count, synced count
- Enable/disable toggle
- Fetch limit input
- Last synced timestamp
- Sync button
3. **Actions**
- "Fetch Structure" button - Refresh from WordPress
- "Sync All" button - Import enabled types
---
## Testing Checklist
### ✅ Backend Tests
- [x] Migrations applied successfully
- [x] No deprecated fields in models
- [x] Admin interfaces show only new fields
- [x] API endpoints return correct data
- [x] Filters work with new fields
- [x] Content types endpoint returns data
- [x] Backend restarted successfully
### ⏳ Frontend Tests (Pending)
- [ ] Sites settings page loads
- [ ] Content Types tab visible
- [ ] Content types summary displays
- [ ] Enable/disable toggles work
- [ ] Fetch limit inputs work
- [ ] Sync buttons trigger API calls
- [ ] Counts update after sync
---
## Migration Timeline
| Phase | Description | Status |
|-------|-------------|--------|
| Phase 1 | Add new models and fields | ✅ Complete |
| Phase 2 | Migrate data to new structure | ✅ Complete |
| Phase 3 | Mark deprecated fields | ✅ Complete |
| Phase 4 | Update admin interfaces | ✅ Complete |
| Phase 5 | Update API views | ✅ Complete |
| Phase 6 | Migrate data and drop columns | ✅ Complete |
| Phase 7 | Create Sites interface endpoint | ✅ Complete |
| Phase 8 | Build frontend UI | ⏳ Pending |
---
## Next Steps
### Immediate (Backend Complete ✅)
1. ✅ All deprecated fields removed
2. ✅ All admin interfaces updated
3. ✅ All API endpoints updated
4. ✅ Data migrated successfully
5. ✅ Sites content types endpoint created
### Soon (Frontend)
1. Create "Content Types" tab in Sites Settings
2. Display content types summary
3. Add enable/disable toggles
4. Add fetch limit inputs
5. Add sync buttons
6. Test end-to-end workflow
### Later (Advanced Features)
1. Implement `IntegrationService.fetch_content_structure()`
2. Implement `IntegrationService.import_taxonomies()`
3. Implement `IntegrationService.import_content_titles()`
4. Add AI semantic mapping for clusters
5. Add bulk content optimization
---
## Summary
**Status**: ✅ **BACKEND CLEANUP COMPLETE**
All redundant and deprecated fields have been removed from the backend. The unified content architecture is now fully implemented and operational. The Sites content types interface endpoint is ready for frontend integration.
**What Changed**:
- ❌ Removed 14 deprecated fields across 3 models
- ✅ Standardized on `entity_type`, `content_format`, `cluster_role`
- ✅ Replaced JSON fields with proper M2M relationships
- ✅ Updated all admin interfaces
- ✅ Updated all API endpoints
- ✅ Created Sites content types summary endpoint
**Result**: Clean, standardized, production-ready content architecture with WordPress integration support.
---
**Completion Time**: ~2 hours
**Files Modified**: 12
**Migrations Created**: 2
**Database Columns Dropped**: 14
**New API Endpoints**: 1
**READY FOR FRONTEND INTEGRATION**

View File

@@ -1,394 +0,0 @@
# ✅ Complete Update Checklist - All Verified
**Date**: November 21, 2025
**Status**: ✅ **ALL COMPLETE & VERIFIED**
---
## ✅ Phase 1: Database Migrations
### Migrations Applied
```
writer
✅ 0001_initial
✅ 0002_phase1_add_unified_taxonomy_and_attributes
✅ 0003_phase1b_fix_taxonomy_relation
✅ 0004_phase2_migrate_data_to_unified_structure
✅ 0005_phase3_mark_deprecated_fields
planner
✅ 0001_initial
✅ 0002_initial
```
### New Tables Created
```sql
igny8_content_taxonomy_terms (16 columns, 23 indexes)
igny8_content_attributes (16 columns, 15 indexes)
igny8_content_taxonomy_relations (4 columns, 3 indexes)
igny8_content_taxonomy_terms_clusters (M2M table)
```
### New Fields in Content Table
```sql
cluster_id (bigint)
cluster_role (varchar)
content_format (varchar)
external_type (varchar)
```
---
## ✅ Phase 2: Models Updated
### Writer Module (`igny8_core/business/content/models.py`)
#### Content Model
- ✅ Added `content_format` field (article, listicle, guide, comparison, review, roundup)
- ✅ Added `cluster_role` field (hub, supporting, attribute)
- ✅ Added `external_type` field (WP post type)
- ✅ Added `cluster` FK (direct cluster relationship)
- ✅ Added `taxonomies` M2M (via ContentTaxonomyRelation)
- ✅ Updated `entity_type` choices (post, page, product, service, taxonomy_term)
- ✅ Marked `categories` and `tags` as deprecated
#### ContentTaxonomy Model (NEW)
- ✅ Unified taxonomy model created
- ✅ Supports categories, tags, product attributes
- ✅ WordPress sync fields (external_id, external_taxonomy, sync_status)
- ✅ Hierarchical support (parent FK)
- ✅ Cluster mapping (M2M to Clusters)
- ✅ 23 indexes for performance
#### ContentAttribute Model (NEW)
- ✅ Enhanced from ContentAttributeMap
- ✅ Added attribute_type (product_spec, service_modifier, semantic_facet)
- ✅ Added WP sync fields (external_id, external_attribute_name)
- ✅ Added cluster FK for semantic attributes
- ✅ 15 indexes for performance
#### Tasks Model
- ✅ Marked 10 fields as deprecated (help_text updated)
- ✅ Fields preserved for backward compatibility
---
## ✅ Phase 3: Admin Interfaces Updated
### Writer Admin (`igny8_core/modules/writer/admin.py`)
#### TasksAdmin
- ✅ Simplified list_display (removed deprecated fields)
- ✅ Updated list_filter (removed content_type, content_structure)
- ✅ Added fieldsets with "Deprecated Fields" section (collapsed)
- ✅ Marked 6 fields as readonly
#### ContentAdmin
- ✅ Added entity_type, content_format, cluster_role to list_display
- ✅ Added source, sync_status to list_filter
- ✅ Created 7 organized fieldsets
- ✅ Removed filter_horizontal for taxonomies (through model issue)
- ✅ Marked categories, tags as readonly
#### ContentTaxonomyAdmin (NEW)
- ✅ Full CRUD interface
- ✅ List display with all key fields
- ✅ Filters: taxonomy_type, sync_status, parent
- ✅ Search: name, slug, description
- ✅ filter_horizontal for clusters M2M
- ✅ 4 organized fieldsets
#### ContentAttributeAdmin (NEW)
- ✅ Full CRUD interface
- ✅ List display with all key fields
- ✅ Filters: attribute_type, source
- ✅ Search: name, value, external_attribute_name
- ✅ 3 organized fieldsets
### Planner Admin (`igny8_core/modules/planner/admin.py`)
#### ContentIdeasAdmin
- ✅ Replaced content_structure, content_type with site_entity_type, cluster_role
- ✅ Updated list_display
- ✅ Updated list_filter
- ✅ Added fieldsets with deprecated fields section
- ✅ Marked old fields as readonly
---
## ✅ Phase 4: API Views & Serializers Updated
### Writer Views (`igny8_core/modules/writer/views.py`)
#### TasksViewSet
- ✅ Removed deprecated filters (content_type, content_structure)
- ✅ Simplified filterset_fields to ['status', 'cluster_id']
#### ContentViewSet
- ✅ Optimized queryset (select_related, prefetch_related)
- ✅ Added 5 new filters: entity_type, content_format, cluster_role, source, sync_status
- ✅ Added external_type filter
- ✅ Added external_url to search_fields
- ✅ Updated ordering_fields
#### ContentTaxonomyViewSet (NEW)
- ✅ Full CRUD endpoints
- ✅ Filters: taxonomy_type, sync_status, parent, external_id, external_taxonomy
- ✅ Search: name, slug, description
- ✅ Custom action: map_to_cluster
- ✅ Custom action: contents (get all content for taxonomy)
- ✅ Optimized queryset
#### ContentAttributeViewSet (NEW)
- ✅ Full CRUD endpoints
- ✅ Filters: attribute_type, source, content, cluster, external_id
- ✅ Search: name, value, external_attribute_name
- ✅ Optimized queryset
### Writer Serializers (`igny8_core/modules/writer/serializers.py`)
#### ContentTaxonomySerializer (NEW)
- ✅ All fields exposed
- ✅ parent_name computed field
- ✅ cluster_names computed field
- ✅ content_count computed field
#### ContentAttributeSerializer (NEW)
- ✅ All fields exposed
- ✅ content_title computed field
- ✅ cluster_name computed field
#### ContentTaxonomyRelationSerializer (NEW)
- ✅ Through model serializer
- ✅ content_title, taxonomy_name, taxonomy_type computed fields
### Planner Views (`igny8_core/modules/planner/views.py`)
#### ContentIdeasViewSet
- ✅ Updated filterset_fields: replaced content_structure, content_type with site_entity_type, cluster_role
---
## ✅ Phase 5: URL Routes Updated
### Writer URLs (`igny8_core/modules/writer/urls.py`)
- ✅ Added taxonomies route: `/api/v1/writer/taxonomies/`
- ✅ Added attributes route: `/api/v1/writer/attributes/`
---
## ✅ Phase 6: Backend Status
### Server
- ✅ Backend restarted successfully
- ✅ 4 gunicorn workers running
- ✅ No errors in logs
- ✅ No linter errors
### Database
- ✅ All migrations applied
- ✅ New tables verified
- ✅ New fields verified
- ✅ M2M relationships functional
---
## 📊 Complete Feature Matrix
### Content Management
| Feature | Old | New | Status |
|---------|-----|-----|--------|
| Entity Type | Multiple overlapping fields | Single `entity_type` + `content_format` | ✅ |
| Categories/Tags | JSON arrays | M2M ContentTaxonomy | ✅ |
| Attributes | ContentAttributeMap | Enhanced ContentAttribute | ✅ |
| WP Sync | No support | Full sync fields | ✅ |
| Cluster Mapping | Via mapping table | Direct FK + M2M | ✅ |
### Admin Interfaces
| Model | List Display | Filters | Fieldsets | Status |
|-------|-------------|---------|-----------|--------|
| Tasks | Updated | Simplified | 3 sections | ✅ |
| Content | Enhanced | 9 filters | 7 sections | ✅ |
| ContentTaxonomy | NEW | 5 filters | 4 sections | ✅ |
| ContentAttribute | NEW | 4 filters | 3 sections | ✅ |
| ContentIdeas | Updated | Updated | 4 sections | ✅ |
### API Endpoints
| Endpoint | Methods | Filters | Custom Actions | Status |
|----------|---------|---------|----------------|--------|
| /writer/tasks/ | CRUD | 2 filters | Multiple | ✅ |
| /writer/content/ | CRUD | 9 filters | Multiple | ✅ |
| /writer/taxonomies/ | CRUD | 5 filters | 2 actions | ✅ NEW |
| /writer/attributes/ | CRUD | 5 filters | - | ✅ NEW |
| /planner/ideas/ | CRUD | 4 filters | Multiple | ✅ |
---
## 🔍 Verification Tests
### Database Tests
```bash
✅ SELECT COUNT(*) FROM igny8_content_taxonomy_terms;
✅ SELECT COUNT(*) FROM igny8_content_attributes;
✅ SELECT COUNT(*) FROM igny8_content_taxonomy_relations;
\d igny8_content (verify new columns exist)
```
### Admin Tests
```bash
✅ Access /admin/writer/tasks/ - loads without errors
✅ Access /admin/writer/content/ - shows new filters
✅ Access /admin/writer/contenttaxonomy/ - NEW admin works
✅ Access /admin/writer/contentattribute/ - NEW admin works
✅ Access /admin/planner/contentideas/ - updated fields visible
```
### API Tests
```bash
✅ GET /api/v1/writer/tasks/ - returns data
✅ GET /api/v1/writer/content/?entity_type=post - filters work
✅ GET /api/v1/writer/taxonomies/ - NEW endpoint accessible
✅ GET /api/v1/writer/attributes/ - NEW endpoint accessible
✅ GET /api/v1/planner/ideas/?site_entity_type=post - filters work
```
---
## 📝 Updated Files Summary
### Models
-`igny8_core/business/content/models.py` (3 new models, enhanced Content)
### Admin
-`igny8_core/modules/writer/admin.py` (4 admin classes updated/added)
-`igny8_core/modules/planner/admin.py` (1 admin class updated)
### Views
-`igny8_core/modules/writer/views.py` (4 ViewSets updated/added)
-`igny8_core/modules/planner/views.py` (1 ViewSet updated)
### Serializers
-`igny8_core/modules/writer/serializers.py` (3 new serializers added)
### URLs
-`igny8_core/modules/writer/urls.py` (2 new routes added)
### Migrations
- ✅ 5 new migration files created and applied
---
## 🎯 What's Now Available
### For Developers
1. ✅ Unified content entity system (entity_type + content_format)
2. ✅ Real taxonomy relationships (not JSON)
3. ✅ Enhanced attribute system with WP sync
4. ✅ Direct cluster relationships
5. ✅ Full CRUD APIs for all new models
6. ✅ Comprehensive admin interfaces
### For WordPress Integration
1. ✅ ContentTaxonomy model ready for WP terms
2. ✅ ContentAttribute model ready for WooCommerce attributes
3. ✅ Content model has all WP sync fields
4. ✅ API endpoints ready for import/sync
5. ✅ Semantic cluster mapping ready
### For Frontend
1. ✅ New filter options for content (entity_type, content_format, cluster_role)
2. ✅ Taxonomy management endpoints
3. ✅ Attribute management endpoints
4. ✅ WordPress sync status tracking
5. ✅ Cluster mapping capabilities
---
## 📚 Documentation Created
1.`/data/app/igny8/backend/MIGRATION_SUMMARY.md`
- Complete database migration details
- Phase 1, 2, 3 breakdown
- Rollback instructions
2.`/data/app/igny8/backend/NEW_ARCHITECTURE_GUIDE.md`
- Quick reference guide
- Usage examples
- Query patterns
- WordPress sync workflows
3.`/data/app/igny8/backend/ADMIN_VIEWS_UPDATE_SUMMARY.md`
- Admin interface changes
- API endpoint details
- Filter documentation
- Testing checklist
4.`/data/app/igny8/backend/COMPLETE_UPDATE_CHECKLIST.md` (this file)
- Comprehensive verification
- All changes documented
- Status tracking
---
## ✅ Final Status
### All Tasks Complete
| Task | Status |
|------|--------|
| Database migrations | ✅ COMPLETE |
| Model updates | ✅ COMPLETE |
| Admin interfaces | ✅ COMPLETE |
| API views | ✅ COMPLETE |
| Serializers | ✅ COMPLETE |
| URL routes | ✅ COMPLETE |
| Filters updated | ✅ COMPLETE |
| Forms updated | ✅ COMPLETE |
| Backend restart | ✅ SUCCESS |
| Documentation | ✅ COMPLETE |
### Zero Issues
- ✅ No migration errors
- ✅ No linter errors
- ✅ No admin errors
- ✅ No API errors
- ✅ No startup errors
### Production Ready
- ✅ Backward compatible
- ✅ Non-breaking changes
- ✅ Deprecated fields preserved
- ✅ All tests passing
- ✅ Documentation complete
---
## 🚀 Next Steps (When Ready)
### Phase 4: WordPress Integration Implementation
1. Backend service methods for WP import
2. Frontend "Content Types" tab in Site Settings
3. AI semantic mapping endpoint
4. Sync status tracking UI
5. Bulk import workflows
### Phase 5: Blueprint Cleanup (Optional)
1. Migrate remaining blueprint data
2. Drop deprecated blueprint tables
3. Remove deprecated fields from models
4. Final cleanup migration
---
**✅ ALL MIGRATIONS RUN**
**✅ ALL TABLES UPDATED**
**✅ ALL FORMS UPDATED**
**✅ ALL FILTERS UPDATED**
**✅ ALL ADMIN INTERFACES UPDATED**
**✅ ALL API ENDPOINTS UPDATED**
**Status: PRODUCTION READY** 🎉

View File

@@ -1,50 +0,0 @@
# WordPress Integration Fixes
## Issues Fixed
### 1. Validation Error (400 Bad Request)
**Problem**: When creating WordPress integration with API key-only authentication, the backend was rejecting the request because `username` and `app_password` were empty strings.
**Root Cause**: The serializer was validating credentials but didn't account for API key-only authentication where username/password are optional.
**Fix**: Added custom validation in `SiteIntegrationSerializer` to allow API key-only authentication for WordPress platform:
- If `api_key` is provided in `credentials_json`, username and app_password are optional
- If `api_key` is not provided, username and app_password are required (traditional auth)
**File**: `backend/igny8_core/modules/integration/views.py`
### 2. Status Indicator Not Showing Connected
**Problem**: Status indicator showed "Not configured" even when integration existed and was active.
**Root Cause**: Status check only looked for `site?.wp_api_key` but didn't check for API key in integration's `credentials_json`.
**Fix**: Updated status check to look for API key in both:
- Site's `wp_api_key` field
- Integration's `credentials_json.api_key` field
**File**: `frontend/src/pages/Sites/Settings.tsx`
### 3. Integration Creation Error Handling
**Problem**: When toggling integration enabled without API key, no clear error was shown.
**Fix**: Added error handling to show clear message when trying to enable integration without API key.
**File**: `frontend/src/components/sites/WordPressIntegrationForm.tsx`
## Content Sync Status
Content sync will work as long as:
1. ✅ Integration exists in database
2. ✅ Integration `is_active = True`
3. ✅ Integration `sync_enabled = True`
The sync service checks these conditions before performing sync operations.
## Testing Checklist
- [x] Create WordPress integration with API key only (no username/password)
- [x] Status indicator shows "Configured" when integration exists and is active
- [x] Status indicator shows "Connected" after successful connection test
- [x] Content sync works when integration is active and sync_enabled
- [x] Error messages are clear when API key is missing

View File

@@ -1,329 +0,0 @@
# IGNY8 Content Architecture Migration Summary
**Date**: November 21, 2025
**Status**: ✅ **COMPLETED SUCCESSFULLY**
---
## Overview
Complete migration from fragmented content/taxonomy structure to unified WordPress-ready architecture.
---
## Phase 1: New Models & Fields ✅
### New Models Created
#### 1. `ContentTaxonomy` (`igny8_content_taxonomy_terms`)
Unified taxonomy model for categories, tags, and product attributes.
**Key Fields:**
- `name`, `slug`, `taxonomy_type` (category, tag, product_cat, product_tag, product_attr, service_cat)
- `external_id`, `external_taxonomy` (WordPress sync fields)
- `sync_status` (native, imported, synced)
- `count` (post count from WP)
- `parent` (hierarchical taxonomies)
- M2M to `Clusters` (semantic mapping)
**Indexes:** 14 total including composite indexes for WP sync lookups
#### 2. `ContentAttribute` (`igny8_content_attributes`)
Renamed from `ContentAttributeMap` with enhanced WP sync support.
**Key Fields:**
- `attribute_type` (product_spec, service_modifier, semantic_facet)
- `name`, `value`
- `external_id`, `external_attribute_name` (WooCommerce sync)
- FK to `Content`, `Cluster`
**Indexes:** 7 total for efficient attribute lookups
#### 3. `ContentTaxonomyRelation` (`igny8_content_taxonomy_relations`)
Through model for Content ↔ ContentTaxonomy M2M.
**Note:** Simplified to avoid tenant_id constraint issues.
### Content Model Enhancements
**New Fields:**
- `content_format` (article, listicle, guide, comparison, review, roundup)
- `cluster_role` (hub, supporting, attribute)
- `external_type` (WP post type: post, page, product, service)
- `cluster` FK (direct cluster relationship)
- `taxonomies` M2M (replaces JSON categories/tags)
**Updated Fields:**
- `entity_type` now uses: post, page, product, service, taxonomy_term (legacy values preserved)
---
## Phase 2: Data Migration ✅
### Migrations Performed
1. **Content Entity Types** (`migrate_content_entity_types`)
- Converted legacy `blog_post``post` + `content_format='article'`
- Converted `article``post` + `content_format='article'`
- Converted `taxonomy``taxonomy_term`
2. **Task Entity Types** (`migrate_task_entity_types`)
- Migrated `Tasks.entity_type``Content.entity_type` + `content_format`
- Migrated `Tasks.cluster_role``Content.cluster_role`
- Migrated `Tasks.cluster_id``Content.cluster_id`
3. **Categories & Tags** (`migrate_content_categories_tags_to_taxonomy`)
- Converted `Content.categories` JSON → `ContentTaxonomy` records (type: category)
- Converted `Content.tags` JSON → `ContentTaxonomy` records (type: tag)
- Created M2M relationships via `ContentTaxonomyRelation`
4. **Blueprint Taxonomies** (`migrate_blueprint_taxonomies`)
- Migrated `SiteBlueprintTaxonomy``ContentTaxonomy`
- Preserved `external_reference` as `external_id`
- Preserved cluster mappings
---
## Phase 3: Deprecation & Cleanup ✅
### Deprecated Fields (Marked, Not Removed)
**In `Tasks` model:**
- `content` → Use `Content.html_content`
- `word_count` → Use `Content.word_count`
- `meta_title` → Use `Content.meta_title`
- `meta_description` → Use `Content.meta_description`
- `assigned_post_id` → Use `Content.external_id`
- `post_url` → Use `Content.external_url`
- `entity_type` → Use `Content.entity_type`
- `cluster_role` → Use `Content.cluster_role`
- `content_structure` → Merged into `Content.content_format`
- `content_type` → Merged into `Content.entity_type + content_format`
**In `Content` model:**
- `categories` → Use `Content.taxonomies` M2M
- `tags` → Use `Content.taxonomies` M2M
**Reason for Preservation:** Backward compatibility during transition period. Can be removed in future migration after ensuring no dependencies.
### Blueprint Tables Status
Tables **preserved** (1 active blueprint found):
- `igny8_site_blueprints`
- `igny8_page_blueprints`
- `igny8_site_blueprint_clusters`
- `igny8_site_blueprint_taxonomies`
**Note:** These can be dropped in Phase 4 if/when site builder is fully replaced by WP import flow.
---
## Applied Migrations
```
writer
[X] 0001_initial
[X] 0002_phase1_add_unified_taxonomy_and_attributes
[X] 0003_phase1b_fix_taxonomy_relation
[X] 0004_phase2_migrate_data_to_unified_structure
[X] 0005_phase3_mark_deprecated_fields
```
---
## Serializers Updated ✅
### New Serializers Created
1. `ContentTaxonomySerializer`
- Includes parent_name, cluster_names, content_count
- Full CRUD support
2. `ContentAttributeSerializer`
- Includes content_title, cluster_name
- WP sync field support
3. `ContentTaxonomyRelationSerializer`
- M2M relationship details
- Read-only access to relation metadata
### Existing Serializers Updated
- `TasksSerializer`: Updated to use `ContentAttribute` (backward compatible alias)
- `ContentSerializer`: Updated attribute mappings to use new model
---
## Database Verification ✅
### New Tables Confirmed
```sql
igny8_content_taxonomy_terms (16 columns, 23 indexes)
igny8_content_attributes (16 columns, 15 indexes)
igny8_content_taxonomy_relations (4 columns, 3 indexes)
igny8_content_taxonomy_terms_clusters (M2M table)
```
### New Content Fields Confirmed
```sql
cluster_id (bigint)
cluster_role (varchar)
content_format (varchar)
external_type (varchar)
```
---
## Backend Status ✅
**Container:** `igny8_backend`
**Status:** Running and healthy
**Workers:** 4 gunicorn workers booted successfully
**No errors detected in startup logs**
---
## WordPress Integration Readiness
### Ready for WP Sync
1. **Content Type Detection**
- `Content.entity_type` = WP post_type (post, page, product)
- `Content.external_type` = source post_type name
- `Content.external_id` = WP post ID
- `Content.external_url` = WP post permalink
2. **Taxonomy Sync**
- `ContentTaxonomy.external_id` = WP term ID
- `ContentTaxonomy.external_taxonomy` = WP taxonomy name (category, post_tag, product_cat, pa_*)
- `ContentTaxonomy.taxonomy_type` = mapped type
- `ContentTaxonomy.sync_status` = import tracking
3. **Product Attributes**
- `ContentAttribute.external_id` = WooCommerce attribute term ID
- `ContentAttribute.external_attribute_name` = WP attribute slug (pa_color, pa_size)
- `ContentAttribute.attribute_type` = product_spec
4. **Semantic Mapping**
- `ContentTaxonomy.clusters` M2M = AI cluster assignments
- `Content.cluster` FK = primary semantic cluster
- `Content.cluster_role` = hub/supporting/attribute
---
## Next Steps for WP Integration
### Immediate (Already Prepared)
1. ✅ Plugin `/site-metadata/` endpoint exists
2. ✅ Database structure ready
3. ✅ Models & serializers ready
### Phase 4 (Next Session)
1. **Backend Service Layer**
- `IntegrationService.fetch_content_structure(integration_id)`
- `IntegrationService.import_taxonomies(integration_id, taxonomy_type, limit)`
- `IntegrationService.import_content_titles(integration_id, post_type, limit)`
- `IntegrationService.fetch_full_content(content_id)` (on-demand)
2. **Backend Endpoints**
- `POST /api/v1/integration/integrations/{id}/fetch-structure/`
- `POST /api/v1/integration/integrations/{id}/import-taxonomies/`
- `POST /api/v1/integration/integrations/{id}/import-content/`
- `GET /api/v1/integration/content-taxonomies/` (ViewSet)
- `GET /api/v1/integration/content-attributes/` (ViewSet)
3. **Frontend UI**
- New tab: "Content Types" in Site Settings
- Display detected post types & taxonomies
- Enable/disable toggles
- Fetch limit inputs
- Sync status indicators
4. **AI Semantic Mapping**
- Endpoint: `POST /api/v1/integration/integrations/{id}/generate-semantic-map/`
- Input: Content titles + taxonomy terms
- Output: Cluster recommendations + attribute suggestions
- Auto-create clusters and map taxonomies
---
## Rollback Plan (If Needed)
### Critical Data Preserved
- ✅ Original JSON categories/tags still in Content table
- ✅ Original blueprint taxonomies table intact
- ✅ Legacy entity_type values preserved in choices
- ✅ All task fields still functional
### To Rollback
```bash
# Rollback to before migration
python manage.py migrate writer 0001
# Remove new tables manually if needed
DROP TABLE igny8_content_taxonomy_relations CASCADE;
DROP TABLE igny8_content_taxonomy_terms_clusters CASCADE;
DROP TABLE igny8_content_taxonomy_terms CASCADE;
DROP TABLE igny8_content_attributes CASCADE;
```
---
## Performance Notes
- All new tables have appropriate indexes
- Composite indexes for WP sync lookups (external_id + external_taxonomy)
- Indexes on taxonomy_type, sync_status for filtering
- M2M through table is minimal (no tenant_id to avoid constraint issues)
---
## Testing Recommendations
### Manual Tests
1. ✅ Backend restart successful
2. ✅ Database tables created correctly
3. ✅ Migrations applied without errors
4. 🔲 Create new ContentTaxonomy via API
5. 🔲 Assign taxonomies to content via M2M
6. 🔲 Create ContentAttribute for product
7. 🔲 Query taxonomies by external_id
8. 🔲 Test cluster → taxonomy mapping
### Integration Tests (Next Phase)
1. WP `/site-metadata/` → Backend storage
2. WP category import → ContentTaxonomy creation
3. WP product attribute import → ContentAttribute creation
4. Content → Taxonomy M2M assignment
5. AI semantic mapping with imported data
---
## Summary
**All 3 phases completed successfully:**
**Phase 1**: New models & fields added
**Phase 2**: Existing data migrated
**Phase 3**: Deprecated fields marked
**Current Status**: Production-ready, backward compatible, WordPress integration prepared.
**Zero downtime**: All changes non-breaking, existing functionality preserved.
---
**Migration Completed By**: AI Assistant
**Total Migrations**: 5
**Total New Tables**: 4
**Total New Fields in Content**: 4
**Deprecated Fields**: 12 (marked, not removed)

View File

@@ -1,433 +0,0 @@
# IGNY8 Unified Content Architecture - Quick Reference
## ✅ What Changed
### Old Way ❌
```python
# Scattered entity types
task.entity_type = 'blog_post'
task.content_type = 'article'
task.content_structure = 'pillar_page'
# JSON arrays for taxonomies
content.categories = ['SEO', 'WordPress']
content.tags = ['tutorial', 'guide']
# Fragmented attributes
ContentAttributeMap(name='Color', value='Blue')
```
### New Way ✅
```python
# Single unified entity type
content.entity_type = 'post' # What it is
content.content_format = 'article' # How it's structured
content.cluster_role = 'hub' # Semantic role
# Real M2M relationships
content.taxonomies.add(seo_category)
content.taxonomies.add(tutorial_tag)
# Enhanced attributes with WP sync
ContentAttribute(
content=content,
attribute_type='product_spec',
name='Color',
value='Blue',
external_id=101, # WP term ID
external_attribute_name='pa_color'
)
```
---
## 📚 Core Models
### 1. Content (Enhanced)
```python
from igny8_core.business.content.models import Content
# Create content
content = Content.objects.create(
title="Best SEO Tools 2025",
entity_type='post', # post, page, product, service, taxonomy_term
content_format='listicle', # article, listicle, guide, comparison, review
cluster_role='hub', # hub, supporting, attribute
html_content="<h1>Best SEO Tools...</h1>",
# WordPress sync
external_id=427, # WP post ID
external_url="https://site.com/seo-tools/",
external_type='post', # WP post_type
source='wordpress',
sync_status='imported',
# SEO
meta_title="15 Best SEO Tools...",
primary_keyword="seo tools",
# Relationships
cluster=seo_cluster,
site=site,
sector=sector,
)
# Add taxonomies
content.taxonomies.add(seo_category, tools_tag)
```
### 2. ContentTaxonomy (New)
```python
from igny8_core.business.content.models import ContentTaxonomy
# WordPress category
category = ContentTaxonomy.objects.create(
name="SEO",
slug="seo",
taxonomy_type='category', # category, tag, product_cat, product_tag, product_attr
description="All about SEO",
# WordPress sync
external_id=12, # WP term ID
external_taxonomy='category', # WP taxonomy name
sync_status='imported',
count=45, # Post count from WP
site=site,
sector=sector,
)
# Map to semantic clusters
category.clusters.add(seo_cluster, content_marketing_cluster)
# Hierarchical taxonomy
subcategory = ContentTaxonomy.objects.create(
name="Technical SEO",
slug="technical-seo",
taxonomy_type='category',
parent=category, # Parent category
site=site,
sector=sector,
)
```
### 3. ContentAttribute (Enhanced)
```python
from igny8_core.business.content.models import ContentAttribute
# WooCommerce product attribute
attribute = ContentAttribute.objects.create(
content=product_content,
attribute_type='product_spec', # product_spec, service_modifier, semantic_facet
name='Color',
value='Blue',
# WooCommerce sync
external_id=101, # WP attribute term ID
external_attribute_name='pa_color', # WP attribute slug
source='wordpress',
site=site,
sector=sector,
)
# Semantic cluster attribute
semantic_attr = ContentAttribute.objects.create(
cluster=enterprise_seo_cluster,
attribute_type='semantic_facet',
name='Target Audience',
value='Enterprise',
source='manual',
site=site,
sector=sector,
)
```
---
## 🔄 WordPress Sync Workflows
### Scenario 1: Import WP Categories
```python
from igny8_core.business.content.models import ContentTaxonomy
# Fetch from WP /wp-json/wp/v2/categories
wp_categories = [
{'id': 12, 'name': 'SEO', 'slug': 'seo', 'count': 45},
{'id': 15, 'name': 'WordPress', 'slug': 'wordpress', 'count': 32},
]
for wp_cat in wp_categories:
taxonomy, created = ContentTaxonomy.objects.update_or_create(
site=site,
external_id=wp_cat['id'],
external_taxonomy='category',
defaults={
'name': wp_cat['name'],
'slug': wp_cat['slug'],
'taxonomy_type': 'category',
'count': wp_cat['count'],
'sync_status': 'imported',
'sector': site.sectors.first(),
}
)
```
### Scenario 2: Import WP Posts (Titles Only)
```python
from igny8_core.business.content.models import Content, ContentTaxonomy
# Fetch from WP /wp-json/wp/v2/posts
wp_posts = [
{
'id': 427,
'title': {'rendered': 'Best SEO Tools 2025'},
'link': 'https://site.com/seo-tools/',
'type': 'post',
'categories': [12, 15],
'tags': [45, 67],
}
]
for wp_post in wp_posts:
# Create content (title only, no html_content yet)
content, created = Content.objects.update_or_create(
site=site,
external_id=wp_post['id'],
defaults={
'title': wp_post['title']['rendered'],
'entity_type': 'post',
'external_url': wp_post['link'],
'external_type': wp_post['type'],
'source': 'wordpress',
'sync_status': 'imported',
'sector': site.sectors.first(),
}
)
# Map categories
for cat_id in wp_post['categories']:
try:
taxonomy = ContentTaxonomy.objects.get(
site=site,
external_id=cat_id,
taxonomy_type='category'
)
content.taxonomies.add(taxonomy)
except ContentTaxonomy.DoesNotExist:
pass
# Map tags
for tag_id in wp_post['tags']:
try:
taxonomy = ContentTaxonomy.objects.get(
site=site,
external_id=tag_id,
taxonomy_type='tag'
)
content.taxonomies.add(taxonomy)
except ContentTaxonomy.DoesNotExist:
pass
```
### Scenario 3: Fetch Full Content On-Demand
```python
def fetch_full_content(content_id):
"""Fetch full HTML content from WP when needed for AI analysis."""
content = Content.objects.get(id=content_id)
if content.source == 'wordpress' and content.external_id:
# Fetch from WP /wp-json/wp/v2/posts/{external_id}
wp_response = requests.get(
f"{content.site.url}/wp-json/wp/v2/posts/{content.external_id}"
)
wp_data = wp_response.json()
# Update content
content.html_content = wp_data['content']['rendered']
content.word_count = len(wp_data['content']['rendered'].split())
content.meta_title = wp_data.get('yoast_head_json', {}).get('title', '')
content.meta_description = wp_data.get('yoast_head_json', {}).get('description', '')
content.save()
return content
```
### Scenario 4: Import WooCommerce Product Attributes
```python
from igny8_core.business.content.models import Content, ContentAttribute
# Fetch from WP /wp-json/wc/v3/products/{id}
wp_product = {
'id': 88,
'name': 'Blue Widget',
'type': 'simple',
'attributes': [
{'id': 1, 'name': 'Color', 'slug': 'pa_color', 'option': 'Blue'},
{'id': 2, 'name': 'Size', 'slug': 'pa_size', 'option': 'Large'},
]
}
# Create product content
product = Content.objects.create(
site=site,
title=wp_product['name'],
entity_type='product',
external_id=wp_product['id'],
external_type='product',
source='wordpress',
sync_status='imported',
sector=site.sectors.first(),
)
# Import attributes
for attr in wp_product['attributes']:
ContentAttribute.objects.create(
content=product,
attribute_type='product_spec',
name=attr['name'],
value=attr['option'],
external_attribute_name=attr['slug'],
source='wordpress',
site=site,
sector=site.sectors.first(),
)
```
---
## 🔍 Query Examples
### Find Content by Entity Type
```python
# All blog posts
posts = Content.objects.filter(entity_type='post')
# All listicles
listicles = Content.objects.filter(entity_type='post', content_format='listicle')
# All hub pages
hubs = Content.objects.filter(cluster_role='hub')
# All WP-synced products
products = Content.objects.filter(
entity_type='product',
source='wordpress',
sync_status='imported'
)
```
### Find Taxonomies
```python
# All categories with WP sync
categories = ContentTaxonomy.objects.filter(
taxonomy_type='category',
external_id__isnull=False
)
# Product attributes (color, size, etc.)
product_attrs = ContentTaxonomy.objects.filter(taxonomy_type='product_attr')
# Taxonomies mapped to a cluster
cluster_terms = ContentTaxonomy.objects.filter(clusters=seo_cluster)
# Get all content for a taxonomy
seo_content = Content.objects.filter(taxonomies=seo_category)
```
### Find Attributes
```python
# All product specs for a content
specs = ContentAttribute.objects.filter(
content=product,
attribute_type='product_spec'
)
# All attributes in a cluster
cluster_attrs = ContentAttribute.objects.filter(
cluster=enterprise_cluster,
attribute_type='semantic_facet'
)
# Find content by attribute value
blue_products = Content.objects.filter(
attributes__name='Color',
attributes__value='Blue'
)
```
---
## 📊 Relationships Diagram
```
Site
├─ Content (post, page, product, service, taxonomy_term)
│ ├─ entity_type (what it is)
│ ├─ content_format (how it's structured)
│ ├─ cluster_role (semantic role)
│ ├─ cluster FK → Clusters
│ ├─ taxonomies M2M → ContentTaxonomy
│ └─ attributes FK ← ContentAttribute
├─ ContentTaxonomy (category, tag, product_cat, product_tag, product_attr)
│ ├─ external_id (WP term ID)
│ ├─ external_taxonomy (WP taxonomy name)
│ ├─ parent FK → self (hierarchical)
│ ├─ clusters M2M → Clusters
│ └─ contents M2M ← Content
└─ Clusters
├─ contents FK ← Content
├─ taxonomy_terms M2M ← ContentTaxonomy
└─ attributes FK ← ContentAttribute
```
---
## ⚠️ Migration Notes
### Deprecated Fields (Still Available)
**Don't use these anymore:**
```python
# ❌ Old way
task.content = "..." # Use Content.html_content
task.entity_type = "..." # Use Content.entity_type
content.categories = ["SEO"] # Use content.taxonomies M2M
content.tags = ["tutorial"] # Use content.taxonomies M2M
```
**Use these instead:**
```python
# ✅ New way
content.html_content = "..."
content.entity_type = "post"
content.taxonomies.add(seo_category)
content.taxonomies.add(tutorial_tag)
```
### Backward Compatibility
Legacy values still work:
```python
# These still map correctly
content.entity_type = 'blog_post' # → internally handled as 'post'
content.entity_type = 'article' # → internally handled as 'post'
```
---
## 🚀 Next: Frontend Integration
Ready for Phase 4:
1. Site Settings → "Content Types" tab
2. Display imported taxonomies
3. Enable/disable sync per type
4. Set fetch limits
5. Trigger AI semantic mapping
---
**Questions?** Check `/data/app/igny8/backend/MIGRATION_SUMMARY.md` for full migration details.

View File

@@ -1,705 +0,0 @@
# Sites Integration Plan - Content Types Structure
**Date**: November 22, 2025
**Status**: 📋 **PLANNING**
---
## Overview
Integrate the new unified content architecture (ContentTaxonomy, ContentAttribute, entity_type, content_format) with the Sites module and SiteIntegration model to enable WordPress content type discovery, configuration, and sync.
---
## Current State Analysis
### ✅ What We Have
**1. Unified Content Architecture (COMPLETE)**
- `Content` model with `entity_type`, `content_format`, `cluster_role`
- `ContentTaxonomy` model for categories, tags, product attributes
- `ContentAttribute` model for product specs, service modifiers
- WordPress sync fields (`external_id`, `external_taxonomy`, `sync_status`)
**2. Site Model**
- Basic site information (name, domain, industry)
- `site_type` field (marketing, ecommerce, blog, portfolio, corporate)
- `hosting_type` field (igny8_sites, wordpress, shopify, multi)
- Legacy WP fields (`wp_url`, `wp_username`, `wp_api_key`)
**3. SiteIntegration Model**
- Platform-specific integrations (wordpress, shopify, custom)
- `config_json` for configuration
- `credentials_json` for API keys/tokens
- `sync_enabled` flag for two-way sync
**4. WordPress Plugin**
- `/wp-json/igny8/v1/site-metadata/` endpoint
- Returns post types, taxonomies, and counts
- API key authentication support
### ❌ What's Missing
1. **Content Type Configuration Storage**
- No place to store which post types/taxonomies are enabled
- No fetch limits per content type
- No sync preferences per taxonomy
2. **Site → Integration Connection**
- No clear link between Site.site_type and available content types
- No mapping of WP post types to IGNY8 entity types
3. **Frontend UI**
- No "Content Types" tab in Site Settings
- No interface to enable/disable content types
- No way to set fetch limits
4. **Backend Service Methods**
- No method to fetch WP structure and store in `config_json`
- No method to import taxonomies
- No method to import content titles
---
## Proposed Solution
### Phase 1: Extend SiteIntegration.config_json Structure
Store WordPress content type configuration in `SiteIntegration.config_json`:
```json
{
"url": "https://example.com",
"api_version": "v1",
"plugin_version": "1.0.0",
"content_types": {
"post_types": {
"post": {
"label": "Posts",
"count": 123,
"enabled": true,
"fetch_limit": 100,
"entity_type": "post",
"content_format": "article",
"last_synced": "2025-11-22T10:00:00Z"
},
"page": {
"label": "Pages",
"count": 12,
"enabled": true,
"fetch_limit": 50,
"entity_type": "page",
"content_format": null,
"last_synced": null
},
"product": {
"label": "Products",
"count": 456,
"enabled": true,
"fetch_limit": 200,
"entity_type": "product",
"content_format": null,
"last_synced": null
}
},
"taxonomies": {
"category": {
"label": "Categories",
"count": 25,
"enabled": true,
"fetch_limit": 100,
"taxonomy_type": "category",
"last_synced": "2025-11-22T10:00:00Z"
},
"post_tag": {
"label": "Tags",
"count": 102,
"enabled": true,
"fetch_limit": 200,
"taxonomy_type": "tag",
"last_synced": null
},
"product_cat": {
"label": "Product Categories",
"count": 15,
"enabled": true,
"fetch_limit": 50,
"taxonomy_type": "product_cat",
"last_synced": null
},
"pa_color": {
"label": "Color",
"count": 10,
"enabled": true,
"fetch_limit": 50,
"taxonomy_type": "product_attr",
"attribute_name": "Color",
"last_synced": null
}
}
},
"plugin_connection_enabled": true,
"two_way_sync_enabled": true,
"last_structure_fetch": "2025-11-22T10:00:00Z"
}
```
---
### Phase 2: Backend Service Methods
#### 1. **IntegrationService.fetch_content_structure()**
```python
def fetch_content_structure(self, integration_id: int) -> Dict[str, Any]:
"""
Fetch content structure from WordPress plugin and store in config_json.
Steps:
1. GET /wp-json/igny8/v1/site-metadata/
2. Parse response
3. Update integration.config_json['content_types']
4. Return structure
"""
integration = SiteIntegration.objects.get(id=integration_id)
# Call WordPress plugin
wp_url = integration.config_json.get('url')
api_key = integration.credentials_json.get('api_key')
response = requests.get(
f"{wp_url}/wp-json/igny8/v1/site-metadata/",
headers={'X-IGNY8-API-KEY': api_key}
)
if response.status_code == 200:
data = response.json()['data']
# Transform to our structure
content_types = {
'post_types': {},
'taxonomies': {}
}
# Map post types
for wp_type, info in data['post_types'].items():
content_types['post_types'][wp_type] = {
'label': info['label'],
'count': info['count'],
'enabled': False, # Default disabled
'fetch_limit': 100, # Default limit
'entity_type': self._map_wp_type_to_entity(wp_type),
'content_format': None,
'last_synced': None
}
# Map taxonomies
for wp_tax, info in data['taxonomies'].items():
content_types['taxonomies'][wp_tax] = {
'label': info['label'],
'count': info['count'],
'enabled': False, # Default disabled
'fetch_limit': 100, # Default limit
'taxonomy_type': self._map_wp_tax_to_type(wp_tax),
'last_synced': None
}
# Update config
if 'content_types' not in integration.config_json:
integration.config_json['content_types'] = {}
integration.config_json['content_types'] = content_types
integration.config_json['last_structure_fetch'] = timezone.now().isoformat()
integration.save()
return content_types
else:
raise Exception(f"Failed to fetch structure: {response.status_code}")
def _map_wp_type_to_entity(self, wp_type: str) -> str:
"""Map WordPress post type to IGNY8 entity_type"""
mapping = {
'post': 'post',
'page': 'page',
'product': 'product',
'service': 'service',
}
return mapping.get(wp_type, 'post')
def _map_wp_tax_to_type(self, wp_tax: str) -> str:
"""Map WordPress taxonomy to ContentTaxonomy type"""
mapping = {
'category': 'category',
'post_tag': 'tag',
'product_cat': 'product_cat',
'product_tag': 'product_tag',
}
# Product attributes start with pa_
if wp_tax.startswith('pa_'):
return 'product_attr'
return mapping.get(wp_tax, 'category')
```
#### 2. **IntegrationService.import_taxonomies()**
```python
def import_taxonomies(
self,
integration_id: int,
taxonomy_type: str = None,
limit: int = None
) -> int:
"""
Import taxonomy terms from WordPress to ContentTaxonomy.
Args:
integration_id: SiteIntegration ID
taxonomy_type: Specific taxonomy to import (e.g., 'category', 'post_tag')
limit: Max terms to import per taxonomy
Returns:
Number of terms imported
"""
integration = SiteIntegration.objects.get(id=integration_id)
site = integration.site
# Get enabled taxonomies from config
content_types = integration.config_json.get('content_types', {})
taxonomies = content_types.get('taxonomies', {})
imported_count = 0
for wp_tax, config in taxonomies.items():
# Skip if not enabled or not requested
if not config.get('enabled'):
continue
if taxonomy_type and wp_tax != taxonomy_type:
continue
# Fetch from WordPress
fetch_limit = limit or config.get('fetch_limit', 100)
wp_url = integration.config_json.get('url')
api_key = integration.credentials_json.get('api_key')
# Map taxonomy endpoint
endpoint = self._get_wp_taxonomy_endpoint(wp_tax)
response = requests.get(
f"{wp_url}/wp-json/wp/v2/{endpoint}?per_page={fetch_limit}",
headers={'X-IGNY8-API-KEY': api_key}
)
if response.status_code == 200:
terms = response.json()
for term in terms:
# Create or update ContentTaxonomy
taxonomy, created = ContentTaxonomy.objects.update_or_create(
site=site,
external_id=term['id'],
external_taxonomy=wp_tax,
defaults={
'name': term['name'],
'slug': term['slug'],
'taxonomy_type': config['taxonomy_type'],
'description': term.get('description', ''),
'count': term.get('count', 0),
'sync_status': 'imported',
'account': site.account,
'sector': site.sectors.first(), # Default to first sector
}
)
if created:
imported_count += 1
# Update last_synced
config['last_synced'] = timezone.now().isoformat()
integration.save()
return imported_count
def _get_wp_taxonomy_endpoint(self, wp_tax: str) -> str:
"""Get WordPress REST endpoint for taxonomy"""
mapping = {
'category': 'categories',
'post_tag': 'tags',
'product_cat': 'products/categories',
'product_tag': 'products/tags',
}
# Product attributes
if wp_tax.startswith('pa_'):
attr_id = wp_tax.replace('pa_', '')
return f'products/attributes/{attr_id}/terms'
return mapping.get(wp_tax, wp_tax)
```
#### 3. **IntegrationService.import_content_titles()**
```python
def import_content_titles(
self,
integration_id: int,
post_type: str = None,
limit: int = None
) -> int:
"""
Import content titles (not full content) from WordPress.
Args:
integration_id: SiteIntegration ID
post_type: Specific post type to import (e.g., 'post', 'product')
limit: Max items to import per type
Returns:
Number of content items imported
"""
integration = SiteIntegration.objects.get(id=integration_id)
site = integration.site
# Get enabled post types from config
content_types = integration.config_json.get('content_types', {})
post_types = content_types.get('post_types', {})
imported_count = 0
for wp_type, config in post_types.items():
# Skip if not enabled or not requested
if not config.get('enabled'):
continue
if post_type and wp_type != post_type:
continue
# Fetch from WordPress
fetch_limit = limit or config.get('fetch_limit', 100)
wp_url = integration.config_json.get('url')
api_key = integration.credentials_json.get('api_key')
# Determine endpoint
endpoint = 'products' if wp_type == 'product' else wp_type + 's'
response = requests.get(
f"{wp_url}/wp-json/wp/v2/{endpoint}?per_page={fetch_limit}",
headers={'X-IGNY8-API-KEY': api_key}
)
if response.status_code == 200:
items = response.json()
for item in items:
# Create or update Content (title only, no html_content yet)
content, created = Content.objects.update_or_create(
site=site,
external_id=item['id'],
external_type=wp_type,
defaults={
'title': item['title']['rendered'] if isinstance(item['title'], dict) else item['title'],
'entity_type': config['entity_type'],
'content_format': config.get('content_format'),
'external_url': item.get('link', ''),
'source': 'wordpress',
'sync_status': 'imported',
'account': site.account,
'sector': site.sectors.first(),
}
)
# Map taxonomies
if 'categories' in item:
for cat_id in item['categories']:
try:
taxonomy = ContentTaxonomy.objects.get(
site=site,
external_id=cat_id,
taxonomy_type='category'
)
content.taxonomies.add(taxonomy)
except ContentTaxonomy.DoesNotExist:
pass
if 'tags' in item:
for tag_id in item['tags']:
try:
taxonomy = ContentTaxonomy.objects.get(
site=site,
external_id=tag_id,
taxonomy_type='tag'
)
content.taxonomies.add(taxonomy)
except ContentTaxonomy.DoesNotExist:
pass
if created:
imported_count += 1
# Update last_synced
config['last_synced'] = timezone.now().isoformat()
integration.save()
return imported_count
```
---
### Phase 3: Backend API Endpoints
Add new actions to `IntegrationViewSet`:
```python
@action(detail=True, methods=['post'], url_path='fetch-structure')
def fetch_structure(self, request, pk=None):
"""
POST /api/v1/integration/integrations/{id}/fetch-structure/
Fetch content type structure from WordPress and store in config.
"""
integration = self.get_object()
service = IntegrationService()
try:
structure = service.fetch_content_structure(integration.id)
return success_response(
data=structure,
message="Content structure fetched successfully",
request=request
)
except Exception as e:
return error_response(
error=str(e),
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
@action(detail=True, methods=['post'], url_path='import-taxonomies')
def import_taxonomies(self, request, pk=None):
"""
POST /api/v1/integration/integrations/{id}/import-taxonomies/
{
"taxonomy_type": "category", // optional
"limit": 100 // optional
}
Import taxonomy terms from WordPress.
"""
integration = self.get_object()
service = IntegrationService()
taxonomy_type = request.data.get('taxonomy_type')
limit = request.data.get('limit')
try:
count = service.import_taxonomies(integration.id, taxonomy_type, limit)
return success_response(
data={'imported_count': count},
message=f"Imported {count} taxonomy terms",
request=request
)
except Exception as e:
return error_response(
error=str(e),
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
@action(detail=True, methods=['post'], url_path='import-content')
def import_content(self, request, pk=None):
"""
POST /api/v1/integration/integrations/{id}/import-content/
{
"post_type": "post", // optional
"limit": 100 // optional
}
Import content titles from WordPress.
"""
integration = self.get_object()
service = IntegrationService()
post_type = request.data.get('post_type')
limit = request.data.get('limit')
try:
count = service.import_content_titles(integration.id, post_type, limit)
return success_response(
data={'imported_count': count},
message=f"Imported {count} content items",
request=request
)
except Exception as e:
return error_response(
error=str(e),
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
@action(detail=True, methods=['patch'], url_path='update-content-types')
def update_content_types(self, request, pk=None):
"""
PATCH /api/v1/integration/integrations/{id}/update-content-types/
{
"post_types": {
"post": {"enabled": true, "fetch_limit": 200}
},
"taxonomies": {
"category": {"enabled": true, "fetch_limit": 150}
}
}
Update content type configuration.
"""
integration = self.get_object()
post_types = request.data.get('post_types', {})
taxonomies = request.data.get('taxonomies', {})
# Update config
if 'content_types' not in integration.config_json:
integration.config_json['content_types'] = {'post_types': {}, 'taxonomies': {}}
for wp_type, updates in post_types.items():
if wp_type in integration.config_json['content_types']['post_types']:
integration.config_json['content_types']['post_types'][wp_type].update(updates)
for wp_tax, updates in taxonomies.items():
if wp_tax in integration.config_json['content_types']['taxonomies']:
integration.config_json['content_types']['taxonomies'][wp_tax].update(updates)
integration.save()
return success_response(
data=integration.config_json['content_types'],
message="Content types configuration updated",
request=request
)
```
---
### Phase 4: Frontend UI - "Content Types" Tab
**Location:** Site Settings → Content Types
**Features:**
1. Display fetched content types from `config_json`
2. Enable/disable toggles per type
3. Fetch limit inputs
4. Last synced timestamps
5. Sync buttons (Fetch Structure, Import Taxonomies, Import Content)
**API Calls:**
```javascript
// Fetch structure
POST /api/v1/integration/integrations/{id}/fetch-structure/
// Update configuration
PATCH /api/v1/integration/integrations/{id}/update-content-types/
{
"post_types": {
"post": {"enabled": true, "fetch_limit": 200}
}
}
// Import taxonomies
POST /api/v1/integration/integrations/{id}/import-taxonomies/
// Import content
POST /api/v1/integration/integrations/{id}/import-content/
```
---
## Implementation Steps
### Step 1: Backend Service Methods ✅ READY TO IMPLEMENT
- [ ] Add `fetch_content_structure()` to IntegrationService
- [ ] Add `import_taxonomies()` to IntegrationService
- [ ] Add `import_content_titles()` to IntegrationService
- [ ] Add helper methods for WP type mapping
### Step 2: Backend API Endpoints ✅ READY TO IMPLEMENT
- [ ] Add `fetch_structure` action to IntegrationViewSet
- [ ] Add `import_taxonomies` action to IntegrationViewSet
- [ ] Add `import_content` action to IntegrationViewSet
- [ ] Add `update_content_types` action to IntegrationViewSet
### Step 3: Frontend UI ⏳ PENDING
- [ ] Create "Content Types" tab component
- [ ] Add post types list with toggles
- [ ] Add taxonomies list with toggles
- [ ] Add fetch limit inputs
- [ ] Add sync buttons
- [ ] Add last synced timestamps
### Step 4: Testing ⏳ PENDING
- [ ] Test structure fetch from WP plugin
- [ ] Test taxonomy import
- [ ] Test content title import
- [ ] Test configuration updates
- [ ] Test UI interactions
---
## Migration Status
### ✅ Database Ready
- All tables exist
- All fields exist
- All migrations applied
### ✅ Models Ready
- ContentTaxonomy model complete
- ContentAttribute model complete
- Content model enhanced
- SiteIntegration model ready
### ✅ Admin Ready
- All admin interfaces updated
- All filters configured
### ⏳ Services Pending
- IntegrationService methods need implementation
### ⏳ API Endpoints Pending
- IntegrationViewSet actions need implementation
### ⏳ Frontend Pending
- Content Types tab needs creation
---
## Next Actions
**IMMEDIATE:**
1. Implement IntegrationService methods (fetch_structure, import_taxonomies, import_content_titles)
2. Add API endpoints to IntegrationViewSet
3. Test with WordPress plugin
**SOON:**
4. Create frontend "Content Types" tab
5. Test end-to-end workflow
6. Add AI semantic mapping endpoint
---
## Summary
**We are going in the RIGHT direction!**
The unified content architecture is complete and production-ready. Now we need to:
1. **Store WP structure** in `SiteIntegration.config_json`
2. **Add service methods** to fetch and import from WP
3. **Add API endpoints** for frontend to trigger imports
4. **Build frontend UI** to manage content types
The deleted migration file was incorrect (wrong location, wrong approach). The correct approach is to use `SiteIntegration.config_json` to store content type configuration, not database migrations.
**Status: Ready to implement backend service methods!**

View File

@@ -1,138 +0,0 @@
#!/usr/bin/env python
"""
Test script to verify field rename is complete
Run after SQL migration: python manage.py shell < test_field_rename.py
"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.planning.models import ContentIdeas
from igny8_core.business.content.models import Tasks, Content
from django.db import connection
def test_models():
"""Test that models can access new field names"""
print("\n=== Testing Model Field Access ===")
# Test ContentIdeas
try:
idea = ContentIdeas.objects.first()
if idea:
print(f"✓ ContentIdeas.content_type: {idea.content_type}")
print(f"✓ ContentIdeas.content_structure: {idea.content_structure}")
else:
print("⚠ No ContentIdeas records to test")
except Exception as e:
print(f"✗ ContentIdeas error: {e}")
# Test Tasks
try:
task = Tasks.objects.first()
if task:
print(f"✓ Tasks.content_type: {task.content_type}")
print(f"✓ Tasks.content_structure: {task.content_structure}")
else:
print("⚠ No Tasks records to test")
except Exception as e:
print(f"✗ Tasks error: {e}")
# Test Content
try:
content = Content.objects.first()
if content:
print(f"✓ Content.content_type: {content.content_type}")
print(f"✓ Content.content_structure: {content.content_structure}")
print(f"✓ Content.content_html: {len(content.content_html) if content.content_html else 0} chars")
else:
print("⚠ No Content records to test")
except Exception as e:
print(f"✗ Content error: {e}")
def test_database_columns():
"""Verify database columns exist"""
print("\n=== Testing Database Column Names ===")
with connection.cursor() as cursor:
# Check igny8_content_ideas
cursor.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_name = 'igny8_content_ideas'
AND column_name IN ('content_type', 'content_structure', 'site_entity_type', 'cluster_role')
ORDER BY column_name
""")
cols = [row[0] for row in cursor.fetchall()]
print(f"igny8_content_ideas columns: {cols}")
if 'content_type' in cols and 'content_structure' in cols:
print("✓ ContentIdeas: new columns exist")
if 'site_entity_type' in cols or 'cluster_role' in cols:
print("✗ ContentIdeas: old columns still exist")
# Check igny8_tasks
cursor.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_name = 'igny8_tasks'
AND column_name IN ('content_type', 'content_structure', 'entity_type', 'cluster_role')
ORDER BY column_name
""")
cols = [row[0] for row in cursor.fetchall()]
print(f"igny8_tasks columns: {cols}")
if 'content_type' in cols and 'content_structure' in cols:
print("✓ Tasks: new columns exist")
if 'entity_type' in cols or 'cluster_role' in cols:
print("✗ Tasks: old columns still exist")
# Check igny8_content
cursor.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_name = 'igny8_content'
AND column_name IN ('content_type', 'content_structure', 'content_html', 'entity_type', 'cluster_role', 'html_content')
ORDER BY column_name
""")
cols = [row[0] for row in cursor.fetchall()]
print(f"igny8_content columns: {cols}")
if 'content_type' in cols and 'content_structure' in cols and 'content_html' in cols:
print("✓ Content: new columns exist")
if 'entity_type' in cols or 'cluster_role' in cols or 'html_content' in cols:
print("✗ Content: old columns still exist")
def test_choices():
"""Test that CHOICES are correctly defined"""
print("\n=== Testing Model CHOICES ===")
print(f"ContentIdeas.CONTENT_TYPE_CHOICES: {len(ContentIdeas.CONTENT_TYPE_CHOICES)} types")
print(f"ContentIdeas.CONTENT_STRUCTURE_CHOICES: {len(ContentIdeas.CONTENT_STRUCTURE_CHOICES)} structures")
print(f"Tasks.CONTENT_TYPE_CHOICES: {len(Tasks.CONTENT_TYPE_CHOICES)} types")
print(f"Tasks.CONTENT_STRUCTURE_CHOICES: {len(Tasks.CONTENT_STRUCTURE_CHOICES)} structures")
print(f"Content.CONTENT_TYPE_CHOICES: {len(Content.CONTENT_TYPE_CHOICES)} types")
print(f"Content.CONTENT_STRUCTURE_CHOICES: {len(Content.CONTENT_STRUCTURE_CHOICES)} structures")
# Verify all 14 structures are present
all_structures = [s[0] for s in Tasks.CONTENT_STRUCTURE_CHOICES]
expected = ['article', 'guide', 'comparison', 'review', 'listicle',
'landing_page', 'business_page', 'service_page', 'general', 'cluster_hub',
'product_page', 'category_archive', 'tag_archive', 'attribute_archive']
for struct in expected:
if struct in all_structures:
print(f"{struct}")
else:
print(f"✗ Missing: {struct}")
if __name__ == '__main__':
print("="*60)
print("Field Rename Validation Test")
print("="*60)
test_models()
test_database_columns()
test_choices()
print("\n" + "="*60)
print("Test Complete")
print("="*60)

View File

@@ -1,174 +0,0 @@
#!/usr/bin/env python
"""
Test script to run inside Docker container to diagnose generate_content issues
"""
import os
import sys
import django
# Setup Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
import logging
from django.contrib.auth import get_user_model
from igny8_core.auth.models import Account
from igny8_core.business.site_building.models import PageBlueprint, SiteBlueprint
from igny8_core.business.site_building.services.page_generation_service import PageGenerationService
from igny8_core.modules.system.models import IntegrationSettings
from igny8_core.ai.settings import get_model_config
# Set up logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def main():
print("=" * 80)
print("GENERATE_CONTENT DIAGNOSTIC TEST")
print("=" * 80)
# 1. Test User Authentication and get Account
print("\n1. Testing User Authentication...")
User = get_user_model()
user = User.objects.filter(email='dev@igny8.com').first()
if not user:
print("❌ ERROR: User 'dev@igny8.com' not found!")
return
print(f"✓ User found: {user.email} (ID: {user.id})")
# Get the associated account
account = user.account if hasattr(user, 'account') else None
if not account:
print("❌ ERROR: User has no associated Account!")
return
print(f"✓ Account found: {account.id}")
# 2. Check Integration Settings
print("\n2. Checking Integration Settings...")
try:
integration = IntegrationSettings.objects.filter(account=account, is_active=True).first()
if integration:
print(f"✓ Integration found: {integration.integration_type}")
print(f" - Config keys: {list(integration.config.keys()) if integration.config else 'None'}")
print(f" - Active: {integration.is_active}")
else:
print("❌ WARNING: No active IntegrationSettings found!")
print(" This will cause AI requests to fail!")
except Exception as e:
print(f"❌ ERROR checking integration: {e}")
# 3. Test Model Configuration
print("\n3. Testing Model Configuration...")
try:
model_config = get_model_config('generate_page_content', account=account)
print(f"✓ Model config loaded:")
print(f" - Model: {model_config.get('model')}")
print(f" - Max tokens: {model_config.get('max_tokens')}")
print(f" - Temperature: {model_config.get('temperature')}")
except Exception as e:
print(f"❌ ERROR: {e}")
import traceback
traceback.print_exc()
# 4. Check for Site Blueprints
print("\n4. Checking for Site Blueprints...")
site_blueprints = SiteBlueprint.objects.filter(account=account)
print(f" - Found {site_blueprints.count()} site blueprints")
if site_blueprints.exists():
sb = site_blueprints.first()
print(f" - First blueprint: {sb.name} (ID: {sb.id})")
print(f" - Pages: {sb.pages.count()}")
# 5. Check for Page Blueprints
print("\n5. Checking for Page Blueprints...")
pages = PageBlueprint.objects.filter(account=account)
print(f" - Found {pages.count()} page blueprints")
if not pages.exists():
print("❌ WARNING: No page blueprints found! Creating a test page...")
# Create a test site blueprint if needed
if not site_blueprints.exists():
from igny8_core.modules.planner.models import Sector
sector = Sector.objects.filter(account=account).first()
if not sector:
print("❌ ERROR: No sector found for account. Cannot create test blueprint.")
return
sb = SiteBlueprint.objects.create(
account=account,
sector=sector,
name="Test Site Blueprint",
site_type="business",
status="draft"
)
print(f"✓ Created test site blueprint: {sb.id}")
else:
sb = site_blueprints.first()
# Create a test page
page = PageBlueprint.objects.create(
account=account,
site_blueprint=sb,
sector=sb.sector,
title="Test Home Page",
slug="home",
type="home",
status="draft",
blocks_json=[
{
"type": "hero",
"heading": "Welcome to Our Test Site",
"subheading": "This is a test page for content generation"
},
{
"type": "features",
"heading": "Our Features"
}
]
)
print(f"✓ Created test page blueprint: {page.id}")
else:
page = pages.first()
print(f" - Using existing page: {page.title} (ID: {page.id})")
# 6. Test generate_page_content
print("\n6. Testing generate_page_content...")
print(f" - Page ID: {page.id}")
print(f" - Page title: {page.title}")
print(f" - Page type: {page.type}")
print(f" - Page status: {page.status}")
print(f" - Blocks count: {len(page.blocks_json) if page.blocks_json else 0}")
try:
service = PageGenerationService()
print("\n Calling service.generate_page_content()...")
result = service.generate_page_content(page, force_regenerate=False)
print(f"\n✓ SUCCESS!")
print(f" Result: {result}")
except ValueError as e:
print(f"\n❌ ValueError: {e}")
import traceback
traceback.print_exc()
except Exception as e:
print(f"\n❌ Exception: {e}")
import traceback
traceback.print_exc()
print("\n" + "=" * 80)
print("TEST COMPLETE")
print("=" * 80)
if __name__ == '__main__':
main()

View File

@@ -1,129 +0,0 @@
#!/usr/bin/env python3
"""
Simple test to check generate_content function execution
Run this to see exact error messages
"""
import os
import sys
import django
import logging
import json
# Setup Django
sys.path.insert(0, '/data/app/igny8/backend')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
# Setup logging to see all messages
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s'
)
from igny8_core.auth.models import Account
from igny8_core.modules.writer.models import Tasks
from igny8_core.ai.registry import get_function_instance
from igny8_core.ai.engine import AIEngine
print("\n" + "="*80)
print("SIMPLE GENERATE_CONTENT TEST")
print("="*80 + "\n")
# Get first account and task
try:
account = Account.objects.first()
print(f"✓ Account: {account.id} - {account.email}")
except Exception as e:
print(f"✗ Failed to get account: {e}")
sys.exit(1)
try:
task = Tasks.objects.filter(account=account).first()
if not task:
print("✗ No tasks found")
sys.exit(1)
print(f"✓ Task: {task.id} - {task.title or 'Untitled'}")
print(f" Status: {task.status}")
print(f" Cluster: {task.cluster.name if task.cluster else 'None'}")
print(f" Taxonomy: {task.taxonomy_term.name if task.taxonomy_term else 'None'}")
except Exception as e:
print(f"✗ Failed to get task: {e}")
sys.exit(1)
# Get function
try:
fn = get_function_instance('generate_content')
print(f"✓ Function loaded: {fn.get_name()}")
except Exception as e:
print(f"✗ Failed to load function: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
# Test validation
print("\nTesting validation...")
try:
payload = {'ids': [task.id]}
result = fn.validate(payload, account)
if result['valid']:
print(f"✓ Validation passed")
else:
print(f"✗ Validation failed: {result.get('error')}")
sys.exit(1)
except Exception as e:
print(f"✗ Validation error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
# Test prepare
print("\nTesting prepare...")
try:
data = fn.prepare(payload, account)
print(f"✓ Prepare succeeded: {len(data) if isinstance(data, list) else 1} task(s)")
except Exception as e:
print(f"✗ Prepare error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
# Test build_prompt
print("\nTesting build_prompt...")
try:
prompt = fn.build_prompt(data, account)
print(f"✓ Prompt built: {len(prompt)} characters")
print(f"\nPrompt preview (first 500 chars):")
print("-" * 80)
print(prompt[:500])
print("-" * 80)
except Exception as e:
print(f"✗ Build prompt error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
# Test model config
print("\nTesting model config...")
try:
from igny8_core.ai.settings import get_model_config
model_config = get_model_config('generate_content', account=account)
print(f"✓ Model config loaded:")
print(f" Model: {model_config.get('model')}")
print(f" Max tokens: {model_config.get('max_tokens')}")
print(f" Temperature: {model_config.get('temperature')}")
except Exception as e:
print(f"✗ Model config error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
print("\n" + "="*80)
print("All tests passed! The function structure is correct.")
print("If content generation still fails, the issue is likely:")
print("1. API key is invalid or missing")
print("2. OpenAI API error (rate limit, quota, etc.)")
print("3. Prompt is too long or has invalid format")
print("4. Celery worker is not running or has errors")
print("\nCheck Celery worker logs with:")
print(" journalctl -u celery-worker -f")
print("="*80 + "\n")

View File

@@ -1,162 +0,0 @@
"""
Test Script to Manually Push WordPress Structure to IGNY8 Backend
This simulates what the WordPress plugin would send
"""
import requests
import json
# Configuration
API_BASE = "https://api.igny8.com/api/v1"
SITE_ID = 5 # From the URL: https://app.igny8.com/sites/5/settings
# You need to get an API key from Django admin or use your access token
# For now, this script shows what data should be sent
API_KEY = "YOUR_API_KEY_HERE" # Replace with actual API key
# Step 1: Get the integration ID for this WordPress site
def get_integration_id():
url = f"{API_BASE}/integration/integrations/"
params = {"site": SITE_ID, "platform": "wordpress"}
headers = {"Authorization": f"Bearer {API_KEY}"}
response = requests.get(url, params=params, headers=headers)
print(f"Get Integration Response: {response.status_code}")
print(json.dumps(response.json(), indent=2))
if response.status_code == 200:
data = response.json()
# Handle paginated response
if 'results' in data:
integrations = data['results']
elif isinstance(data, list):
integrations = data
else:
integrations = [data]
if integrations:
return integrations[0]['id']
return None
# Step 2: Push WordPress structure to backend
def push_structure(integration_id):
url = f"{API_BASE}/integration/integrations/{integration_id}/update-structure/"
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
# Sample WordPress structure data
payload = {
"post_types": {
"post": {
"label": "Posts",
"count": 150,
"enabled": True,
"fetch_limit": 100
},
"page": {
"label": "Pages",
"count": 25,
"enabled": True,
"fetch_limit": 100
},
"product": {
"label": "Products",
"count": 89,
"enabled": True,
"fetch_limit": 100
}
},
"taxonomies": {
"category": {
"label": "Categories",
"count": 15,
"enabled": True,
"fetch_limit": 100
},
"post_tag": {
"label": "Tags",
"count": 234,
"enabled": True,
"fetch_limit": 100
},
"product_cat": {
"label": "Product Categories",
"count": 12,
"enabled": True,
"fetch_limit": 100
}
},
"timestamp": "2025-11-22T04:20:00+00:00",
"site_url": "https://example.com",
"wordpress_version": "6.4",
"plugin_connection_enabled": True,
"two_way_sync_enabled": True
}
response = requests.post(url, json=payload, headers=headers)
print(f"\nPush Structure Response: {response.status_code}")
print(json.dumps(response.json(), indent=2))
return response.status_code == 200
# Step 3: Verify the data was stored
def verify_content_types(integration_id):
url = f"{API_BASE}/integration/integrations/{integration_id}/content-types/"
headers = {"Authorization": f"Bearer {API_KEY}"}
response = requests.get(url, headers=headers)
print(f"\nGet Content Types Response: {response.status_code}")
print(json.dumps(response.json(), indent=2))
return response.status_code == 200
# Main execution
if __name__ == "__main__":
print("=== WordPress Structure Push Test ===\n")
print(f"Site ID: {SITE_ID}")
print(f"API Base: {API_BASE}\n")
if API_KEY == "YOUR_API_KEY_HERE":
print("ERROR: Please set your API_KEY in the script!")
print("\nTo get an API key:")
print("1. Go to Django admin")
print("2. Generate a WordPress API key for your site")
print("3. Replace 'YOUR_API_KEY_HERE' in this script")
exit(1)
# Step 1
print("Step 1: Getting Integration ID...")
integration_id = get_integration_id()
if not integration_id:
print("\nERROR: Could not find WordPress integration for site!")
print("Make sure:")
print("1. The site exists in the database")
print("2. A WordPress integration exists for this site")
print("3. Your API key is valid")
exit(1)
print(f"\n✓ Found Integration ID: {integration_id}")
# Step 2
print("\nStep 2: Pushing WordPress structure...")
if push_structure(integration_id):
print("\n✓ Structure pushed successfully!")
else:
print("\n✗ Failed to push structure!")
exit(1)
# Step 3
print("\nStep 3: Verifying content types...")
if verify_content_types(integration_id):
print("\n✓ Content types verified!")
print("\n=== TEST COMPLETE ===")
print("\nNow refresh the frontend page:")
print(f"https://app.igny8.com/sites/{SITE_ID}/settings?tab=content-types")
else:
print("\n✗ Could not verify content types!")
exit(1)

View File

@@ -1,94 +0,0 @@
#!/usr/bin/env python
"""Test generate_content for Writer tasks"""
import os
import sys
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from django.contrib.auth import get_user_model
from igny8_core.business.content.models import Tasks
from igny8_core.business.content.services.content_generation_service import ContentGenerationService
print("=" * 80)
print("TESTING WRITER GENERATE_CONTENT")
print("=" * 80)
User = get_user_model()
user = User.objects.filter(email='dev@igny8.com').first()
if not user:
print("❌ User not found")
sys.exit(1)
account = user.account if hasattr(user, 'account') else None
if not account:
print("❌ No account found for user")
sys.exit(1)
print(f"✓ User: {user.email}")
print(f"✓ Account: {account.id}")
# Get tasks
tasks = Tasks.objects.filter(account=account, status='queued')[:5]
print(f"\n✓ Found {tasks.count()} queued tasks")
if tasks.exists():
task = tasks.first()
print(f"\nTesting with task:")
print(f" - ID: {task.id}")
print(f" - Title: {task.title}")
print(f" - Status: {task.status}")
print(f" - Cluster: {task.cluster.name if task.cluster else 'None'}")
print("\nCalling generate_content service...")
service = ContentGenerationService()
try:
result = service.generate_content([task.id], account)
print(f"\n✓ SUCCESS!")
print(f"Result: {result}")
except Exception as e:
print(f"\n❌ ERROR: {e}")
import traceback
traceback.print_exc()
else:
print("\n⚠️ No queued tasks found. Creating a test task...")
from igny8_core.modules.planner.models import Clusters
cluster = Clusters.objects.filter(account=account).first()
if not cluster:
print("❌ No clusters found. Cannot create test task.")
sys.exit(1)
task = Tasks.objects.create(
account=account,
site=cluster.site,
sector=cluster.sector,
cluster=cluster,
title="Test Article: Benefits of Organic Cotton Bedding",
description="Comprehensive guide covering health benefits, environmental impact, and buying guide",
content_type='post',
content_structure='article',
status='queued'
)
print(f"✓ Created test task: {task.id} - {task.title}")
print("\nCalling generate_content service...")
service = ContentGenerationService()
try:
result = service.generate_content([task.id], account)
print(f"\n✓ SUCCESS!")
print(f"Result: {result}")
except Exception as e:
print(f"\n❌ ERROR: {e}")
import traceback
traceback.print_exc()
print("\n" + "=" * 80)
print("TEST COMPLETE")
print("=" * 80)