CSRF 令牌丢失或不正确 - 在 Django 中使用自动完成灯

CSRF token missing or incorrect - using auto-complete light in Django

运行 到 CSRF_Token 我的 Django 应用程序中丢失或不正确,我不太明白问题是什么,因为我已经包含了 {% csrf_token %}我的模板呈现的任何可能的形式。我怀疑这可能与在表单中完成的 ajax 请求有关,以检索区域名称等,也许有人可以告诉我问题出在哪里。我正在使用 autocomplete-light 从我的数据库中检索一些数据,不知道这是否可以发挥作用。我试过在线搜索,但没有找到似乎适用于我的问题的解决方案。

Views.py

Class BreedAutocomplete(autocomplete.Select2QuerySetView):

    def get_queryset(self):

        if not self.request.user.is_authenticated:
            return DogBreeds.objects.none()

        qs = DogBreeds.objects.all()

        if self.q:
            qs = qs.filter(name__istartswith=self.q)

        return qs


class AdListTakeMyDog(generic.ListView):
    model = Advertisement
    context_object_name = 'ads'
    template_name = 'core/advertisement_list_take.html'

    def get_queryset(self):
        queryset = Advertisement.objects.filter(is_offering_own_dog=True)
        return queryset


class AdListGetMeADog(generic.ListView):
    model = Advertisement
    context_object_name = 'ads'
    template_name = 'core/advertisement_list_get.html'

    def get_queryset(self):
        queryset = Advertisement.objects.filter(is_offering_own_dog=False)
        return queryset


class NewAdTakeMyDog(CreateView):
    model = Advertisement
    form_class = NewAdTakeMyDogForm
    success_url = reverse_lazy('view_ads_take_my_dog')
    template_name = 'core/advertisement_form_take.html'

    def form_valid(self, form):
        form.instance.author = self.request.user
        form.instance.is_offering_own_dog = True
        return super().form_valid(form)


class NewAdGetMeADog(CreateView):
    model = Advertisement
    form_class = NewAdGetMeADogForm
    success_url = reverse_lazy('ad_changelist')
    template_name = 'core/advertisement_form_get.html'

    def form_valid(self, form):
        form.instance.author = self.request.user
        form.instance.is_offering_own_dog = False
        return super().form_valid(form)

advertisement_create - 模板

{% extends '_base.html' %}

{% load static %}

{% block content %}
  <body onload="hide_area_field()">
    

  {% block form_title %}
  <h2>Ny annons</h2>
  {% endblock form_title %}

      
  {% if form.is_multipart %}
    <form method="post" enctype="multipart/form-data" id="adForm" data-municipalities-url="{% url 'ajax_load_municipalities' %}" data-areas-url="{% url 'ajax_load_areas' %}" novalidate>
    {% csrf_token %}  

  {% else %}
    <form method="post" id="adForm" data-municipalities-url="{% url 'ajax_load_municipalities' %}" data-areas-url="{% url 'ajax_load_areas' %}" novalidate>
    {% csrf_token %}  
    
    {% endif %}
      

    <table>  
      {{ form.as_p }}
    </table>

    <button type="submit">Publicera annons</button>

  {{ form.media }}

  </form>

</body>
{% endblock content %}

{% block footer %}

  {% comment %} Imports for managing Django Autocomplete Light in form {% endcomment %}
  <script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
  <link rel="stylesheet" type="text/css" href="{% static 'autocomplete_light/select2.css' %}" />
  <script type="text/javascript" src="{% static 'autocomplete_light/jquery.init.js' %}"></script>
  <script type="text/javascript" src="{% static 'autocomplete_light/autocomplete.init.js' %}"></script>
  <script type="text/javascript" src="{% static 'autocomplete_light/select2.js' %}"></script>
  <link rel="stylesheet" type="text/css" href="{% static 'autocomplete_light/vendor/select2/dist/css/select2.css' %}" />
  <script type="text/javascript" src="{% static 'autocomplete_light/vendor/select2/dist/js/select2.full.js' %}"></script>
  <script type="text/javascript" src="{% static 'autocomplete_light/forward.js' %}"></script>
  <script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>




  {% comment %} Functions for handling area selectors {% endcomment %}
  <script>
      function hide_area_field() {
        const area_selector = document.getElementById('id_area');
        area_selector.style.display = 'none';
      }

      /* Get URL for requesting list of municipalities & Areas */
      const baseURI = window.location.origin
      const municipality_url = document.getElementById('adForm')['attributes']['data-municipalities-url']['value']
      const area_url = document.getElementById('adForm')['attributes']['data-areas-url']['value']


      /* Get relevant objects from DOM */
      const province_selector = document.getElementById('id_province');
      const municipality_selector = document.getElementById('id_municipality');
      const area_selector = document.getElementById('id_area');

      /* Update municipalities when Province have been chosen */
      province_selector.addEventListener('change', (event) => {
        
        var xhr = new XMLHttpRequest();

        xhr.onload = function () {
          if (xhr.readyState === xhr.DONE) {
              if (xhr.status === 200) {
                  municipality_selector.innerHTML = xhr.response
              }
          }
        };

        xhr.open('GET',  baseURI + municipality_url, true);
        xhr.setRequestHeader('province', province_selector.value)
        xhr.send();

      });


      /* Update Areas when Municipiality have been chosen */
      municipality_selector.addEventListener('change', (event) => {
      
      var xhr = new XMLHttpRequest();

      xhr.onload = function () {
        if (xhr.readyState === xhr.DONE) {
            if (xhr.status === 200) {
                if (xhr.response.length > 58) {
                  area_selector.style.display = ''
                  area_selector.innerHTML = xhr.response
                } else {
                  area_selector.style.display = 'none'
                }
            }
        }
      };

      xhr.open('GET',  baseURI + area_url, true);
      xhr.setRequestHeader('municipality', municipality_selector.value)
      xhr.send();

    });
  </script>

{% endblock footer %}

forms.py

class NewAdTakeMyDogForm(forms.ModelForm):

    breed = forms.ModelChoiceField(
        queryset=DogBreeds.objects.all(),
        widget=autocomplete.ModelSelect2(url='breed-autocomplete')
    )

    class Meta:
        model = Advertisement
        fields = ('province', 'municipality', 'area', 'title', 'description', 'days_per_week', 'size_offered', 'breed', 'image1', 'image2', 'image3')

    def __init__(self, *args, **kwargs):
        super(NewAdTakeMyDogForm, self).__init__(*args, **kwargs)
        self.fields['municipality'].queryset = Municipality.objects.none()
        self.fields['area'].queryset = Area.objects.none()
        self.fields['area'].required = False

        if 'province' in self.data:
            try:
                # Set municipality queryset
                province_id = int(self.data.get('province'))
                self.fields['municipality'].queryset = Municipality.objects.filter(province_id=province_id).order_by('name')
            
                # Set area queryset
                municipality_id = int(self.data.get('municipality'))
                self.fields['area'].queryset = Area.objects.filter(municipality_id=municipality_id).order_by('name')
                

            except (ValueError, TypeError):
                pass # invalid input from the client; ignore and fallback to empty Municipality/Area queryset
            

您的 AJAX 请求不包含 CSRF 令牌。于是Django的跨站请求伪造保护就开始了。

Django documentation 详细解释了如何在发送 AJAX POST 请求时获取 CSRF 令牌,以及如何将其包含在请求的 headers 中。

错误是我看错了模板,我有多个具有继承性的模板,并且把它们搞混了,所以虽然我在表单中显示了 CSRF 令牌,但它是在一个未使用的模板。经验教训 - 有一个 BASE 模板,您可以从该模板开始,并且只使用块来编写在基本模板之间更改的基本代码。