Как получить и передать контекст из одной формы в другую на основе полей первичного/внешнего ключа?
В настоящее время я создаю веб-сайт, который позволит продавать услуги по сведению и мастерингу. Поскольку это небольшой набор услуг, мне не нужна корзина или какая-либо сложная форма заказа. Вместо этого я хотел бы иметь страницу с информацией о клиенте (которая информирует мою модель 'Customer'), страницу заказа, на которой клиент выбирает, что именно он будет покупать, и загружает все необходимые файлы (что также информирует мою модель 'Order'), и, наконец, отправляет клиента на страницу оформления заказа через stripe.
В настоящее время форма Custome rdetails работает и сохраняет данные в соответствующую модель базы данных. После того, как пользователь нажимает кнопку продолжить, я пытаюсь понять, как сохранить первичный ключ экземпляра Customer, созданного пользователем при заполнении формы, и сохранить эти данные в следующей форме с помощью отношения внешнего ключа.
Подобным образом, перед отправкой покупателя в Stripe checkout, я хотел бы создать страницу 'Order Review', просматривающую детали его заказа. Я не уверен, как извлечь первичный ключ из только что созданного Order intance, чтобы создать представление модели на последующей странице. Я полагаю, что для достижения любой из этих целей мне не хватает способа получить первичный ключ инстанции базы данных, созданной клиентом при отправке формы.
Вот код, относящийся к моему вопросу, на случай, если я делаю это в корне неправильно:
models.py
class Customer(models.Model):
first_name = models.CharField(max_length=200, null=False)
last_name = models.CharField(max_length=200, null=False)
phone = models.CharField(max_length=10)
email = models.EmailField(null=False)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.last_name
class Product(models.Model):
MIXMAS = 'Mixing and Mastering Package'
MASO = 'Mastering Only'
FEAT = 'Request a Feature'
TUT = 'Request a Tutor'
NONE = 'Select an option'
PRODUCT_NAME_CHOICES = [
(MIXMAS, 'Mixing and Mastering Package'),
(MASO, 'Mastering Only'),
(FEAT, 'Request a Feature'),
(TUT, 'Request a Tutor'),
(NONE, 'Select an option')
]
name = models.CharField(max_length=100, choices=PRODUCT_NAME_CHOICES, default=NONE)
stripe_product_id = models.CharField(max_length=100)
product_description = models.CharField(max_length=300, null=True)
def __str__(self):
return self.name
class Price(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="prices")
stripe_price_id = models.CharField(max_length=100)
price = models.IntegerField(default=0) # cents
price_description = models.CharField(max_length=300, null=True)
class Meta:
ordering = ['price']
def get_display_price(self):
return "{0:.2f}".format(self.price / 100)
def __str__(self):
return '%s %s %s %s' % ("$", self.price, "-", self.price_description)
class Order(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name='Package Type: ')
price = models.ForeignKey(Price, on_delete=models.CASCADE, verbose_name="Number of stems: ")
cust_requests = models.TextField(max_length=500, null=True, verbose_name='Enter any specific requests here: (Leave blank if none): ')
reference_track = models.CharField(max_length=200, null=True, verbose_name='Reference Track (Leave blank if none): ')
music_file = models.FileField(upload_to='studio_orders/', verbose_name="Upload zipped music file: ")
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
order_date = models.DateTimeField(auto_now_add=True)
forms.py
from .models import Order, Customer, Product, Price
from django import forms
from django.urls import reverse_lazy
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from dynamic_forms import DynamicField, DynamicFormMixin
class OrderForm(DynamicFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super(OrderForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
def price_choices(form):
product = form['product'].value()
return Price.objects.filter(product=product)
def initial_price(form):
product = form['product'].value()
return Price.objects.filter(product=product).first()
product = forms.ModelChoiceField(
queryset=Product.objects.all(),
initial=Product.objects.first(),
label= "Select a Product:",
widget= forms.RadioSelect(
attrs={
'hx-get' : 'prices',
'hx-target' : '#prices',
'hx-swap' : 'innerHTML'
}),
required=True,
)
prices = DynamicField(
forms.ModelChoiceField,
queryset=price_choices,
initial=initial_price,
label= "Select a price:"
)
cust_requests = forms.CharField(
label = 'Enter any specific requests here: (Leave blank if none): ',
required=False,
max_length=500
)
reference_track = forms.FileField(
label = 'Upload a reference track, if applicable.',
required=False,
)
music_file = forms.FileField(
label = 'Upload your project here. Please ensure project has been zipped prior to uploading.',
required=True
)
class Meta:
model= Order
fields = ['product', 'prices', 'cust_requests', 'reference_track', 'music_file']
class CustomerForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(CustomerForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
class Meta:
model = Customer
fields = ['first_name', 'last_name', 'phone', 'email']
urls.py
urlpatterns = [
path('', views.StudiosOverview.as_view(), name='musicstudios'),
path('order-details/', views.orderdetails, name='orderdetails'),
path('customer-details/', views.CustomerDetails.as_view(), name='custdetails'),
path('customer-details/upload', views.custupload, name='custupload'),
path('order-details/prices/', views.prices, name='prices'),
path('order-details/upload', views.orderupload, name='orderupload'),
path('cancel/', CancelView.as_view(), name='cancel'),
path('success/', SuccessView.as_view(), name='success'),
path('create-checkout-session/<int:pk>', CreateCheckoutSessionView.as_view(), name='create-checkout-session')
]
views.py
def orderdetails(request):
form = OrderForm()
context = {'form' : form}
template_name = 'musicstudios/order_details.html'
return render(request, template_name, context)
def prices(request):
form = OrderForm(request.GET)
return HttpResponse(form['prices'])
def custupload(request):
if request.POST:
form = forms.CustomerForm(request.POST, request.FILES)
success_url = reverse_lazy('orderdetails')
print(request.FILES)
if form.is_valid():
form.save()
else:
ctx = {'form' : form}
return HttpResponseRedirect(request, 'musicstudios/customer_details.html', ctx)
return HttpResponseRedirect(success_url)
def orderupload(request):
if request.POST:
form = OrderForm()
success_url = reverse_lazy('create-checkout-session')
if form.is_valid():
form.save()
else:
ctx = {'form' : form}
return render(request, 'musicstudios/order_details.html', ctx)
return reverse_lazy(success_url)
class StudiosOverview(View):
def get_context_data(self, **kwargs):
product = Product.objects.all()
prices = Price.objects.all()
context = super(StudiosOverview, self).get_context_data(**kwargs)
context.update({
"product": product,
"prices": prices
})
return context
def get(self, request):
context = {
'page_headline' : 'Studio Services'
}
context.update(sidebar_context)
return render(request, 'musicstudios/overview.html', context)
class CustomerDetails(CreateView):
form_class = forms.CustomerForm
template_name = 'musicstudios/customer_details.html'
stripe.api_key = settings.STRIPE_SECRET_KEY
class CreateCheckoutSessionView(View):
def post(self, request, *args, **kwargs):
product_id = self.kwargs["pk"]
product = Product.objects.get(id=product_id)
domain = "https://lewnytoonsstudios.com"
if settings.DEBUG:
domain = "http://127.0.0.1:8000"
checkout_session = stripe.checkout.Session.create(
line_items=[
{
# Provide the exact Price ID (for example, pr_1234) of the product you want to sell
'price': product.prices.stripe_price_id,
'quantity': 1,
},
],
mode='payment',
success_url=domain + '/success.html',
cancel_url=domain + '/cancel.html',
automatic_tax={'enabled': True},
)
return JsonResponse({
'id' : checkout_session.id
})
class SuccessView(TemplateView):
template_name = "success.html"
class CancelView(TemplateView):
template_name = "cancel.html"
Соответствующие HTML шаблоны:
customer_details.html
<span class="flex-auto flex-col">
<form method="post" class="py-2" action="{% url 'custupload' %}" enctype="multipart/form-data"; return False;>
{% csrf_token %}
{{ form|crispy }}
<span class="flex justify-end">
<button class="lewny_button my-4" type="submit">Continue to Order</button>
</span>
</form>
</span>
</div>
order_details.html
<span class="flex-auto flex-col">
<form class="py-2" method="post" action="{% url 'orderupload' %}">
{% csrf_token %}
{{ form|crispy }}
<span class="flex justify-end">
<button class="lewny_button my-4" type="submit">Review Your Order</button>
</span>
</form>
</span>
</div>
Спасибо за помощь.
Я попробовал несколько htmx-методов "получения" объекта, но не смог добиться ничего работающего. Я рассматривал возможность захвата самого последнего объекта из базы данных, но это показалось очень небезопасным способом решения проблемы.
Это кажется работой для сессионных переменных. Как только клиент создан функцией сохранения, вы можете взять его id и поместить его в переменную сессии для последующего использования.
if form.is_valid():
customer = form.save()
request.session['customer_id'] = customer.id
Вы можете получить к нему доступ везде, где вам нужно, либо как request.session['customer_id']
(или request.sessions.get('customer_id')
для возврата None, если он не установлен) в функциональном представлении, либо self.request
как указано выше в представлении, основанном на классе.
Больше информации в docs