带有ModelSelect2Widget的AttributeError 更新1

问题描述

我安装了Django-Select2并将其应用到我的项目中,但是遇到一个错误,我无法解决问题。

这是我的forms.py文件

from django.forms import modelform_factory
from django_select2 import forms as s2forms
from .models import Employer,JobListing,Category_JobListing

class LocationWidget(s2forms.ModelSelect2Widget):
    search_fields = [
        "location__icontains",]

    def get_queryset(self):
        return Employer._Meta.get_field("location").choices


EmployerForm = modelform_factory(Employer,fields=["name","location","short_bio","website","profile_picture"],widgets={"location": LocationWidget})

views.py文件(仅相关的代码部分):

def submitJobListing(request):
    if request.method == "POST":
        employer_form = EmployerForm(request.POST,request.FILES)
        employer = employer_form.save()
        return HttpResponse("Your form has been successfully submitted.")

    else:
        employer_form = EmployerForm()

    context = {
        "employer_form": employer_form,}

    return render(request,'employers/submit_job_listing.html',context)

submit_job_listing.html(我的模板文件):

<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ employer_form }}
    <input type="submit" value= "Submit">
</form>

使用此代码,我得到:

/ submit-job-listing / 中的

AttributeError /

“列表”对象没有属性“无”

输出表明错误与模板中的{{ employer_form }}和视图中的return render(request,context)有关。

我知道发生此错误是因为我已经覆盖了get_queryset()中的LocationWidget方法,但是我不知道自己在做什么错。

这是怎么了?我该如何解决

更新1:

我遇到一个问题,说noreverseMatch at /submit-job-listing/ Reverse for 'location_choices' not found. 'location_choices' is not a valid view function or pattern name.。这很可能与我的整个项目中答案代码的集成有关。我不明白为什么会这样。出现错误时,我尝试访问的URL是http://127.0.0.1:8000/submit-job-listing/

以下是相关代码

forms.py

from django.forms import modelform_factory
from django_select2 import forms as s2forms
from .models import Employer,Category
from django import forms

class LocationWidget(s2forms.HeavySelect2Widget):
    data_view = "location_choices"

    def __init__(self,attrs=None,choices=(),**kwargs):
        super().__init__(attrs=attrs,choices=choices,data_view=self.data_view,**kwargs)

EmployerForm = modelform_factory(Employer,widgets={"location": LocationWidget})
JobListingForm = modelform_factory(JobListing,fields=["job_title","job_description","job_requirements","what_we_offer","job_application_url","categories"])

urls.py

from django.urls import path

from . import views

app_name = "employers"
urlpatterns = [
    path("",views.ListJobListingsView.as_view(),name="list_joblistings"),path("choices/location.json",views.LocationChoicesView.as_view(),name="location_choices"),path("<int:pk>/",views.DetailJobListingView.as_view(),name="detail_joblisting"),# path("employers/",views.IndexEmployersView.as_view(),name="index"),path("employers/<int:pk>/",views.DetailEmployerView.as_view(),name="detail_employer"),path("submit-job-listing/",views.submitJobListing,name="submit_job_listing"),# path("submit-job-listing/success/",)
]

views.py

def submitJobListing(request):
    if request.method == "POST":
        employer_form = EmployerForm(request.POST,request.FILES)
        job_listing_form = JobListingForm(request.POST,request.FILES)
        #check if employer with that name already exists
        employer_name = str(request.POST.get("name",""))
        employer_with_the_same_name = Employer.objects.get(name=employer_name)
        employer = None
        if (employer_with_the_same_name != None):
            employer = employer_with_the_same_name

        if employer == None and employer_form.is_valid() and job_listing_form.is_valid():
            employer = employer_form.save()

        job_listing = job_listing_form.save(commit=False)
        job_listing.employer = employer
        job_listing.save()

        #return HttpResponseRedirect(reverse("employers:thank_you")) # Todo: Add this URL and template
        return HttpResponse("Your form has been successfully submitted.")

    else:
        employer_form = EmployerForm()
        job_listing_form = JobListingForm()

    context = {
        "employer_form": employer_form,"job_listing_form": job_listing_form,context)

# https://stackoverflow.com/questions/64425630/attributeerror-after-i-added-select2-as-a-widget-in-one-of-my-forms-how-to-fix/64618891?noredirect=1#comment114273660_64618891
class ChoicesView(generic.list.BaseListView):
    paginate_by = 25

    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        self.object_list = None
        self.term = None

    def get(self,request,*args,**kwargs):
        self.term = kwargs.get("term",request.GET.get("term",""))
        self.object_list = self.get_queryset()
        context = self.get_context_data()
        return JsonResponse(
            {
                "results": [
                    {"id": value,"text": name}
                    for value,name in context["object_list"]
                ],"more": context["page_obj"].has_next(),}
        )

    def get_queryset(self):
        return [(value,name) for value,name in self.queryset if self.term.lower() in name.lower()]


class LocationChoicesView(ChoicesView):
    queryset = COUNTRY_CITY_CHOICES

跟踪:

Environment:


Request Method: GET
Request URL: http://127.0.0.1:8000/submit-job-listing/

Django Version: 3.1.2
Python Version: 3.8.3
Installed Applications:
['employers.apps.EmployersConfig','django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','django_select2']
Installed Middleware:
['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware']


Template error:
In template /home/john/Documents/Django_projects/jobsforjuniorseu/Version_1/jobsforjuniorseu/employers/templates/employers/submit_job_listing.html,error at line 15
   Reverse for 'location_choices' not found. 'location_choices' is not a valid view function or pattern name.
   5 :     {{ employer_form.media.css }}
   6 :     <style>
   7 :         input,select {width: 100%}
   8 :     </style>
   9 : </head>
   10 : <body>
   11 :     <h1>Submit a job listing</h1>
   12 :     <form method="post" enctype="multipart/form-data">
   13 :         {% csrf_token %}
   14 :         {{ job_listing_form }}
   15 :          {{ employer_form }} 
   16 :         <input type="submit" value= "Submit">
   17 :     </form>
   18 :     <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
   19 :     {{ employer_form.media.js }}
   20 : </body>
   21 : </html>
   22 : 

Traceback (most recent call last):
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/core/handlers/exception.py",line 47,in inner
    response = get_response(request)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/core/handlers/base.py",line 179,in _get_response
    response = wrapped_callback(request,*callback_args,**callback_kwargs)
  File "/home/john/Documents/Django_projects/jobsforjuniorseu/Version_1/jobsforjuniorseu/employers/views.py",line 88,in submitJobListing
    return render(request,context)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/shortcuts.py",line 19,in render
    content = loader.render_to_string(template_name,context,using=using)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/template/loader.py",line 62,in render_to_string
    return template.render(context,request)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/template/backends/django.py",line 61,in render
    return self.template.render(context)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/template/base.py",line 170,in render
    return self._render(context)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/template/base.py",line 162,in _render
    return self.nodelist.render(context)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/template/base.py",line 938,in render
    bit = node.render_annotated(context)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/template/base.py",line 905,in render_annotated
    return self.render(context)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/template/base.py",line 994,in render
    return render_value_in_context(output,context)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/template/base.py",line 973,in render_value_in_context
    value = str(value)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/utils/html.py",line 376,in <lambda>
    klass.__str__ = lambda self: mark_safe(klass_str(self))
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/forms/forms.py",line 134,in __str__
    return self.as_table()
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/forms/forms.py",line 272,in as_table
    return self._html_output(
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/forms/forms.py",line 229,in _html_output
    output.append(normal_row % {
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/utils/html.py",in <lambda>
    klass.__str__ = lambda self: mark_safe(klass_str(self))
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/forms/boundfield.py",line 34,in __str__
    return self.as_widget()
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/forms/boundfield.py",line 93,in as_widget
    return widget.render(
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django_select2/forms.py",line 262,in render
    output = super().render(*args,**kwargs)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/forms/widgets.py",line 241,in render
    context = self.get_context(name,value,attrs)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/forms/widgets.py",line 678,in get_context
    context = super().get_context(name,line 638,line 234,in get_context
    'attrs': self.build_attrs(self.attrs,attrs),File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django_select2/forms.py",line 240,in build_attrs
    "data-ajax--url": self.get_url(),line 235,in get_url
    return reverse(self.data_view)
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/urls/base.py",line 87,in reverse
    return iri_to_uri(resolver._reverse_with_prefix(view,prefix,**kwargs))
  File "/home/john/anaconda3/envs/django/lib/python3.8/site-packages/django/urls/resolvers.py",line 685,in _reverse_with_prefix
    raise noreverseMatch(msg)

Exception Type: noreverseMatch at /submit-job-listing/
Exception Value: Reverse for 'location_choices' not found. 'location_choices' is not a valid view function or pattern name.

解决方法

正如方法名称所建议的那样, get_queryset(...) 方法应返回 QuerySet ,对于您而言,您还返回了其他东西。

应该是这样的

from django_select2 import forms as s2forms


class LocationWidget(s2forms.ModelSelect2Widget):
    search_fields = [
        "location__icontains",]

    def get_queryset(self):
        return Employer.objects.all()

但是,我希望您不需要使用 get_queryset(...) 方法

更新1

您似乎错过了 .html

中的某些内容
{{ form.media.css }}
<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ employer_form.as_p }}
    <input type="submit" value="Submit">
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
{{ form.media.js }}
,

看起来您只需要一个light 1 小部件。

如果choices是一个较大的静态列表,则您可能需要一个沉重的小部件。

Light小部件

LocationWidget替换为s2forms.Select2Widget

forms.py:

EmployerForm = modelform_factory(
    Employer,fields=["name","location","short_bio","website"],widgets={"location": s2forms.Select2Widget})

沉重的小部件

不是继承s2forms.ModelSelect2Widget的{​​{1}}:

  • 继承LocationWidget并指定s2forms.HeavySelect2Widget,然后
  • 实现指定的视图。

forms.py:

data_view

urls.py:

from django.db.models.fields import BLANK_CHOICE_DASH


class LocationWidget(s2forms.HeavySelect2Widget):
    data_view = "location_choices"  # "employers:location_choices"

    def __init__(self,attrs=None,choices=(),**kwargs):
        super().__init__(attrs=attrs,choices=choices,data_view=self.data_view,**kwargs)

    @property
    def choices(self):
        return BLANK_CHOICE_DASH  # Avoid rendering all choices in HeavySelect2Widget

    @choices.setter
    def choices(self,value):
        pass

views.py:

# app_name = "employers"
urlpatterns = [
    path('',views.submitEmployer,name='index'),path('choices/location.json',LocationChoicesView.as_view(),name='location_choices'),]

参考

  1. 小部件:https://django-select2.readthedocs.io/en/latest/django_select2.html#widgets
  2. Select2数据格式:https://select2.org/data-sources/formats
  3. Ajax(远程数据):https://select2.org/data-sources/ajax