Initial commit - clean project structure

This commit is contained in:
2026-08-18 16:48:15 +03:30
commit 331148037e
147 changed files with 5543 additions and 0 deletions

0
statuslog/__init__.py Normal file
View File

10
statuslog/admin.py Normal file
View File

@@ -0,0 +1,10 @@
from django.contrib import admin
from .models import StatusChangeLog
@admin.register(StatusChangeLog)
class StatusChangeLogAdmin(admin.ModelAdmin):
list_display = ('id', 'label', 'object_id', 'old_status', 'new_status', 'event', 'user_id', 'created_at')
list_filter = ('label', 'event', 'new_status')
search_fields = ('object_id', 'user_id')

9
statuslog/apps.py Normal file
View File

@@ -0,0 +1,9 @@
from django.apps import AppConfig
class StatuslogConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'statuslog'
def ready(self):
from . import receivers # noqa: F401

View File

@@ -0,0 +1,30 @@
# Generated by Django 6.1 on 2026-08-18 02:26
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='StatusChangeLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('label', models.CharField(db_index=True, max_length=50)),
('object_id', models.IntegerField(db_index=True)),
('old_status', models.CharField(blank=True, max_length=30)),
('new_status', models.CharField(max_length=30)),
('event', models.CharField(max_length=50)),
('user_id', models.IntegerField(blank=True, db_index=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
options={
'ordering': ['-created_at'],
},
),
]

View File

23
statuslog/models.py Normal file
View File

@@ -0,0 +1,23 @@
from django.db import models
class StatusChangeLog(models.Model):
"""
لاگ عمومیِ تغییر Status هر مدلی در پروژه (Order، Subscription، UserOffer و...)
از طریق سیگنال statuslog.signals.status_changed ساخته می‌شه، نه مستقیم.
label به‌جای FK واقعی، چون این اپ نباید به هیچ اپ دیگه‌ای وابسته بشه
(دقیقاً همون الگوی ارجاع سبک با id که در کل پروژه استفاده شده).
"""
label = models.CharField(max_length=50, db_index=True) # مثل 'payment.Order'
object_id = models.IntegerField(db_index=True)
old_status = models.CharField(max_length=30, blank=True)
new_status = models.CharField(max_length=30)
event = models.CharField(max_length=50) # چه رویدادی باعث تغییر شده
user_id = models.IntegerField(null=True, blank=True, db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return f"{self.label}#{self.object_id}: {self.old_status}→{self.new_status} ({self.event})"

16
statuslog/receivers.py Normal file
View File

@@ -0,0 +1,16 @@
from .models import StatusChangeLog
from .signals import status_changed
def _log_status_change(sender, **kwargs):
StatusChangeLog.objects.create(
label=sender,
object_id=kwargs['object_id'],
old_status=kwargs.get('old_status', ''),
new_status=kwargs['new_status'],
event=kwargs['event'],
user_id=kwargs.get('user_id'),
)
status_changed.connect(_log_status_change, dispatch_uid='statuslog_log_status_change')

27
statuslog/signals.py Normal file
View File

@@ -0,0 +1,27 @@
"""
سیگنال عمومیِ تغییر Status، مستقل از هر اپ خاص (مثل الگوی notifications که
هیچ اپی بهش رفرنس نمی‌ده، ولی برعکسش: اینجا خودِ اپ‌ها چیزی از statuslog
import نمی‌کنن جز همین سیگنال، و منطق ثبت لاگ کاملاً داخل statuslog می‌مونه).
هر اپی که یک فیلد status داره (Order، Subscription، UserOffer، در آینده
پیشرفت درس/دوره و ...) موقع تغییر status باید این سیگنال رو send کنه:
from statuslog.signals import status_changed
status_changed.send(
sender='payment.Order', # رشته‌ی label مدل، نه خودِ کلاس
object_id=order.id,
old_status=old_status,
new_status=order.status,
event='payment_webhook_paid', # چه اتفاقی باعث تغییر شده
user_id=order.user_id, # اختیاری
)
چون send با sender رشته‌ای (نه کلاس مدل) صدا زده می‌شه، هیچ اپی لازم نیست
مدل واقعیِ اپ دیگه رو import کنه؛ دقیقاً همون اصل «ارجاع سبک، بدون FK واقعی»
که در کل پروژه رعایت شده.
"""
from django.dispatch import Signal
# kwargs: sender (str, مثل 'payment.Order')، object_id، old_status، new_status،
# event، user_id (اختیاری)
status_changed = Signal()