部署至Heroku后仅结账页出现Server Error 500问题排查求助
Django结账页500错误排查求助
我近期将一个Django项目部署至Heroku,使用AWS存储媒体文件、ElephantSQL作为数据库。目前项目多数页面运行正常,但仅有结账页返回Server Error 500错误。我已尝试移动结账页、检查views.py与settings.py配置、确认allowed hosts,也对比了其他项目的结账页代码,但未找到问题所在。以下是该结账页的模板代码及视图代码,恳请各位提供排查方向:
结账页模板代码
{% extends "base.html" %} {% load static %} {% load bag_tools %} {% block extra_css %} <link rel="stylesheet" href="{% static 'checkout/css/checkout.css' %}"> {% endblock %} {% block page_header %} <div class="container header-container"> <div class="row"> <div class="col"></div> </div> </div> {% endblock %} {% block content %} <div class="overlay"></div> <div class="container"> <div class="row"> <div class="col"> <hr> <h2 class="logo-font mb-4">Checkout</h2> <hr> </div> </div> <div class="row"> <div class="col-12 col-lg-6 order-lg-last mb-5"> <p class="text-muted">Order Summary ({{ product_count }})</p> <div class="row"> <div class="col-7 offset-2"> <p class="mb-1 mt-0 small text-muted">Item</p> </div> <div class="col-3 text-right"> <p class="mb-1 mt-0 small text-muted">Subtotal</p> </div> </div> {% for item in bag_items %} <div class="row"> <div class="col-2 mb-1"> <a href="{% url 'product_detail' item.product.id %}"> {% if item.product.image %} <img class="w-100" src="{{ item.product.image.url }}" alt="{{ product.name }}"> {% else %} <img class="w-100" src="{{ MEDIA_URL }}noimage.png" alt="{{ product.name }}"> {% endif %} </a> </div> <div class="col-7"> <p class="my-0"><strong>{{ item.product.name }}</strong></p> <p class="my-0 small">Size: {% if item.product.has_sizes %}{{ item.size|upper }}{% else %}N/A{% endif %}</p> <p class="my-0 small text-muted">Qty: {{ item.quantity }}</p> </div> <div class="col-3 text-right"> <p class="my-0 small text-muted">${{ item.product.price | calc_subtotal:item.quantity }}</p> </div> </div> {% endfor %} <hr class="my-0"> <div class="row text-black text-right"> <div class="col-7 offset-2"> <p class="my-0">Order Total:</p> <p class="my-0">Delivery:</p> <p class="my-0">Grand Total:</p> </div> <div class="col-3"> <p class="my-0">${{ total | floatformat:2 }}</p> <p class="my-0">${{ delivery | floatformat:2 }}</p> <p class="my-0"><strong>${{ grand_total | floatformat:2 }}</strong></p> </div> </div> </div> <div class="col-12 col-lg-6"> <p class="text-muted">Please fill out the form below to complete your order</p> <form action="{% url 'checkout' %}" method="POST" id="payment-form"> {% csrf_token %} <fieldset class="rounded px-3 mb-5"> <legend class="fieldset-label small text-black px-2 w-auto">Details</legend> {{ order_form.full_name | as_crispy_field }} {{ order_form.email | as_crispy_field }} </fieldset> <fieldset class="rounded px-3 mb-5"> <legend class="fieldset-label small text-black px-2 w-auto">Delivery</legend> {{ order_form.phone_number | as_crispy_field }} {{ order_form.street_address1 | as_crispy_field }} {{ order_form.street_address2 | as_crispy_field }} {{ order_form.town_or_city | as_crispy_field }} {{ order_form.county | as_crispy_field }} {{ order_form.postcode | as_crispy_field }} {{ order_form.country | as_crispy_field }} <div class="form-check form-check-inline float-right mr-0"> {% if user.is_authenticated %} <label class="form-check-label" for="id-save-info">Save this delivery information to my profile</label> <input class="form-check-input ml-2 mr-0" type="checkbox" id="id-save-info" name="save-info" checked> {% else %} <label class="form-check-label" for="id-save-info"> <a class="text-info" href="{% url 'account_signup' %}">Create an account</a> or <a class="text-info" href="{% url 'account_login' %}">login</a> to save this information </label> {% endif %} </div> </fieldset> <fieldset class="px-3"> <legend class="fieldset-label small text-black px-2 w-auto">Payment</legend> <!-- A Stripe card element will go here --> <div class="mb-3" id="card-element"></div> <!-- Used to display form errors --> <div class="mb-3 text-danger" id="card-errors" role="alert"></div> <!-- Pass the client secret to the view so we can get the payment intent id --> <input type="hidden" value="{{ client_secret }}" name="client_secret"> </fieldset> <div class="submit-button text-right mt-5 mb-2"> <a href="{% url 'view_bag' %}" class="btn btn-outline-black rounded-0"> <span class="icon"> <i class="fas fa-chevron-left"></i> </span> <span class="font-weight-bold">Adjust Bag</span> </a> <button id="submit-button" class="btn btn-black rounded-0"> <span class="font-weight-bold">Complete Order</span> <span class="icon"> <i class="fas fa-lock"></i> </span> </button> <p class="small text-danger my-0"> <span class="icon"> <i class="fas fa-exclamation-circle"></i> </span> <span>Your card will be charged <strong>${{ grand_total|floatformat:2 }}</strong></span> </p> </div> </form> </div> </div> </div> <div id="loading-overlay"> <h1 class="text-light logo-font loading-spinner"> <span class="icon"> <i class="fas fa-3x fa-sync-alt fa-spin"></i> </span> </h1> </div> {% endblock %} {% block postloadjs %} {{ block.super }} {{ stripe_public_key|json_script:"id_stripe_public_key" }} {{ client_secret|json_script:"id_client_secret" }} <script src="{% static 'checkout/js/stripe_elements.js' %}"></script> {% endblock %}
结账页视图代码
from django.shortcuts import render, redirect, reverse, get_object_or_404, HttpResponse from django.views.decorators.http import require_POST from django.contrib import messages from django.conf import settings from .forms import OrderForm from .models import Order, OrderLineItem from products.models import Product from profiles.models import UserProfile from profiles.forms import UserProfileForm from bag.contexts import bag_contents import stripe import json @require_POST def cache_checkout_data(request): try: pid = request.POST.get('client_secret').split('_secret')[0] stripe.api_key = settings.STRIPE_SECRET_KEY stripe.PaymentIntent.modify(pid, metadata={ 'bag': json.dumps(request.session.get('bag', {})), 'save_info': request.POST.get('save_info'), 'username': request.user, }) return HttpResponse(status=200) except Exception as e: messages.error(request, 'Sorry, your payment cannot be \ processed right now. Please try again later.') return HttpResponse(content=e, status=400) def checkout(request): stripe_public_key = settings.STRIPE_PUBLIC_KEY stripe_secret_key = settings.STRIPE_SECRET_KEY if request.method == 'POST': bag = request.session.get('bag', {}) form_data = { 'full_name': request.POST['full_name'], 'email': request.POST['email'], 'phone_number': request.POST['phone_number'], 'country': request.POST['country'], 'postcode': request.POST['postcode'], 'town_or_city': request.POST['town_or_city'], 'street_address1': request.POST['street_address1'], 'street_address2': request.POST['street_address2'], 'county': request.POST['county'], } order_form = OrderForm(form_data) if order_form.is_valid(): order = order_form.save(commit=False) pid = request.POST.get('client_secret').split('_secret')[0] order.stripe_pid = pid order.original_bag = json.dumps(bag) order.save() for item_id, item_data in bag.items(): try: product = Product.objects.get(id=item_id) if isinstance(item_data, int): order_line_item = OrderLineItem( order=order, product=product, quantity=item_data, ) order_line_item.save() else: for size, quantity in item_data['items_by_size'].items(): order_line_item = OrderLineItem( order=order, product=product, quantity=quantity, product_size=size, ) order_line_item.save() except Product.DoesNotExist: messages.error(request, ( "One of the products in your bag wasn't found in our database. " "Please call us for assistance!") ) order.delete() return redirect(reverse('view_bag')) # Save the info to the user's profile if all is well request.session['save_info'] = 'save-info' in request.POST return redirect(reverse('checkout_success', args=[order.order_number])) else: messages.error(request, 'There was an error with your form. \ Please double check your information.') else: bag = request.session.get('bag', {}) if not bag: messages.error(request, "There's nothing in your bag at the moment") return redirect(reverse('products')) current_bag = bag_contents(request) total = current_bag['grand_total'] stripe_total = round(total * 100) stripe.api_key = stripe_secret_key intent = stripe.PaymentIntent.create( amount=stripe_total, currency=settings.STRIPE_CURRENCY, ) # Attempt to prefill the form with any info the user maintains in their profile if request.user.is_authenticated: try: profile = UserProfile.objects.get(user=request.user) order_form = OrderForm(initial={ 'full_name': profile.user.get_full_name(), 'email': profile.user.email, 'phone_number': profile.default_phone_number, 'country': profile.default_country, 'postcode': profile.default_postcode, 'town_or_city': profile.default_town_or_city, 'street_address1': profile.default_street_address1, 'street_address2': profile.default_street_address2, 'county': profile.default_county, }) except UserProfile.DoesNotExist: order_form = OrderForm() else: order_form = OrderForm() if not stripe_public_key: messages.warning(request, 'Stripe public key is missing. \ Did you forget to set it in your environment?') template = 'checkout/checkout.html' context = { 'order_form': order_form, 'stripe_public_key': stripe_public_key, 'client_secret': intent.client_secret, } return render(request, template, context) def checkout_success(request, order_number): """ Handle successful checkouts """ save_info = request.session.get('save_info') order = get_object_or_404(Order, order_number=order_number) if request.user.is_authenticated: profile = UserProfile.objects.get(user=request.user) # Attach the user's profile to the order order.user_profile = profile order.save() # Save the user's info if save_info: profile_data = { 'default_phone_number': order.phone_number, 'default_country': order.country, 'default_postcode': order.postcode, 'default_town_or_city': order.town_or_city, 'default_street_address1': order.street_address1, 'default_street_address2': order.street_address2, 'default_county': order.county, } user_profile_form = UserProfileForm(profile_data, instance=profile) if user_profile_form.is_valid(): user_profile_form.save() messages.success(request, f'Order successfully processed! \ Your order number is {order_number}. A confirmation \ email will be sent to {order.email}.') if 'bag' in request.session: del request.session['bag'] template = 'checkout/checkout_success.html' context = { 'order': order, } return render(request, template, context)
排查方向
- 查看Heroku实时日志:执行
heroku logs --tail命令,获取500错误的具体堆栈追踪信息,直接定位出错的代码行,比如变量未定义、数据库操作失败、Stripe API调用错误等。 - 验证上下文处理器变量:模板中使用的
product_count、bag_items、total、delivery、grand_total等变量来自bag_contents上下文处理器,确认settings.py的TEMPLATES['OPTIONS']['context_processors']中已添加bag.contexts.bag_contents,生产环境是否生效。 - 检查Stripe环境配置:确认Heroku的环境变量中
STRIPE_PUBLIC_KEY、STRIPE_SECRET_KEY、STRIPE_CURRENCY是否正确配置,没有遗漏或值错误。同时检查Stripe账户的支付设置是否符合生产环境要求。 - 确认数据库迁移与权限:执行
heroku run python manage.py migrate确保所有模型迁移都已应用到ElephantSQL数据库;检查数据库用户是否拥有读写权限,Order、OrderLineItem模型的字段是否在生产库中存在。 - 修复模板变量错误:模板中图片的
alt="{{ product.name }}"应为alt="{{ item.product.name }}",这个变量错误可能导致模板渲染失败引发500。 - CSRF信任域名配置:在
settings.py中确认CSRF_TRUSTED_ORIGINS包含Heroku项目的域名(格式如https://your-app-name.herokuapp.com),避免POST请求时CSRF验证失败。 - 静态文件收集状态:执行
heroku run python manage.py collectstatic确认静态文件(如结账页的CSS、JS)已正确收集到生产环境的静态文件存储中,避免因资源加载失败引发错误。
内容的提问来源于stack exchange,提问作者Timmyontech
相关产品推荐
相关产品推荐

