account, schduels, timezone profile and many imporant updates

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-19 15:37:03 +00:00
parent 618ed8b8c6
commit e7219a2390
28 changed files with 919 additions and 358 deletions

View File

@@ -0,0 +1,43 @@
# Generated by Django 5.2.9 on 2026-01-19 00:00
from django.db import migrations
def add_team_invite_template(apps, schema_editor):
EmailTemplate = apps.get_model('system', 'EmailTemplate')
EmailTemplate.objects.get_or_create(
template_name='team_invite',
defaults={
'template_path': 'emails/team_invite.html',
'display_name': 'Team Invitation',
'description': 'Sent when a team member is invited to join an account',
'template_type': 'auth',
'default_subject': "You're invited to join IGNY8",
'required_context': ['inviter_name', 'invited_name', 'account_name', 'reset_url', 'frontend_url'],
'sample_context': {
'inviter_name': 'Alex Johnson',
'invited_name': 'Jamie Lee',
'account_name': 'Acme Co',
'reset_url': 'https://app.igny8.com/reset-password?token=example',
'frontend_url': 'https://app.igny8.com',
},
'is_active': True,
}
)
def remove_team_invite_template(apps, schema_editor):
EmailTemplate = apps.get_model('system', 'EmailTemplate')
EmailTemplate.objects.filter(template_name='team_invite').delete()
class Migration(migrations.Migration):
dependencies = [
('system', '0025_delete_accountintegrationoverride'),
]
operations = [
migrations.RunPython(add_team_invite_template, remove_team_invite_template),
]

View File

@@ -1595,8 +1595,9 @@ class ContentViewSet(SiteSectorModelViewSet):
}
"""
from django.utils import timezone
from datetime import timedelta
from igny8_core.business.integration.models import Site, SitePublishingSettings
from igny8_core.auth.models import Site
from igny8_core.business.integration.models import PublishingSettings
from igny8_core.tasks.publishing_scheduler import _calculate_available_slots
import logging
logger = logging.getLogger(__name__)
@@ -1621,7 +1622,7 @@ class ContentViewSet(SiteSectorModelViewSet):
# Get site and publishing settings
try:
site = Site.objects.get(id=site_id)
pub_settings = SitePublishingSettings.objects.filter(site=site).first()
pub_settings, _ = PublishingSettings.get_or_create_for_site(site)
except Site.DoesNotExist:
return error_response(
error=f'Site {site_id} not found',
@@ -1629,41 +1630,25 @@ class ContentViewSet(SiteSectorModelViewSet):
request=request
)
# Default settings if none exist
base_time_str = '09:00 AM'
stagger_interval = 15 # minutes
timezone_str = 'America/New_York'
if pub_settings:
base_time_str = pub_settings.auto_publish_time or base_time_str
stagger_interval = pub_settings.stagger_interval_minutes or stagger_interval
timezone_str = pub_settings.timezone or timezone_str
# Get content items
content_qs = self.get_queryset().filter(id__in=content_ids)
# Generate schedule preview
# Generate schedule preview using the same slot logic as automation
schedule_preview = []
now = timezone.now()
# Parse base time (format: "09:00 AM" or "14:30")
try:
from datetime import datetime
if 'AM' in base_time_str or 'PM' in base_time_str:
time_obj = datetime.strptime(base_time_str, '%I:%M %p').time()
else:
time_obj = datetime.strptime(base_time_str, '%H:%M').time()
except ValueError:
time_obj = datetime.strptime('09:00', '%H:%M').time()
# Start from tomorrow at base time
start_date = now.replace(hour=time_obj.hour, minute=time_obj.minute, second=0, microsecond=0)
if start_date <= now:
start_date += timedelta(days=1)
available_slots = _calculate_available_slots(pub_settings, site)
if not available_slots:
return error_response(
error='No available publishing slots based on site defaults',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
# Create schedule for each content item
for index, content in enumerate(content_qs):
scheduled_at = start_date + timedelta(minutes=stagger_interval * index)
if index >= len(available_slots):
break
scheduled_at = available_slots[index]
schedule_preview.append({
'content_id': content.id,
'title': content.title,
@@ -1671,15 +1656,25 @@ class ContentViewSet(SiteSectorModelViewSet):
})
logger.info(f"[bulk_schedule_preview] Generated preview for {len(schedule_preview)} items")
# Base time for display should match first scheduled slot
display_base_time = '09:00'
if schedule_preview:
try:
from datetime import datetime
first_dt = datetime.fromisoformat(schedule_preview[0]['scheduled_at'])
display_base_time = first_dt.strftime('%H:%M')
except Exception:
display_base_time = '09:00'
return success_response(
data={
'scheduled_count': len(schedule_preview),
'schedule_preview': schedule_preview,
'site_settings': {
'base_time': base_time_str,
'stagger_interval': stagger_interval,
'timezone': timezone_str,
'base_time': display_base_time,
'stagger_interval': pub_settings.stagger_interval_minutes,
'timezone': site.account.account_timezone if hasattr(site, 'account') else 'UTC',
},
},
message=f'Preview generated for {len(schedule_preview)} items',
@@ -1699,8 +1694,9 @@ class ContentViewSet(SiteSectorModelViewSet):
}
"""
from django.utils import timezone
from datetime import timedelta
from igny8_core.business.integration.models import Site, SitePublishingSettings
from igny8_core.auth.models import Site
from igny8_core.business.integration.models import PublishingSettings
from igny8_core.tasks.publishing_scheduler import _calculate_available_slots
import logging
logger = logging.getLogger(__name__)
@@ -1726,7 +1722,7 @@ class ContentViewSet(SiteSectorModelViewSet):
# Get site and publishing settings
try:
site = Site.objects.get(id=site_id)
pub_settings = SitePublishingSettings.objects.filter(site=site).first()
pub_settings, _ = PublishingSettings.get_or_create_for_site(site)
except Site.DoesNotExist:
return error_response(
error=f'Site {site_id} not found',
@@ -1734,39 +1730,28 @@ class ContentViewSet(SiteSectorModelViewSet):
request=request
)
# Default settings if none exist
base_time_str = '09:00 AM'
stagger_interval = 15 # minutes
if pub_settings and use_site_defaults:
base_time_str = pub_settings.auto_publish_time or base_time_str
stagger_interval = pub_settings.stagger_interval_minutes or stagger_interval
# Get content items
content_qs = self.get_queryset().filter(id__in=content_ids)
# Generate schedule and apply
# Generate schedule and apply using the same slot logic as automation
available_slots = _calculate_available_slots(pub_settings, site) if use_site_defaults else []
if use_site_defaults and not available_slots:
return error_response(
error='No available publishing slots based on site defaults',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
now = timezone.now()
# Parse base time
try:
from datetime import datetime
if 'AM' in base_time_str or 'PM' in base_time_str:
time_obj = datetime.strptime(base_time_str, '%I:%M %p').time()
else:
time_obj = datetime.strptime(base_time_str, '%H:%M').time()
except ValueError:
time_obj = datetime.strptime('09:00', '%H:%M').time()
# Start from tomorrow at base time
start_date = now.replace(hour=time_obj.hour, minute=time_obj.minute, second=0, microsecond=0)
if start_date <= now:
start_date += timedelta(days=1)
# Schedule each content item
scheduled_count = 0
for index, content in enumerate(content_qs):
scheduled_at = start_date + timedelta(minutes=stagger_interval * index)
if use_site_defaults:
if index >= len(available_slots):
break
scheduled_at = available_slots[index]
else:
scheduled_at = now
content.site_status = 'scheduled'
content.scheduled_publish_at = scheduled_at
content.site_status_updated_at = now