**Desired Outcome:**我正在尝试使用htmx和Django实现一个UX流程,用户首先选择一个日期,然后是一个时间段(基于日期确定),然后是一张票(基于日期和时间计算的可用性),然后输入任何特殊请求。表单和元素是动态呈现的,到目前为止没有页面刷新。用户可以继续进行下一步(预订摘要),这是在不同的页面上。在每个阶段(即我视图中的每个帖子),我都会在会话中保存重要的变量。这些是通过htmx发布的变量,稍后将用于创建模型示例。
**问题:当我到达最终视图时,post请求返回一个空白页面,url没有改变。它似乎再次从我的原始视图发起post请求(因为所选日期变量在控制台中再次打印为 None,而在此之前,它是从SelectDateForm中选择的日期)。
上下文:
该项目是一个活动预订网站。这个流程是用户如何预订一个特定的活动。
它一直工作到该流中的最后一个视图(SpecialRequestsView),并且我能够使用动态响应更新表单(例如,如果我已经呈现了SelectTicketForm,但决定返回SelectDateForm并更改日期,则会显示一组新的时隙,并且SelectTicketForm再次隐藏,直到选择时隙)。
怀疑我可能需要完全重新设计,但在Django中找不到太多关于htmx的基于类的视图的文档/研究。
我的views.py:
class CreateBookingView(View):
template_name = 'bookings/create_booking.html'
form_class1 = SelectDateForm
def get(self, request, *args, **Kwagrs):
date_form = self.form_class1()
listing = get_object_or_404(Listing, slug=self.kwargs['slug'])
context = {
'date_form': date_form,
'listing': listing,
'slug': self.kwargs['slug']
}
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
is_htmx = request.headers.get('HX-Request') == True
selected_date = request.POST.get('select_date')
request.session['booking_date'] = selected_date
print(f'Selected Date: {selected_date}')
listing = get_object_or_404(Listing, slug=self.kwargs['slug'])
try:
available_time_slots = calculate_availability(listing, selected_date)
except:
available_time_slots = None
past_date_error = "You cannot select a past date."
no_availability_error = "We're sorry. There is no availability for your selected date. Please select another date."
# Store session Variables
request.session['listing_slug'] = self.kwargs['slug']
request.session['booking_date'] = selected_date
if selected_date is not None:
date_date = datetime.strptime(request.session['booking_date'], '%Y-%m-%d').date()
else:
date_date = date(2150, 12, 30)
context = {
'available_time_slots': available_time_slots,
'slug': self.kwargs['slug'],
'selected_date': selected_date,
'selected_date_datetype': date_date,
'today': date.today(),
'past_date_error': past_date_error,
'no_availability_error': no_availability_error
}
#if selected_date is not None:
return render(request, 'bookings/timeslots.html', context)
class TimeSlotView(View):
form_class = SelectTicketForm
def post(self, request, *args, **kwargs):
slug = request.session['listing_slug']
listing = get_object_or_404(Listing, slug=slug)
selected_time_str = request.POST.get('data-timeslot')
selected_time_str = selected_time_str.replace('datetime.time', '')
booking_date = request.session['booking_date']
selected_time_dict = eval(selected_time_str)
start_time = get_start_time(selected_time_dict, booking_date)
end_time = get_end_time(selected_time_dict, booking_date)
# store session Variables
request.session['selected_time_dict'] = selected_time_dict
form = self.form_class(listing=listing, ticket_dict=selected_time_dict)
context = {
'slug': request.session['listing_slug'],
'start_time': start_time,
'end_time': end_time,
'form': form,
}
#if start_time is not None and end_time is not None: # if it doesn't work might need to be start_date and end_time
return render(request, 'bookings/book_tickets.html', context)
class BookTicketsView(View):
form_class = SelectTicketForm
special_req_form = SpecialRequestForm
def post(self, request, *args, **kwargs):
slug = request.session['listing_slug']
special_req_form = self.special_req_form()
selected_time_dict = request.session['selected_time_dict']
selected_ticket_quantities = []
for ticket in selected_time_dict['tickets']:
selected_tickets = {}
selected_tickets['ticket_id'] = ticket['ticket_id']
selected_tickets['ticket_name'] = ticket['ticket_name']
selected_tickets['selected_quantity'] = int(request.POST.get(f'ticket_{ticket["ticket_id"]}'))
selected_ticket_quantities.append(selected_tickets)
print(f'Selected Tickets {selected_ticket_quantities}')
selected_ticket_list = [ticket for ticket in selected_ticket_quantities if ticket['selected_quantity'] != 0]
print(f'Selected Tickets List {selected_ticket_list}')
# store session variables
request.session['selected_ticket_list'] = selected_ticket_list
total_selected_quantity = sum(ticket['selected_quantity'] for ticket in selected_ticket_list)
max_selection = selected_time_dict['total_availability']
no_selection_error = "Please select at least one ticket to proceed."
max_selection_error = f'Too many tickets selected. Maximum selection is: {int(max_selection)}'
context = {
'slug': slug,
'special_req_form': special_req_form,
'max_selection': int(selected_time_dict['total_availability']),
'selected_ticket_list': selected_ticket_list,
'total_selected_quantity': total_selected_quantity,
'no_selection_error': no_selection_error,
'max_selection_error': max_selection_error,
'max_selection': max_selection
}
return render(request, 'bookings/special_requests.html', context)
class SpecialRequestsView(View):
formclass = SpecialRequestForm
template_name = 'bookings/special_requests.html'
def post(self, request, *args, **kwargs):
slug = request.session['listing_slug']
special_requests = request.POST.get('special_req')
if special_requests is not None:
request.session['special_booking_request'] = special_requests
print(special_requests)
return redirect(reverse('bookings:booking_summary', kwargs={'slug': slug}))
class BookingSummaryView(View):
template_name = 'bookings/booking_summary.html'
def get(self, request, *args, **kwargs):
selected_time_dict = request.session['selected_time_dict']
booking_date = request.session['booking_date']
start_time = get_start_time(selected_time_dict=selected_time_dict, booking_date=booking_date)
end_time = get_end_time(selected_time_dict=selected_time_dict, booking_date=booking_date)
selected_ticket_list = request.session['selected_ticket_list']
listing = get_object_or_404(Listing, slug=self.kwargs['slug'])
try:
special_requests = request.session['special_booking_request']
except:
special_requests = None
context = {
'start_time': start_time,
'end_time': end_time,
'selected_ticket_list': selected_ticket_list,
'listing': listing,
'special_requests': special_requests
}
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
return render(request, self.template_name)
字符串
我的forms.py:
class SelectDateForm(forms.Form):
select_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date', 'id': 'select_date'}))
class SelectTicketForm(forms.Form):
def __init__(self, listing, ticket_dict, *args, **kwargs):
super(SelectTicketForm, self).__init__(*args, **kwargs)
ticket_dict = ticket_dict
tickets = Ticket.objects.filter(listing=listing)
for ticket in tickets:
self.fields[f'ticket_{ticket.uid}'] = TicketSelectField(
ticket_id=ticket.uid,
price=ticket.price,
adults = ticket.adults,
children = ticket.children,
label=ticket.name, # Display the ticket name as the label
initial=0, # Initial quantity set to 0
min_value=0, # Minimum value allowed,
max_value = ticket_max_availability(ticket, ticket_dict),
required=False,
help_text=ticket.info,
widget=forms.NumberInput(attrs={
'class': 'form-control'
})
)
print(f"Ticket {ticket.uid} - Price: {ticket.price}")
class SpecialRequestForm(forms.Form):
special_req = forms.CharField(widget=forms.Textarea(attrs={'class': 'form-control', 'rows': '5'}), required=False)
型
创建_预订.html:
{% extends "base.html" %}
{% block content %}
<div class="container" style="width: 80%">
<div class="row">
<div class="col-4">
<div class="d-flex justify-content-left">
<h1 style='font-family: copperplate mt; font-size: 80px'>{{ listing }}</h1>
</div>
</div>
<div class="col-8">
<div id="listingimagecarousel" class="carousel slide mb-5">
<div class="carousel-inner" style="height: 50%;">
<div class="carousel-item active">
{% if listing.listing_image %}
<img class="d-block w-100" src="{{ listing.listing_image.url }}" alt="Listing Image" />
{% else %}
<img src="..." class="d-block w-100" alt="...">
{% endif %}
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#carouselExample" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#carouselExample" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="container" style="width: 80%; justify-content: center; align-items: center; display: flex;">
<form hx-post='{% url "bookings:create_booking" slug=listing.slug %}' hx-target='#available-time-slots'>
{% csrf_token %}
{{ date_form }}
<input type="submit" value="View Time Slots" class="dayzeout-btn dayzeout-grdnt-outline-btn">
</form>
</div>
<div id="available-time-slots">
{% include 'bookings/timeslots.html' %}
</div>
{% endblock %}
<script>
$('#flexCheckDefault').prop('checked', true);
$('#flexCheckDefault').prop('checked', false);
</script>
型
timeslots.html:
{% block content %}
{% load custom_filters %}
<div class="container mt-5" style="width: 80%">
<div id="time-slots">
{% if selected_date != None %}
{% if selected_date_datetype < today %}
<div class="alert alert-danger" style="width: 50%; margin: 0 auto; text-align: center;">
<i class="bi bi-exclamation-circle"></i> {{ past_date_error }}
</div>
{% else %}
{% if available_time_slots == None %}
<div class="alert alert-danger" style="width: 50%; margin: 0 auto; text-align: center;">
<i class="bi bi-exclamation-circle"></i> {{ no_availability_error }}
</div>
{% else %}
{% for timeslot in available_time_slots %}
<button name="timeslot_button" hx-post='{% url "bookings:select_timeslot" slug=slug %}' hx-target="#time-slots" hx-swap="outerHTML show:#ticket-form:top " class="select-time-slot btn dayzeout-green-btn mt-2" hx-include="this">
{{ timeslot.start_time|format_time }} - {{ timeslot.end_time|format_time }}
<input name="data-timeslot" value="{{timeslot}}" type="hidden">
</button>
{% endfor %}
{% endif %}
{% endif %}
{% endif %}
</div>
</div>
{% endblock %}
型
book_tickets.html:
{% block content %}
{% csrf_token %}
{% load custom_filters %}
<style>
.custom-tooltip {
--bs-tooltip-bg: #c855fa;
--bs-tooltip-color: var(--bs-white);
--bs-tooltip-opacity: 1;
}
</style>
<div class="container mt-5" style="width: 80%; text-align: center;">
<row class="mb-5">
<h6>Selected Time:</h6><button class="dayzeout-btn dayzeout-grdnt-nhvr-btn" disabled><strong> {{start_time|format_time}} - {{end_time|format_time}}</strong></button>
</row>
<div class="container mt-5" style="width: 100%;" id="ticket-form">
<form hx-post='{% url "bookings:book_tickets" slug=slug %}' hx-target="#special-requests" hx-swap="show:#special-requests:top">
{% csrf_token %}
<div class="container" style="width: 100%;">
<h3>Select Tickets:</h3>
<table class="table" style="width: 100%">
<thead>
<tr>
<th name="ticket & description" scope="col" hidden/>
<th name="price" scope="col" hidden/>
<th name="quantity" scope="col" hidden/>
</tr>
</thead>
<tbody>
{% for field in form %}
<tr>
<td>
<strong>{{field.label}}</strong>
<i class="bi bi-info-circle-fill" style="font-size: 15px; color: #000000" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-custom-class="custom-tooltip" data-bs-title="{{field.field.adults}} Adults; {{field.field.children}} Children"></i>
<br/>
{{field.help_text}}
</td>
<td>£{{field.field.price}}</td>
<td>{{field}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<input type="submit" value="Confirm Selection" class="dayzeout-btn dayzeout-grdnt-btn">
</form>
</div>
</div>
<div id="special-requests">
</div>
{% endblock %}
<script>
$(function () {
$('[data-bs-toggle="tooltip"]').tooltip();
});
</script>
型
special_requests.html:
{% block content %}
<div class="container mt-5" style="width: 80%; text-align: center;">
<div id="ticket-selection">
{% if total_selected_quantity == 0 %}
<div class="alert alert-danger" style="width: 60%; margin: 0 auto; text-align: center;">
<i class="bi bi-exclamation-circle"></i> {{ no_selection_error }}
</div>
{% elif total_selected_quantity > max_selection %}
<div class="alert alert-danger" style="width: 60%; margin: 0 auto; text-align: center;">
<i class="bi bi-exclamation-circle"></i> {{ max_selection_error }}
</div>
{% else %}
{% for ticket in selected_ticket_list %}
<div class="col">
<strong>Your Selection</strong>
</div>
<div class="col">
{{ ticket.selected_quantity }} x {{ ticket.ticket_name }}
</div>
{% endfor %}
<div class="container mt-5" style="width: 80%">
<form method="POST" enctype="multipart/form-data">
<div>
{% csrf_token %}
<row class="row mb-2">
<strong>Special Requests:</strong>
</row>
<row class="row">
{{ special_req_form }}
</row>
</div>
<input type="submit" value="Confirm Selection" class="dayzeout-btn dayzeout-grdnt-btn">
</form>
</div>
{% endif %}
</div>
</div>
{% endblock %}
型
booking_summary.html(未完成,不应该引起问题):
{% extends "base.html" %}
{% block content %}
{% csrf_token %}
{% load custom_filters %}
<div class="container mt-5" style="width: 80%; text-align: center;">
<div>
<h6>Your Tickets</h6>
{{ selected_ticket_list }}
</div>
<div>
<row class="row">
<h6>Special Requests</h6>
</row>
<row class="row">
{{ special_requests }}
</row>
<row class="row">
<button class="dayzeout-btn dayzeout-grdnt-btn">Proceed to Payment</button>
</row>
</div>
</div>
{% endblock %}
型
1条答案
按热度按时间5anewei61#
不知道我是否理解了这个问题,但我认为你试图在通过htmx发出请求时使用django的
redirect
。这不会起作用,因为htmx通过使用响应头来处理重定向。您应该尝试将django-htmx添加到项目中。
它包括一堆非常有用的实用程序,在使用django和htmx时可以节省很多时间。
为了解决您的问题,您需要更换
字符串
与
型
参考:https://django-htmx.readthedocs.io/en/latest/http.html#django_htmx.http.HttpResponseClientRedirect
另外,在处理htmx时,我发现FBV比CBV更容易使用。CBV有很多魔力,要让它在htmx上工作可能很棘手。