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,26 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0031_drop_all_blueprint_tables'),
]
operations = [
migrations.AddField(
model_name='account',
name='account_timezone',
field=models.CharField(default='UTC', help_text='IANA timezone name', max_length=64),
),
migrations.AddField(
model_name='account',
name='timezone_mode',
field=models.CharField(choices=[('country', 'Country'), ('manual', 'Manual')], default='country', help_text='Timezone selection mode', max_length=20),
),
migrations.AddField(
model_name='account',
name='timezone_offset',
field=models.CharField(blank=True, default='', help_text='Optional UTC offset label', max_length=10),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.2.9 on 2026-01-19 00:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0032_add_account_timezone_fields'),
]
operations = [
migrations.AddField(
model_name='historicalaccount',
name='account_timezone',
field=models.CharField(default='UTC', max_length=64, help_text='IANA timezone name'),
),
migrations.AddField(
model_name='historicalaccount',
name='timezone_mode',
field=models.CharField(choices=[('country', 'Country'), ('manual', 'Manual')], default='country', max_length=20, help_text='Timezone selection mode'),
),
migrations.AddField(
model_name='historicalaccount',
name='timezone_offset',
field=models.CharField(blank=True, default='', max_length=10, help_text='Optional UTC offset label'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.9 on 2026-01-19 00:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0033_add_account_timezone_history_fields'),
]
operations = [
migrations.AddField(
model_name='user',
name='phone',
field=models.CharField(blank=True, default='', max_length=30),
),
]

View File

@@ -106,6 +106,16 @@ class Account(SoftDeletableModel):
billing_postal_code = models.CharField(max_length=20, blank=True)
billing_country = models.CharField(max_length=2, blank=True, help_text="ISO 2-letter country code")
tax_id = models.CharField(max_length=100, blank=True, help_text="VAT/Tax ID number")
# Account timezone (single source of truth for all users/sites)
account_timezone = models.CharField(max_length=64, default='UTC', help_text="IANA timezone name")
timezone_mode = models.CharField(
max_length=20,
choices=[('country', 'Country'), ('manual', 'Manual')],
default='country',
help_text="Timezone selection mode"
)
timezone_offset = models.CharField(max_length=10, blank=True, default='', help_text="Optional UTC offset label")
# Monthly usage tracking (reset on billing cycle)
usage_ahrefs_queries = models.IntegerField(default=0, validators=[MinValueValidator(0)], help_text="Ahrefs queries used this month")
@@ -922,6 +932,7 @@ class User(AbstractUser):
account = models.ForeignKey('igny8_core_auth.Account', on_delete=models.CASCADE, related_name='users', null=True, blank=True, db_column='tenant_id')
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='viewer')
email = models.EmailField(_('email address'), unique=True)
phone = models.CharField(max_length=30, blank=True, default='')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

View File

@@ -53,7 +53,9 @@ class AccountSerializer(serializers.ModelSerializer):
fields = [
'id', 'name', 'slug', 'owner', 'plan', 'plan_id',
'credits', 'status', 'payment_method',
'subscription', 'billing_country', 'created_at'
'subscription', 'billing_country',
'account_timezone', 'timezone_mode', 'timezone_offset',
'created_at'
]
read_only_fields = ['owner', 'created_at']
@@ -270,7 +272,18 @@ class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'role', 'account', 'accessible_sites', 'created_at']
fields = [
'id',
'username',
'email',
'phone',
'first_name',
'last_name',
'role',
'account',
'accessible_sites',
'created_at',
]
read_only_fields = ['created_at']
def get_accessible_sites(self, obj):

View File

@@ -255,6 +255,25 @@ class UsersViewSet(AccountModelViewSet):
serializer = UserSerializer(user)
return success_response(data={'user': serializer.data}, request=request)
@action(detail=False, methods=['get', 'patch'], permission_classes=[IsAuthenticatedAndActive])
def me(self, request):
"""Get or update the current user profile."""
user = request.user
if request.method == 'PATCH':
serializer = UserSerializer(user, data=request.data, partial=True)
if not serializer.is_valid():
return error_response(
error='Validation failed',
errors=serializer.errors,
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
serializer.save()
serializer = UserSerializer(user)
return success_response(data={'user': serializer.data}, request=request)
# ============================================================================
# 3. ACCOUNTS - Register each unique organization/user space