This commit is contained in:
2026-08-31 15:11:52 +03:30
parent 236002a87c
commit 463b812e3e
63 changed files with 3254 additions and 1476 deletions
+92 -2
View File
@@ -1,14 +1,39 @@
from django.contrib import admin
from django.contrib import admin, messages
from django.shortcuts import redirect
from django.urls import path, reverse
from django.utils.html import format_html
from .models import BazaarPurchase, BazaarSettings, FeatureActivation, FeaturePlan, FeaturePlanTier, GemPack, GemTransaction
from .providers.bazaar import BazaarAuthError, build_authorize_url, exchange_code_for_tokens
@admin.register(BazaarSettings)
class BazaarSettingsAdmin(admin.ModelAdmin):
"""تک‌ردیفی — لیست را رد می‌کنیم و مستقیم فرم ویرایش همان یک ردیف را نشان می‌دهیم."""
"""تک‌ردیفی — لیست را رد می‌کنیم و مستقیم فرم ویرایش همان یک ردیف را نشان می‌دهیم.
برای اتصال با روش OAuth (تمدید خودکار access_token): این سه فیلد را پر
کن — Client ID، Client Secret، application id اپ — ذخیره کن، بعد از
«وضعیت اتصال کافه‌بازار» پایین فرم روی «شروع اتصال» بزن. اگر Redirect URI
که موقع ساخت Client در پیشخان ثبت کرده‌ای با آدرسِ نشان‌داده‌شده اینجا فرق
دارد، بعد از تایید در کافه‌بازار، مقدار code= را از آدرسِ برگشتی کپی و
خودت به همان آدرس callback اضافه کن (?code=...) — نیازی به فیلد جداگانه نیست.
یا ساده‌تر: اگر همین حالا یک توکن ثابتِ «روش جدید» پیشخان داری، مستقیم در
Access Token بچسبان و Access Token Expires At را خالی بگذار — کارش تمام
است، فقط اگر روزی باطل شد باید دستی توکن تازه بگیری."""
list_display = ("package_name", "updated_at")
readonly_fields = ("oauth_status",)
fields = (
"package_name",
"access_token",
"access_token_expires_at",
"refresh_token",
"client_id",
"client_secret",
"dynamic_discount_secret",
"oauth_status",
)
def has_add_permission(self, request):
return not BazaarSettings.objects.exists()
@@ -17,6 +42,71 @@ class BazaarSettingsAdmin(admin.ModelAdmin):
obj = BazaarSettings.get_solo()
return redirect("admin:billing_bazaarsettings_change", obj.pk)
def get_urls(self):
custom = [
path(
"oauth/connect/",
self.admin_site.admin_view(self.oauth_connect_view),
name="billing_bazaarsettings_oauth_connect",
),
path(
"oauth/callback/",
self.admin_site.admin_view(self.oauth_callback_view),
name="billing_bazaarsettings_oauth_callback",
),
]
return custom + super().get_urls()
def _callback_url(self, request) -> str:
return request.build_absolute_uri(reverse("admin:billing_bazaarsettings_oauth_callback"))
def oauth_connect_view(self, request):
obj = BazaarSettings.get_solo()
try:
authorize_url = build_authorize_url(self._callback_url(request))
except BazaarAuthError as exc:
messages.error(request, str(exc))
return redirect("admin:billing_bazaarsettings_change", obj.pk)
return redirect(authorize_url)
def oauth_callback_view(self, request):
obj = BazaarSettings.get_solo()
code = request.GET.get("code")
if not code:
messages.error(request, "پارامتر code در آدرس بازگشتی پیدا نشد.")
return redirect("admin:billing_bazaarsettings_change", obj.pk)
try:
exchange_code_for_tokens(code, self._callback_url(request))
except BazaarAuthError as exc:
messages.error(request, f"اتصال به کافه‌بازار ناموفق بود: {exc}")
else:
messages.success(request, "اتصال به کافه‌بازار موفق بود — access_token از این پس خودکار تمدید می‌شود.")
return redirect("admin:billing_bazaarsettings_change", obj.pk)
@admin.display(description="وضعیت اتصال کافه‌بازار")
def oauth_status(self, obj):
if not obj or not obj.pk:
return "ابتدا ذخیره کن."
callback_hint = reverse("admin:billing_bazaarsettings_oauth_callback")
connect_url = reverse("admin:billing_bazaarsettings_oauth_connect")
if obj.access_token_expires_at:
status_line = f"متصل (OAuth) — انقضای فعلی: {obj.access_token_expires_at:%Y-%m-%d %H:%M}"
elif obj.access_token:
status_line = "متصل با توکن ثابتِ روش جدید (بدون تمدید خودکار)."
else:
status_line = "هنوز متصل نیست."
return format_html(
"<p>{}</p>"
"<p>Redirect URI برای ثبت در پیشخان (اگر می‌خواهی OAuth را وصل کنی) — این مسیر را با "
"آدرس دامنه‌ی سرورت جلو ببر، مثلا https://api.example.com{} :</p>"
"<p><code>{}</code></p>"
'<p><a class="button" href="{}">شروع اتصال به کافه‌بازار (OAuth)</a></p>',
status_line,
callback_hint,
callback_hint,
connect_url,
)
class FeaturePlanTierInline(admin.TabularInline):
model = FeaturePlanTier