HTMX和Django:在多个基于类的视图中使用HMTX

p8h8hvxi  于 2023-11-20  发布在  Go
关注(0)|答案(1)|浏览(136)

**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 %}

5anewei6

5anewei61#

不知道我是否理解了这个问题,但我认为你试图在通过htmx发出请求时使用django的redirect。这不会起作用,因为htmx通过使用响应头来处理重定向。
您应该尝试将django-htmx添加到项目中。
它包括一堆非常有用的实用程序,在使用django和htmx时可以节省很多时间。
为了解决您的问题,您需要更换

return redirect(reverse('bookings:booking_summary', kwargs={'slug': slug}))

字符串

return HttpResponseClientRedirect(reverse('bookings:booking_summary', kwargs={'slug': slug})


参考:https://django-htmx.readthedocs.io/en/latest/http.html#django_htmx.http.HttpResponseClientRedirect
另外,在处理htmx时,我发现FBV比CBV更容易使用。CBV有很多魔力,要让它在htmx上工作可能很棘手。

相关问题