Compare commits

..

No commits in common. "c90f8edc5a517ca95568c7863dbfe168005e5bda" and "7a2e5af9b78dc1adad623e8b1caf08fc0c5a3816" have entirely different histories.

4 changed files with 245 additions and 506 deletions

View File

@ -25,7 +25,6 @@ from api import routing
from inventory.notifications.sse import NotificationSSEApp from inventory.notifications.sse import NotificationSSEApp
from django.urls import re_path from django.urls import re_path
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
# application = ProtocolTypeRouter( # application = ProtocolTypeRouter(
# { # {
@ -33,9 +32,6 @@ from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
# # "websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)), # # "websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
# } # }
# ) # )
app = get_asgi_application()
application = ProtocolTypeRouter( application = ProtocolTypeRouter(
{ {
"http": AuthMiddlewareStack( "http": AuthMiddlewareStack(
@ -43,13 +39,10 @@ application = ProtocolTypeRouter(
[ [
path("sse/notifications/", NotificationSSEApp()), path("sse/notifications/", NotificationSSEApp()),
re_path( re_path(
r"", app r"", get_asgi_application()
), # All other routes go to Django ), # All other routes go to Django
] ]
) )
), ),
} }
) )
if django.conf.settings.DEBUG:
application = ASGIStaticFilesHandler(app)

View File

@ -1220,11 +1220,11 @@ urlpatterns = [
views.permenant_delete_account, views.permenant_delete_account,
name="permenant_delete_account", name="permenant_delete_account",
), ),
# path( path(
# "<slug:dealer_slug>/management/audit_log_dashboard/", "<slug:dealer_slug>/management/audit_log_dashboard/",
# views.AuditLogDashboardView, views.AuditLogDashboardView,
# name="audit_log_dashboard", name="audit_log_dashboard",
# ), ),
######### #########
# Purchase Order # Purchase Order
path( path(

View File

@ -1909,22 +1909,23 @@ def create_make_accounts(dealer):
) )
def handle_payment(request, dealer): def handle_payment(request, order):
url = "https://api.moyasar.com/v1/payments" url = "https://api.moyasar.com/v1/payments"
callback_url = request.build_absolute_uri( callback_url = request.build_absolute_uri(
reverse("payment_callback", kwargs={"dealer_slug": dealer.slug}) reverse("payment_callback", kwargs={"dealer_slug": request.dealer.slug})
) )
email = request.POST.get("email") if request.user.is_authenticated:
first_name = request.POST.get("first_name") email = request.POST["email"]
last_name = request.POST.get("last_name") first_name = request.POST["first_name"]
phone = request.POST.get("phone") last_name = request.POST["last_name"]
card_name = request.POST.get("card_name") phone = request.POST["phone"]
card_number = str(request.POST.get("card_number", "")).replace(" ", "").strip()
expiry = request.POST.get("card_expiry", "").split("/") card_name = request.POST["card_name"]
month = int(expiry[0].strip()) if len(expiry) > 0 else 0 card_number = str(request.POST["card_number"]).replace(" ", "").strip()
year = int(expiry[1].strip()) if len(expiry) > 1 else 0 month = int(request.POST["card_expiry"].split("/")[0].strip())
cvv = request.POST.get("card_cvv") year = int(request.POST["card_expiry"].split("/")[1].strip())
cvv = request.POST["card_cvv"]
user_data = { user_data = {
"email": email, "email": email,
@ -1932,166 +1933,65 @@ def handle_payment(request, dealer):
"last_name": last_name, "last_name": last_name,
"phone": phone, "phone": phone,
} }
try:
# Get selected plan from session total = int((order.total() + order.tax * order.total() / 100) * 100)
selected_plan_id = request.session.get('pending_plan_id') except (AttributeError, TypeError):
if not selected_plan_id: raise ValueError("Order total or tax is invalid")
raise ValueError("No pending plan found in session") payload = json.dumps(
from plans.models import PlanPricing {
"amount": total,
pp = PlanPricing.objects.get(pk=selected_plan_id) "currency": "SAR",
# Calculate amount without creating order "description": f"payment issued for {email}",
amount_sar = pp.price # assuming price is in SAR "callback_url": callback_url,
tax_amount = amount_sar * 15 / 100 "source": {
total = int((amount_sar + tax_amount) * 100) # convert to halalas "type": "creditcard",
"name": card_name,
# Pass plan & dealer info via metadata "number": card_number,
metadata = { "month": month,
**user_data, "year": year,
"plan_pricing_id": selected_plan_id, "cvc": cvv,
"dealer_slug": dealer.slug, "statement_descriptor": "Century Store",
} "3ds": True,
"manual": False,
payload = json.dumps({ "save_card": False,
"amount": total, },
"currency": "SAR", "metadata": user_data,
"description": f"Payment for plan {pp.plan.name}", }
"callback_url": callback_url, )
"source": {
"type": "creditcard",
"name": card_name,
"number": card_number,
"month": month,
"year": year,
"cvc": cvv,
"statement_descriptor": "Century Store",
"3ds": True,
"manual": False,
"save_card": False,
},
"metadata": metadata,
})
headers = {"Content-Type": "application/json", "Accept": "application/json"} headers = {"Content-Type": "application/json", "Accept": "application/json"}
auth = (settings.MOYASAR_SECRET_KEY, "") auth = (settings.MOYASAR_SECRET_KEY, "")
response = requests.post(url, auth=auth, headers=headers, data=payload) response = requests.request("POST", url, auth=auth, headers=headers, data=payload)
if response.status_code != 201: if response.status_code == 400:
logger.error(f"Payment initiation failed: {response.text}") data = response.json()
return None, response.get("message") if data["type"] == "validation_error":
errors = data.get("errors", {})
if "source.year" in errors:
raise Exception("Invalid expiry year")
else:
raise Exception("Validation Error: ", errors)
else:
print("Failed to process payment:", data)
#
data = response.json() data = response.json()
# Create PaymentHistory WITHOUT linking to order (since order doesn't exist yet) # order.status = AbstractOrder.STATUS.NEW
amount_decimal = Decimal("{0:.2f}".format(Decimal(total) / Decimal(100))) order.save()
#
amount = Decimal("{0:.2f}".format(Decimal(total) / Decimal(100)))
models.PaymentHistory.objects.create( models.PaymentHistory.objects.create(
user=request.user, user=request.user,
user_data=json.dumps(metadata), user_data=user_data,
amount=amount_decimal, amount=amount,
currency=data["currency"], currency=data["currency"],
status=data["status"], status=data["status"],
transaction_id=data["id"], transaction_id=data["id"],
payment_date=data["created_at"], payment_date=data["created_at"],
gateway_response=data, gateway_response=data,
) )
logger.info(f"Payment initiated: {data}") transaction_url = data["source"]["transaction_url"]
return data["source"]["transaction_url"],None return transaction_url
# def handle_payment(request, order):
# logger.info(f"Handling payment for order {order}")
# url = "https://api.moyasar.com/v1/payments"
# callback_url = request.build_absolute_uri(
# reverse("payment_callback", kwargs={"dealer_slug": request.dealer.slug})
# )
# logger.info(f"Got callback_url: {callback_url}")
# if request.user.is_authenticated:
# email = request.POST["email"]
# first_name = request.POST["first_name"]
# last_name = request.POST["last_name"]
# phone = request.POST["phone"]
# logger.info(f"Got user data: {email}, {first_name}, {last_name}, {phone}")
# card_name = request.POST["card_name"]
# card_number = str(request.POST["card_number"]).replace(" ", "").strip()
# month = int(request.POST["card_expiry"].split("/")[0].strip())
# year = int(request.POST["card_expiry"].split("/")[1].strip())
# cvv = request.POST["card_cvv"]
# logger.info(f"Got card data: {card_name}, {card_number[:4]}****, {month}, {year}, {cvv}")
# user_data = {
# "email": email,
# "first_name": first_name,
# "last_name": last_name,
# "phone": phone,
# }
# try:
# total = int((order.total() + order.tax * order.total() / 100) * 100)
# except (AttributeError, TypeError):
# raise ValueError("Order total or tax is invalid")
# logger.info(f"Calculated total: {total}")
# payload = json.dumps(
# {
# "amount": total,
# "currency": "SAR",
# "description": f"payment issued for {email}",
# "callback_url": callback_url,
# "source": {
# "type": "creditcard",
# "name": card_name,
# "number": card_number,
# "month": month,
# "year": year,
# "cvc": cvv,
# "statement_descriptor": "Century Store",
# "3ds": False,
# "manual": False,
# "save_card": False,
# },
# "metadata": user_data,
# }
# )
# logger.info(f"Generated payload: {payload}")
# headers = {"Content-Type": "application/json", "Accept": "application/json"}
# auth = (settings.MOYASAR_SECRET_KEY, "")
# response = requests.request("POST", url, auth=auth, headers=headers, data=payload)
# logger.info(f"Sent request to Moyasar API: {response.status_code}")
# if response.status_code == 400:
# data = response.json()
# if data["type"] == "validation_error":
# errors = data.get("errors", {})
# if "source.year" in errors:
# raise Exception("Invalid expiry year")
# else:
# raise Exception("Validation Error: ", errors)
# else:
# logger.error(f"Failed to process payment: {data}")
# #
# data = response.json()
# # order.status = AbstractOrder.STATUS.NEW
# order.save()
# #
# amount = Decimal("{0:.2f}".format(Decimal(total) / Decimal(100)))
# models.PaymentHistory.objects.create(
# user=request.user,
# user_data=user_data,
# amount=amount,
# currency=data["currency"],
# status=data["status"],
# transaction_id=data["id"],
# payment_date=data["created_at"],
# gateway_response=data,
# )
# transaction_url = data["source"]["transaction_url"]
# logger.info(f"Created payment history and got transaction_url: {transaction_url}")
# return transaction_url
# def get_user_quota(user): # def get_user_quota(user):

View File

@ -218,7 +218,7 @@ from .utils import (
from .tasks import create_accounts_for_make, create_user_dealer, send_email from .tasks import create_accounts_for_make, create_user_dealer, send_email
# djago easy audit log # djago easy audit log
# from easyaudit.models import RequestEvent, CRUDEvent, LoginEvent from easyaudit.models import RequestEvent, CRUDEvent, LoginEvent
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@ -504,7 +504,6 @@ def general_dashboard(request,dealer_slug):
total_vat_collected = total_vat_collected_from_cars + total_vat_collected_from_services total_vat_collected = total_vat_collected_from_cars + total_vat_collected_from_services
total_revenue_generated = total_revenue_from_cars + total_revenue_from_services total_revenue_generated = total_revenue_from_cars + total_revenue_from_services
# total_expenses=sum([x.amount_paid for x in dealer.entity.get_bills().filter(bill_items__item_role="expense")]) # total_expenses=sum([x.amount_paid for x in dealer.entity.get_bills().filter(bill_items__item_role="expense")])
total_expenses=dealer.entity.get_bills().filter(bill_items__item_role="expense").aggregate(total=Sum('amount_paid'))['total'] or 0 total_expenses=dealer.entity.get_bills().filter(bill_items__item_role="expense").aggregate(total=Sum('amount_paid'))['total'] or 0
gross_profit = net_profit_from_cars+total_revenue_from_services - total_expenses gross_profit = net_profit_from_cars+total_revenue_from_services - total_expenses
@ -9890,123 +9889,50 @@ def pricing_page(request, dealer_slug):
# @login_required @login_required
# @permission_required("inventory.change_dealer", raise_exception=True) @permission_required("inventory.change_dealer", raise_exception=True)
# @require_POST @require_POST
# def submit_plan(request, dealer_slug):
# logger.info(f"dealer slug : {dealer_slug}")
# if request.method == "GET":
# logger.info("method is GET, redirecting to pricing page")
# return redirect("pricing_page", dealer_slug=dealer_slug)
# dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
# logger.info(f"Selected dealer : {dealer}")
# selected_plan_id = request.POST.get("selected_plan")
# logger.info(f"Selected plan id : {selected_plan_id}")
# pp = PlanPricing.objects.get(pk=selected_plan_id)
# logger.info(f"Selected plan pricing : {pp}")
# order = None
# try:
# order = Order.objects.create(
# user=dealer.user,
# plan=pp.plan,
# pricing=pp.pricing,
# amount=pp.price,
# currency="SA",
# tax=15,
# status=1,
# )
# logger.info(f"order created {order}")
# except Exception as e:
# logger.error(e)
# if not order:
# messages.error(request, _("Error creating order"))
# logger.error("unable to create order")
# return redirect("pricing_page", dealer_slug=dealer_slug)
# transaction_url = handle_payment(request, order)
# logger.info(f"Redirecting to : {transaction_url}")
# return redirect(transaction_url)
def submit_plan(request, dealer_slug): def submit_plan(request, dealer_slug):
if request.method == "GET":
return redirect("pricing_page", dealer_slug=dealer_slug)
dealer = get_object_or_404(models.Dealer, slug=dealer_slug) dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
selected_plan_id = request.POST.get("selected_plan") selected_plan_id = request.POST.get("selected_plan")
if not selected_plan_id: pp = PlanPricing.objects.get(pk=selected_plan_id)
messages.error(request, _("No plan selected.")) order = None
try:
order = Order.objects.create(
user=dealer.user,
plan=pp.plan,
pricing=pp.pricing,
amount=pp.price,
currency="SA",
tax=15,
status=1,
)
logger.info(f"order created {order}")
except Exception as e:
logger.error(e)
if not order:
messages.error(request, _("Error creating order"))
logger.error("unable to create order")
return redirect("pricing_page", dealer_slug=dealer_slug) return redirect("pricing_page", dealer_slug=dealer_slug)
transaction_url = handle_payment(request, order)
# Store plan & dealer info in session for use in callback
request.session['pending_plan_id'] = selected_plan_id
request.session['pending_dealer_slug'] = dealer_slug
# Initiate payment WITHOUT creating order
transaction_url,error = handle_payment(request, dealer)
if not transaction_url:
messages.error(request, _(f"Payment initiation failed. {error}"))
return redirect("pricing_page", dealer_slug=dealer_slug)
return redirect(transaction_url) return redirect(transaction_url)
# @login_required @login_required
@permission_required("inventory.change_dealer", raise_exception=True)
def payment_callback(request, dealer_slug): def payment_callback(request, dealer_slug):
message = request.GET.get("message")
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
payment_id = request.GET.get("id") payment_id = request.GET.get("id")
payment_status = request.GET.get("status")
message = request.GET.get("message", "")
logger.info(f"Received payment callback for dealer_slug: {dealer_slug}, payment_id: {payment_id}, status: {payment_status}")
history = models.PaymentHistory.objects.filter(transaction_id=payment_id).first() history = models.PaymentHistory.objects.filter(transaction_id=payment_id).first()
if not history: payment_status = request.GET.get("status")
logger.error(f"No PaymentHistory found for transaction_id: {payment_id}") logger.info(f"Received payment callback for dealer_slug: {dealer_slug}, payment_id: {payment_id}, status: {payment_status}")
return render(request, "payment_failed.html", {"message": "Invalid transaction"}) order = Order.objects.filter(user=dealer.user, status=1).first() # Status 1 = NEW
if history.status == "paid": if history.status == "paid":
logger.info("Payment already processed. Redirecting to home.")
return redirect('home') return redirect('home')
if payment_status == "paid": if payment_status == "paid":
logger.info(f"Payment successful for transaction ID {payment_id}. Creating order...") logger.info(f"Payment successful for transaction ID {payment_id}. Processing order completion.")
# Get metadata from PaymentHistory (passed during handle_payment)
metadata = history.user_data
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError:
logger.error(f"Failed to decode metadata JSON: {metadata}")
metadata = {}
plan_pricing_id = metadata.get("plan_pricing_id")
dealer_slug_from_meta = metadata.get("dealer_slug")
if not plan_pricing_id or dealer_slug_from_meta != dealer_slug:
logger.error("Invalid metadata in payment callback")
history.status = "failed"
history.save()
return render(request, "payment_failed.html", {"message": "Invalid payment data"})
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
pp = get_object_or_404(PlanPricing, pk=plan_pricing_id)
# ✅ CREATE ORDER HERE
try:
order = Order.objects.create(
user=dealer.user,
plan=pp.plan,
pricing=pp.pricing,
amount=pp.price,
currency="SAR", # Fixed typo: was "SA"
tax=15,
status=Order.STATUS.NEW, # Use constant if available
)
logger.info(f"Order {order.id} created for user {dealer.user}")
except Exception as e:
logger.exception(f"Failed to create order: {e}")
history.status = "failed"
history.save()
return render(request, "payment_failed.html", {"message": "Order creation failed"})
# Create or get BillingInfo
billing_info, created = BillingInfo.objects.get_or_create( billing_info, created = BillingInfo.objects.get_or_create(
user=dealer.user, user=dealer.user,
defaults={ defaults={
@ -10023,8 +9949,8 @@ def payment_callback(request, dealer_slug):
else: else:
logger.debug(f"Billing info already exists for user {dealer.user}.") logger.debug(f"Billing info already exists for user {dealer.user}.")
# Create or update UserPlan
if not hasattr(order.user, 'userplan'): if not hasattr(order.user, 'userplan'):
print(order.get_plan_pricing().pricing.period)
UserPlan.objects.create( UserPlan.objects.create(
user=order.user, user=order.user,
plan=order.plan, plan=order.plan,
@ -10032,22 +9958,26 @@ def payment_callback(request, dealer_slug):
) )
logger.info(f"Created new UserPlan for user {order.user} with plan {order.plan}.") logger.info(f"Created new UserPlan for user {order.user} with plan {order.plan}.")
else: else:
# Optional: upgrade existing plan
# user_plan = order.user.userplan
# user_plan.plan = order.plan
# user_plan.save()
logger.info(f"UserPlan already exists for user {order.user}.") logger.info(f"UserPlan already exists for user {order.user}.")
try: try:
# Complete the order (this may generate invoice, etc.)
# if order.user.userplan:
# user = order.user
# pricing = order.get_plan_pricing().pricing
# logger.info(f"Processing order completion for {user} - upgrading to {order.plan}")
# user.userplan.plan = order.plan
# user.userplan.expire = datetime.now() + timedelta(days=pricing.period)
# user.userplan.save()
# user.save()
# logger.info(f"User {user} upgraded to {order.plan} plan successfully")
order.complete_order() order.complete_order()
history.status = "paid" history.status = "paid"
history.order = order # Link payment to order
history.save() history.save()
logger.info(f"Order {order.id} for user {order.user} completed successfully. Payment history updated.")
invoice = order.get_invoices().first() invoice = order.get_invoices().first()
logger.info(f"Order {order.id} completed. Rendering success page.")
return render( return render(
request, request,
"payment_success.html", "payment_success.html",
@ -10055,14 +9985,13 @@ def payment_callback(request, dealer_slug):
) )
except Exception as e: except Exception as e:
logger.exception(f"Error completing order {order.id}: {e}") logger.exception(f"Error completing order {order.id} for user {order.user}: {e}")
logger.error(f"Plan activation failed: {str(e)}")
history.status = "failed" history.status = "failed"
history.save() history.save()
return render(request, "payment_failed.html", {"message": "Plan activation error"}) return render(request, "payment_failed.html", {"message": "Plan activation error"})
finally: finally:
# Activate dealer & staff if needed if dealer := getattr(order.user,"dealer", None):
if dealer := getattr(order.user, "dealer", None):
if not dealer.user.is_active: if not dealer.user.is_active:
dealer.user.is_active = True dealer.user.is_active = True
dealer.user.save() dealer.user.save()
@ -10070,6 +9999,7 @@ def payment_callback(request, dealer_slug):
if not staff.user.is_active: if not staff.user.is_active:
staff.activate_account() staff.activate_account()
logger.info(f"Order {order.id} for user {order.user} completed successfully. Payment history updated.")
elif payment_status == "failed": elif payment_status == "failed":
logger.warning(f"Payment failed for transaction ID {payment_id}. Message: {message}") logger.warning(f"Payment failed for transaction ID {payment_id}. Message: {message}")
history.status = "failed" history.status = "failed"
@ -10077,90 +10007,6 @@ def payment_callback(request, dealer_slug):
return render(request, "payment_failed.html", {"message": message}) return render(request, "payment_failed.html", {"message": message})
return render(request, "payment_failed.html", {"message": "Unknown payment status"}) return render(request, "payment_failed.html", {"message": "Unknown payment status"})
# @login_required
# @permission_required("inventory.change_dealer", raise_exception=True)
# def payment_callback(request, dealer_slug):
# message = request.GET.get("message")
# dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
# payment_id = request.GET.get("id")
# history = models.PaymentHistory.objects.filter(transaction_id=payment_id).first()
# payment_status = request.GET.get("status")
# logger.info(f"Received payment callback for dealer_slug: {dealer_slug}, payment_id: {payment_id}, status: {payment_status}")
# order = Order.objects.filter(user=dealer.user, status=1).first() # Status 1 = NEW
# logger.info(f"Retrieved order {order.id} for user {order.user}.")
# if history.status == "paid":
# logger.info(f"Payment history already marked as paid for transaction ID {payment_id}. Redirecting to home.")
# return redirect('home')
# if payment_status == "paid":
# logger.info(f"Payment successful for transaction ID {payment_id}. Processing order completion.")
# billing_info, created = BillingInfo.objects.get_or_create(
# user=dealer.user,
# defaults={
# 'tax_number': dealer.vrn,
# 'name': dealer.arabic_name,
# 'street': dealer.address,
# 'zipcode': dealer.entity.zip_code or " ",
# 'city': dealer.entity.city or " ",
# 'country': dealer.entity.country or " ",
# }
# )
# if created:
# logger.info(f"Created new billing info for user {dealer.user}.")
# else:
# logger.debug(f"Billing info already exists for user {dealer.user}.")
# if not hasattr(order.user, 'userplan'):
# logger.info(f"Creating new UserPlan for user {order.user} with plan {order.plan}.")
# UserPlan.objects.create(
# user=order.user,
# plan=order.plan,
# # expire=datetime.now().date() + timedelta(days=order.get_plan_pricing().pricing.period)
# )
# else:
# logger.info(f"UserPlan already exists for user {order.user}.")
# try:
# logger.info(f"Processing order completion for {order.user} - upgrading to {order.plan}")
# order.complete_order()
# history.status = "paid"
# history.save()
# logger.info(f"Order {order.id} for user {order.user} completed successfully. Payment history updated.")
# invoice = order.get_invoices().first()
# logger.info(f"Redirecting to payment success page with invoice {invoice.id}.")
# return render(
# request,
# "payment_success.html",
# {"order": order, "invoice": invoice}
# )
# except Exception as e:
# logger.exception(f"Error completing order {order.id} for user {order.user}: {e}")
# logger.error(f"Plan activation failed: {str(e)}")
# history.status = "failed"
# history.save()
# logger.info(f"Redirecting to payment failed page with message {message}.")
# return render(request, "payment_failed.html", {"message": "Plan activation error"})
# finally:
# if dealer := getattr(order.user,"dealer", None):
# logger.info(f"Activating dealer {dealer} and its staff.")
# if not dealer.user.is_active:
# logger.info(f"Activating dealer {dealer}.")
# dealer.user.is_active = True
# dealer.user.save()
# for staff in dealer.get_staff():
# logger.info(f"Activating staff {staff}.")
# if not staff.user.is_active:
# staff.activate_account()
# logger.info(f"Order {order.id} for user {order.user} completed successfully. Payment history updated.")
# elif payment_status == "failed":
# logger.warning(f"Payment failed for transaction ID {payment_id}. Message: {message}")
# history.status = "failed"
# history.save()
# logger.info(f"Redirecting to payment failed page with message {message}.")
# return render(request, "payment_failed.html", {"message": message})
# logger.info(f"Redirecting to payment failed page with message {message}.")
# return render(request, "payment_failed.html", {"message": "Unknown payment status"})
# def payment_callback(request, dealer_slug): # def payment_callback(request, dealer_slug):
# message = request.GET.get("message") # message = request.GET.get("message")
# dealer = get_object_or_404(models.Dealer, slug=dealer_slug) # dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
@ -10504,140 +10350,140 @@ def user_management(request, dealer_slug):
return render(request, "admin_management/user_management.html", context) return render(request, "admin_management/user_management.html", context)
# @login_required @login_required
# @permission_required("inventory.change_dealer", raise_exception=True) @permission_required("inventory.change_dealer", raise_exception=True)
# def AuditLogDashboardView(request, dealer_slug): def AuditLogDashboardView(request, dealer_slug):
# """ """
# Displays audit logs (User Actions, Login Events, Request Events) with pagination. Displays audit logs (User Actions, Login Events, Request Events) with pagination.
# Log type is determined by the 'q' query parameter (e.g., ?q=userActions). Log type is determined by the 'q' query parameter (e.g., ?q=userActions).
# Pagination page number is passed as a query parameter (e.g., ?page=2). Pagination page number is passed as a query parameter (e.g., ?page=2).
# """ """
# get_object_or_404(models.Dealer, slug=dealer_slug) get_object_or_404(models.Dealer, slug=dealer_slug)
# q = request.GET.get("q") # Get the log type from the 'q' query parameter q = request.GET.get("q") # Get the log type from the 'q' query parameter
# current_pagination_page = request.GET.get("page", 1) current_pagination_page = request.GET.get("page", 1)
# context = {} context = {}
# template_name = None template_name = None
# logs_per_page = 30 # Define logs per page once logs_per_page = 30 # Define logs per page once
# # --- Determine Data Source and Template based on 'q' parameter --- # --- Determine Data Source and Template based on 'q' parameter ---
# if ( if (
# q == "userRequests" q == "userRequests"
# ): # This block handles cases where 'q' is 'requestEvents', None, or any other invalid value. ): # This block handles cases where 'q' is 'requestEvents', None, or any other invalid value.
# # It defaults to Request Logs if 'q' is not 'userActions' or 'loginEvents'. # It defaults to Request Logs if 'q' is not 'userActions' or 'loginEvents'.
# template_name = "admin_management/request_logs.html" template_name = "admin_management/request_logs.html"
# context["title"] = "Request Logs Dashboard" context["title"] = "Request Logs Dashboard"
# request_events = RequestEvent.objects.all().order_by("-datetime") request_events = RequestEvent.objects.all().order_by("-datetime")
# paginator = Paginator(request_events, logs_per_page) paginator = Paginator(request_events, logs_per_page)
# try: try:
# page_obj = paginator.page(current_pagination_page) page_obj = paginator.page(current_pagination_page)
# except PageNotAnInteger: except PageNotAnInteger:
# page_obj = paginator.page(1) page_obj = paginator.page(1)
# except EmptyPage: except EmptyPage:
# page_obj = paginator.page(paginator.num_pages) page_obj = paginator.page(paginator.num_pages)
# elif q == "loginEvents": elif q == "loginEvents":
# template_name = "admin_management/auth_logs.html" template_name = "admin_management/auth_logs.html"
# context["title"] = "Login Events Dashboard" context["title"] = "Login Events Dashboard"
# auth_events = LoginEvent.objects.all().order_by("-datetime") auth_events = LoginEvent.objects.all().order_by("-datetime")
# paginator = Paginator(auth_events, logs_per_page) paginator = Paginator(auth_events, logs_per_page)
# try: try:
# page_obj = paginator.page(current_pagination_page) page_obj = paginator.page(current_pagination_page)
# except PageNotAnInteger: except PageNotAnInteger:
# page_obj = paginator.page(1) page_obj = paginator.page(1)
# except EmptyPage: except EmptyPage:
# page_obj = paginator.page(paginator.num_pages) page_obj = paginator.page(paginator.num_pages)
# else: else:
# template_name = "admin_management/model_logs.html" template_name = "admin_management/model_logs.html"
# context["title"] = "User Actions Dashboard" context["title"] = "User Actions Dashboard"
# # OPTIMIZATION: Get the QuerySet but don't evaluate it yet # OPTIMIZATION: Get the QuerySet but don't evaluate it yet
# model_events_queryset = CRUDEvent.objects.all().order_by("-datetime") model_events_queryset = CRUDEvent.objects.all().order_by("-datetime")
# # 1. Paginate the raw QuerySet FIRST # 1. Paginate the raw QuerySet FIRST
# paginator = Paginator(model_events_queryset, logs_per_page) paginator = Paginator(model_events_queryset, logs_per_page)
# try: try:
# # Get the page object, which contains only the raw QuerySet objects for the current page # Get the page object, which contains only the raw QuerySet objects for the current page
# page_obj_raw = paginator.page(current_pagination_page) page_obj_raw = paginator.page(current_pagination_page)
# except PageNotAnInteger: except PageNotAnInteger:
# page_obj_raw = paginator.page(1) page_obj_raw = paginator.page(1)
# except EmptyPage: except EmptyPage:
# page_obj_raw = paginator.page(paginator.num_pages) page_obj_raw = paginator.page(paginator.num_pages)
# # 2. Now, process 'field_changes' ONLY for the events on the current page # 2. Now, process 'field_changes' ONLY for the events on the current page
# processed_model_events_for_page = [] processed_model_events_for_page = []
# for ( for (
# event event
# ) in page_obj_raw.object_list: # Loop only through the current page's items ) in page_obj_raw.object_list: # Loop only through the current page's items
# event_data = { event_data = {
# "datetime": event.datetime, "datetime": event.datetime,
# "user": event.user, "user": event.user,
# "event_type_display": event.get_event_type_display(), "event_type_display": event.get_event_type_display(),
# "model_name": event.content_type.model, "model_name": event.content_type.model,
# "object_id": event.object_id, "object_id": event.object_id,
# "object_repr": event.object_repr, "object_repr": event.object_repr,
# "field_changes": [], "field_changes": [],
# } }
# if event.changed_fields: if event.changed_fields:
# try: try:
# changes = json.loads(event.changed_fields) changes = json.loads(event.changed_fields)
# if isinstance(changes, dict): if isinstance(changes, dict):
# for field_name, values in changes.items(): for field_name, values in changes.items():
# old_value = ( old_value = (
# values[0] values[0]
# if isinstance(values, list) and len(values) > 0 if isinstance(values, list) and len(values) > 0
# else None else None
# ) )
# new_value = ( new_value = (
# values[1] values[1]
# if isinstance(values, list) and len(values) > 1 if isinstance(values, list) and len(values) > 1
# else None else None
# ) )
# event_data["field_changes"].append( event_data["field_changes"].append(
# { {
# "field": field_name, "field": field_name,
# "old": old_value, "old": old_value,
# "new": new_value, "new": new_value,
# } }
# ) )
# elif changes is None: elif changes is None:
# event_data["field_changes"].append( event_data["field_changes"].append(
# { {
# "field": "Info", "field": "Info",
# "old": "", "old": "",
# "new": "No specific field changes recorded (JSON was null)", "new": "No specific field changes recorded (JSON was null)",
# } }
# ) )
# else: # Handle valid JSON but not a dictionary (e.g., "[]", 123) else: # Handle valid JSON but not a dictionary (e.g., "[]", 123)
# event_data["field_changes"].append( event_data["field_changes"].append(
# { {
# "field": "Error", "field": "Error",
# "old": "", "old": "",
# "new": f"Unexpected JSON format: {type(changes).__name__}", "new": f"Unexpected JSON format: {type(changes).__name__}",
# } }
# ) )
# except json.JSONDecodeError: except json.JSONDecodeError:
# # Handle invalid JSON; you might log this error # Handle invalid JSON; you might log this error
# event_data["field_changes"].append( event_data["field_changes"].append(
# { {
# "field": "Error", "field": "Error",
# "old": "", "old": "",
# "new": "Invalid JSON in changed_fields", "new": "Invalid JSON in changed_fields",
# } }
# ) )
# processed_model_events_for_page.append(event_data) processed_model_events_for_page.append(event_data)
# # 3. Replace the object_list of the original page_obj with the processed data # 3. Replace the object_list of the original page_obj with the processed data
# # This keeps all pagination properties (has_next, number, etc.) intact. # This keeps all pagination properties (has_next, number, etc.) intact.
# page_obj_raw.object_list = processed_model_events_for_page page_obj_raw.object_list = processed_model_events_for_page
# page_obj = page_obj_raw # This will be passed to the context page_obj = page_obj_raw # This will be passed to the context
# # Pass the final page object to the context # Pass the final page object to the context
# context["page_obj"] = page_obj context["page_obj"] = page_obj
# return render(request, template_name, context) return render(request, template_name, context)
@login_required @login_required