django bacekdn opeartioanl fixes and site wp integration api fixes

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-11 21:54:08 +00:00
parent 00ef985a5f
commit 3925ddf894
16 changed files with 2045 additions and 89 deletions

View File

@@ -5,6 +5,12 @@ from django.contrib import admin
from django.utils.html import format_html
from django.contrib import messages
from unfold.admin import ModelAdmin
from unfold.contrib.filters.admin import (
RelatedDropdownFilter,
ChoicesDropdownFilter,
DropdownFilter,
RangeDateFilter,
)
from simple_history.admin import SimpleHistoryAdmin
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
from igny8_core.business.billing.models import (
@@ -20,7 +26,6 @@ from igny8_core.business.billing.models import (
from .models import CreditTransaction, CreditUsageLog, AccountPaymentMethod
from import_export.admin import ExportMixin, ImportExportMixin
from import_export import resources
from rangefilter.filters import DateRangeFilter
class CreditTransactionResource(resources.ModelResource):
@@ -36,7 +41,11 @@ class CreditTransactionResource(resources.ModelResource):
class CreditTransactionAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
resource_class = CreditTransactionResource
list_display = ['id', 'account', 'transaction_type', 'amount', 'balance_after', 'description', 'created_at']
list_filter = ['transaction_type', ('created_at', DateRangeFilter), 'account']
list_filter = [
('transaction_type', ChoicesDropdownFilter),
('created_at', RangeDateFilter),
('account', RelatedDropdownFilter),
]
search_fields = ['description', 'account__name']
readonly_fields = ['created_at']
date_hierarchy = 'created_at'
@@ -188,7 +197,13 @@ class PaymentAdmin(ExportMixin, AccountAdminMixin, SimpleHistoryAdmin, Igny8Mode
'approved_by',
'processed_at',
]
list_filter = ['status', 'payment_method', 'currency', ('created_at', DateRangeFilter), ('processed_at', DateRangeFilter)]
list_filter = [
('status', ChoicesDropdownFilter),
('payment_method', ChoicesDropdownFilter),
('currency', ChoicesDropdownFilter),
('created_at', RangeDateFilter),
('processed_at', RangeDateFilter),
]
search_fields = [
'invoice__invoice_number',
'account__name',
@@ -654,10 +669,10 @@ class PlanLimitUsageAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
'created_at',
]
list_filter = [
'limit_type',
('period_start', DateRangeFilter),
('period_end', DateRangeFilter),
'account',
('limit_type', ChoicesDropdownFilter),
('period_start', RangeDateFilter),
('period_end', RangeDateFilter),
('account', RelatedDropdownFilter),
]
search_fields = ['account__name']
readonly_fields = ['created_at', 'updated_at']

View File

@@ -67,25 +67,23 @@ class IntegrationViewSet(SiteSectorModelViewSet):
api_key = serializers.SerializerMethodField()
def get_api_key(self, obj):
"""Return the API key from encrypted credentials"""
credentials = obj.get_credentials()
return credentials.get('api_key', '')
"""Return the API key from Site.wp_api_key (SINGLE source of truth)"""
# API key is stored on Site model, not in SiteIntegration credentials
return obj.site.wp_api_key or ''
def validate(self, data):
"""
Custom validation for WordPress integrations.
API key is the only required authentication method.
API key is stored on Site model, not in SiteIntegration.
"""
validated_data = super().validate(data)
# For WordPress platform, require API key only
# For WordPress platform, check API key exists on Site (not in credentials_json)
if validated_data.get('platform') == 'wordpress':
credentials = validated_data.get('credentials_json', {})
# API key is required for all WordPress integrations
if not credentials.get('api_key'):
site = validated_data.get('site') or getattr(self.instance, 'site', None)
if site and not site.wp_api_key:
raise serializers.ValidationError({
'credentials_json': 'API key is required for WordPress integration.'
'site': 'Site must have an API key generated before creating WordPress integration.'
})
return validated_data
@@ -198,7 +196,7 @@ class IntegrationViewSet(SiteSectorModelViewSet):
# Try to find an existing integration for this site+platform
integration = SiteIntegration.objects.filter(site=site, platform='wordpress').first()
# If not found, create and save the integration to database
# If not found, create and save the integration to database (for status tracking, not credentials)
integration_created = False
if not integration:
integration = SiteIntegration.objects.create(
@@ -207,7 +205,7 @@ class IntegrationViewSet(SiteSectorModelViewSet):
platform='wordpress',
platform_type='cms',
config_json={'site_url': site_url} if site_url else {},
credentials_json={'api_key': api_key} if api_key else {},
credentials_json={}, # API key is stored in Site.wp_api_key, not here
is_active=True,
sync_enabled=True
)
@@ -805,27 +803,38 @@ class IntegrationViewSet(SiteSectorModelViewSet):
random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
api_key = f"igny8_site_{site_id}_{timestamp}_{random_suffix}"
# Get or create SiteIntegration
# SINGLE SOURCE OF TRUTH: Store API key ONLY in Site.wp_api_key
# This is where APIKeyAuthentication validates against
site.wp_api_key = api_key
site.save(update_fields=['wp_api_key'])
# Get or create SiteIntegration (for integration status/config, NOT credentials)
integration, created = SiteIntegration.objects.get_or_create(
site=site,
platform='wordpress',
defaults={
'integration_type': 'wordpress',
'account': site.account,
'platform': 'wordpress',
'platform_type': 'cms',
'is_active': True,
'credentials_json': {'api_key': api_key},
'sync_enabled': True,
'credentials_json': {}, # Empty - API key is on Site model
'config_json': {}
}
)
# If integration already exists, update the API key
# If integration already exists, just ensure it's active
if not created:
credentials = integration.get_credentials()
credentials['api_key'] = api_key
integration.credentials_json = credentials
integration.is_active = True
integration.sync_enabled = True
# Clear any old credentials_json API key (migrate to Site.wp_api_key)
if integration.credentials_json.get('api_key'):
integration.credentials_json = {}
integration.save()
logger.info(
f"Generated new API key for site {site.name} (ID: {site_id}), "
f"integration {'created' if created else 'updated'}"
f"stored in Site.wp_api_key (single source of truth)"
)
# Serialize the integration with the new key

View File

@@ -122,10 +122,10 @@ def wordpress_status_webhook(request):
request=request
)
# Verify API key matches integration
stored_api_key = integration.credentials_json.get('api_key')
# Verify API key matches Site.wp_api_key (SINGLE source of truth)
stored_api_key = integration.site.wp_api_key
if not stored_api_key or stored_api_key != api_key:
logger.error(f"[wordpress_status_webhook] Invalid API key for integration {integration.id}")
logger.error(f"[wordpress_status_webhook] Invalid API key for site {integration.site.id}")
return error_response(
error='Invalid API key',
status_code=http_status.HTTP_401_UNAUTHORIZED,
@@ -293,8 +293,8 @@ def wordpress_metadata_webhook(request):
request=request
)
# Verify API key
stored_api_key = integration.credentials_json.get('api_key')
# Verify API key against Site.wp_api_key (SINGLE source of truth)
stored_api_key = integration.site.wp_api_key
if not stored_api_key or stored_api_key != api_key:
return error_response(
error='Invalid API key',

View File

@@ -114,7 +114,6 @@ class KeywordsAdmin(ImportExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
'bulk_assign_cluster',
'bulk_set_status_active',
'bulk_set_status_inactive',
'bulk_soft_delete',
]
@admin.display(description='Keyword')