Django CBV form submission returned JSON is displayed as a new page

Viewed 164

I am using Django 3.2

I am creating a simple newsletter subscription form. The form submission returns JSON to the frontend, which should then be used to update parts of the page - however, when I post the form, the JSON string is displayed as text on a new page.

Here is the route that calls the view:

urlpatterns = [
               # ...
               path('subscription', BlogsubscriberCreateView.as_view(), name='subscription-post'),
               # ...
              ]

Here is my class based view:

class BlogsubscriberCreateView(CreateView):
    model = BlogPostSubscriber
    form_class = BlogSubscriptionForm
    http_method_names = ['post']

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        content_type = "application/json"

        if not form.is_valid():
            return JsonResponse({'ok': 0, 'msg': form.errors.get('email')[0]}, content_type=content_type, status=200)

        else:
            email = form.cleaned_data.get("email")
            subscriber = BlogPostSubscriber(email= email)
            subscriber.save()
            # send email to confirm opt-in
            email_message='Please confirm your subscription'
            message = f"A confirmation email has been sent to {email}. Please confirm within 7 days"
            return JsonResponse({'ok': 1, 'msg': message}, content_type=content_type, status=200)

Here is a snippet of the HTML containing the form:

<div class="col-lg-8 content">
                                <form id="blog-subscription" action="{% url 'blog:subscription-post' %}" method="post">
                                    {% csrf_token %}
                                    <br />
                                    <h3>Some title</h3>
                                    <br />
                                    
                                    <p>Lorem ipsum ...</p>
                                    
                                    <br />
                                    
                                    <h4 id='submit-response-h4'>SUBSCRIBE TO OUR BLOG</h4>
                                    <div id="submit-response" class="input-group">
                                        <span id="email-error"></span>
                                        <input type="email" id="blog-subscription-email" name="email" class="form-control" placeholder="Enter your email" required="true">
                                        <span class="input-group-btn">
                                            <button id="subscribe-btn" class="btn" type="submit">Subscribe Now</button>
                                        </span>
                                    </div>
                                </form>

Here is the Javascript that is responsible for updating the page:

  $().ready(function() {

        $('form#blog-subscription button#subscribe-btn.btn').on('click', function(e){
            let email = $('#blog-subscription-email').val().trim().toLowerCase();

            if (email.length && isValidEmail(email)){
                
               e.preventDefault();

                $.post({
                    url: "{% url 'blog:subscription-post' %}",
                    dataType: 'json',
                    data: {
                        email: email,
                        csrfmiddlewaretoken: '{{ csrf_token }}'
                    },   
                    success: function (data){  
                        let ok = data.ok;

                        if (ok){
                            if ($('#submit-response-h4').length){
                                $('#submit-response-h4').remove();
                            }
                            $('#submit-response').text(data.msg);
                        }
                        else{
                            $('#email-error').text(data.msg);
                        }

                    }
                });      
            }
        });
});

I placed an alert note in my Javascript and realised that it is simply not being called at all. I don't understand what is going on - if the Javascript is not being called - how does JQuery know to call the post function to the correct URL? (form has no action attribute!).

Typical example of a response (with a bad email) shown at http://example.com/path/to/subscription:

{"ok": 0, "msg": "Please use a different email service provider"}

What is causing this problem - and how do I fix it?

1 Answers

This code populates a modal with relevant images. It is not exactly the same as your issue, but this does work so should be transferable with amendments.

js

function modal_form(form_url){
    var modal_id = 'modal-form';
    $.ajax({
        type: 'GET',
        url: form_url,
        cache: false,
        success: function (data, status) {
            $('#carouselModal').html(data);
            $('#carouselModal').modal('show');
        }
    });
}

html

                                <img class="d-block w-100" src="{% thumbnail image.file 'carousel_main' subject_location=image.file.subject_location %}" alt="{{ organisation.name }} - {{ forloop.counter }}" itemprop="image" onclick="modal_form('{% url "providers:carousel-modal" organisation.id "carousel_photos" %}')">

urls

path('carousel-modal/<int:pk>/<str:gallery>/', views.CarouselModal.as_view(), name="carousel-modal"),

views

class CarouselModal(DetailView): model = models.Organisation template_name = "providers/extras/gallery_modal.html"

def get_context_data(self, **kwargs):
    ctx = super().get_context_data(**kwargs)
    gallery = self.kwargs['gallery']
    if gallery == 'carousel_photos':
        ctx['images'] = self.object.carousel_photos
    elif gallery == 'propertyimages':
        ctx['images'] = self.object.propertyimages_set.all()
    
    if self.request.user_agent.is_mobile:
        ctx['image_size'] = 'mobile_modal_carousel_slide'
    elif self.request.user_agent.is_tablet:
        ctx['image_size'] = 'tablet_modal_carousel_slide'
    else: ctx['image_size'] = 'modal_carousel_slide'
    try: ctx['show_all'] = self.object.is_premium
    except: ctx['show_all'] = False
    return ctx

gallery_modal.html

{% load static thumbnail %}
<div class="modal-dialog carousel-modal" role="document">
    <div class="modal-content">
        <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
        <div class="modal-body">    
            <div id="mainCarousel" class="carousel slide thumbnail-carousel" data-ride="carousel">
                <div class="thumbnail-carousel__main">
                    <div class="carousel-inner">
                        {% for image in images %}
                            {% if show_all or forloop.first %}
                            <div class="carousel-item{% if forloop.first %} active{% endif %}">
                                <img class="d-block w-100" width="100%" src="{% thumbnail image.file image_size subject_location=image.file.subject_location %}" alt="{{ image.name }}" itemprop="image" >
                            </div>
                            {% endif %}
                        {% empty %}
                            {% include 'providers/detail/stock_carousel.html' %}
                        {% endfor %}
                    </div>
                    {% if show_all and images.count > 1 %}
                        <a class="carousel-control-prev" href="#mainCarousel" role="button" data-slide="prev">
                            <span class="carousel-control-prev-icon" aria-hidden="true"></span>
                            <span class="sr-only">Previous</span>
                        </a>
                        <a class="carousel-control-next" href="#mainCarousel" role="button" data-slide="next">
                            <span class="carousel-control-next-icon" aria-hidden="true"></span>
                            <span class="sr-only">Next</span>
                        </a>
                    {% endif %}
                </div>

            </div>
        </div>
    </div>
</div>

I've just realised in this example I'm not return a JSONResponse, but again, I think the principle is good

Related