Django Autocomplete Light 创建新选择

Ash*_*n M 7 python django django-forms django-autocomplete-light

我一直在学习为 Django Autocomplete Light 提供的以下教程:

https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html

我已成功为表单中的一个字段实现了自动完成,但是我无法完成以下部分:

https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#creation-of-new-choices-in-the-autocomplete-form

The documentation states that I should be able to add in a feature which allows the user to create a new choice in the form if their required choice is unavailable. However the tutorial is not particularly clear in explaining how to do this.

I am trying to implement a form in which the user can create a new Feedback by:

  1. Selecting from an autocompleting list of Categories
  2. Selecting a Message corresponding to the chosen Category
  3. If the Category or Message they wish to choose is not available, they should be able to add to the existing choices

I have this partly implemented, but it does not appear to work correctly as if no Category is selected, the drop down for the Messages displays the list of Categories. However, if a Category is selected, the correct Messages are displayed as required.

models.py

class Feedback(models.Model):
     feedback_id = models.IntegerField(primary_key=True,default=0)
     pre_defined_message = models.ForeignKey('Message',on_delete=models.CASCADE,null=True,blank=True) # Selected from a pre defined list depending on selected category
     points = models.IntegerField(default=0)
     lecturer = models.ForeignKey('LecturerProfile', on_delete=models.CASCADE, null=True, blank=True)
     student = models.ForeignKey('StudentProfile', on_delete=models.CASCADE, null=True, blank=True)
     which_course = models.ForeignKey('Course', on_delete=models.CASCADE, null=True, blank=True)
     datetime_given = models.DateTimeField(default=timezone.now, blank=False)
     optional_message = models.CharField(max_length=200,default="")
     category = models.ForeignKey('Category', on_delete=models.CASCADE, null=True, blank=True)

 class Category(models.Model):
     name = models.CharField(max_length=20, default="Empty",primary_key=True)

     def __str__(self):
         return self.name

class Message(models.Model):
     category = models.ForeignKey('Category',on_delete=models.CASCADE,null=True,blank=True)
     text = models.CharField(max_length=200,default="No message",primary_key=True)

     def __str__(self):
          return self.text
Run Code Online (Sandbox Code Playgroud)

forms.py

class FeedbackForm(autocomplete.FutureModelForm):
     optional_message = forms.CharField(max_length=200, required=False)

     class Meta:
         model = Feedback
         fields = ('category', 'pre_defined_message','optional_message','points')
         widgets = {
             'pre_defined_message': autocomplete.ModelSelect2(url='category_autocomplete',forward=['category']),
             'category': autocomplete.ModelSelect2(url='category_autocomplete')
         }
         help_texts = {
             'pre_defined_message': "Select a Message",
             'category': 'Category',
             'optional_message': "Optional Message",
             'points': "Points"
         }
Run Code Online (Sandbox Code Playgroud)

views.py

class CategoryAutocomplete(autocomplete.Select2QuerySetView):
     def get_queryset(self):
         if not self.request.user.is_authenticated or not self.request.user.is_lecturer:
             return Category.objects.none()

         query_set = Category.objects.all()

         category = self.forwarded.get('category', None)

         if self.q:
             query_set = query_set.filter(name__istartswith=self.q)
             return query_set

         if category:
             query_set = Message.objects.filter(category=category)

         return query_set
Run Code Online (Sandbox Code Playgroud)

urls.py

re_path(r'^category-autocomplete/$', CategoryAutocomplete.as_view(create_field='name'), name='category_autocomplete'),
Run Code Online (Sandbox Code Playgroud)


我已经搜索了一段时间的答案,并且一直在努力寻找解决方案。我也知道我的 forms.py 特别是可能没有最有效/最干净的代码,并且愿意接受改进的建议。我曾尝试定义一个init方法,但是我无法成功做到这一点。

提前致谢

Ash*_*n M 7

在搜索了 Django Autocomplete Light 的所有开源文档后:

https://github.com/yourlabs/django-autocomplete-light

我相信我已经找到了解决方案,并认为我应该将其分享给对提供的教程感到困惑的其他人。

到达我上面的阶段(即工作自动完成)后,您必须包含一个 get_create_option 方法,以允许视图了解在检索 create_field 时要执行的操作。

因此,在 urls.py 的 urlpatterns 列表中,确保存在以下行:

re_path(r'^category-autocomplete/$', CategoryAutocomplete.as_view(model=Category,create_field='name'), name='category_autocomplete')
Run Code Online (Sandbox Code Playgroud)


注意:create_field 变量必须设置为相关模型的主键。在我的例子中,Category 模型的主键是name

教程中没有说清楚的是下一步。查看以下文件后:

https://github.com/yourlabs/django-autocomplete-light/blob/master/src/dal_select2/views.py

我找到了一个方法 get_create_option 来处理新选项的创建。

def get_create_option(self, context, q):
    """Form the correct create_option to append to results."""
    create_option = []
    display_create_option = False
    if self.create_field and q:
        page_obj = context.get('page_obj', None)
        if page_obj is None or page_obj.number == 1:
            display_create_option = True

        # Don't offer to create a new option if a
        # case-insensitive) identical one already exists
        existing_options = (self.get_result_label(result).lower()
                            for result in context['object_list'])
        if q.lower() in existing_options:
            display_create_option = False

    if display_create_option and self.has_add_permission(self.request):
        create_option = [{
            'id': q,
            'text': _('Create "%(new_value)s"') % {'new_value': q},
            'create_id': True,
        }]
    return create_option
Run Code Online (Sandbox Code Playgroud)


在我的 views.py 中的 CategoryAutocomplete 类中包含此方法后,在搜索中创建新类别的能力终于奏效了!

我现在无法使用先前选择的类别作为外键创建 Message 对象,因为这也没有很好的记录。如果我找到解决方案,我会更新这个答案。

希望这对某人有帮助!

更新

虽然有点小技巧,但我已经设法设置了 Message 模型的外键。我只需访问创建的 Message 并在表单验证本身中设置其类别字段:

if request.method == 'POST':
        form = FeedbackForm(request.POST)
        if form.is_valid():
            new_fb = form.save(commit=False)
            # When a new message is made, the category it is associated with is not saved
            # To fix this, set the category field within this form and save the message object.
            new_fb.pre_defined_message.category = Category.objects.get(name=new_fb.category)
            new_fb.pre_defined_message.save()
Run Code Online (Sandbox Code Playgroud)