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() def changelist_view(self, request, extra_context=None): 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( "

{}

" "

Redirect URI برای ثبت در پیشخان (اگر می‌خواهی OAuth را وصل کنی) — این مسیر را با " "آدرس دامنه‌ی سرورت جلو ببر، مثلا https://api.example.com{} :

" "

{}

" '

شروع اتصال به کافه‌بازار (OAuth)

', status_line, callback_hint, callback_hint, connect_url, ) class FeaturePlanTierInline(admin.TabularInline): model = FeaturePlanTier extra = 1 @admin.register(FeaturePlan) class FeaturePlanAdmin(admin.ModelAdmin): list_display = ("name", "key", "is_purchasable") search_fields = ("name", "key") inlines = [FeaturePlanTierInline] @admin.register(GemPack) class GemPackAdmin(admin.ModelAdmin): list_display = ("display_name", "bazaar_sku", "gem_amount", "is_active", "sort_order") list_editable = ("is_active", "sort_order") search_fields = ("display_name", "bazaar_sku") @admin.register(FeatureActivation) class FeatureActivationAdmin(admin.ModelAdmin): list_display = ("user", "feature", "expires_at", "updated_at") list_filter = ("feature",) search_fields = ("user__phone",) autocomplete_fields = ["user"] @admin.register(GemTransaction) class GemTransactionAdmin(admin.ModelAdmin): list_display = ("user", "amount", "reason", "created_at") list_filter = ("reason",) search_fields = ("user__phone", "note") readonly_fields = ("user", "amount", "reason", "note", "created_at") def has_add_permission(self, request): # تراکنش جم فقط از سرویس‌های داخلی ساخته می‌شود، نه دستی از ادمین — # برای اصلاح دستی از GemTransaction.Reason.ADMIN_ADJUSTMENT در شل/سرویس استفاده کن. return False @admin.register(BazaarPurchase) class BazaarPurchaseAdmin(admin.ModelAdmin): list_display = ("user", "gem_pack", "status", "gems_granted", "created_at", "verified_at") list_filter = ("status",) search_fields = ("user__phone", "purchase_token") readonly_fields = ("purchase_token", "created_at", "verified_at")