Django Autocomplete Light 创建新选择

Django Autocomplete Light create new choice

我一直在学习为 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

文档指出我应该能够添加一项功能,允许用户在他们所需的选择不可用时在表单中创建新的选择。但是教程并没有特别清楚地解释如何做到这一点。

我正在尝试实现一种用户可以通过以下方式创建新反馈的表单:

  1. 从类别的自动完成列表中选择
  2. 选择与所选类别对应的消息
  3. 如果他们希望选择的类别或消息不可用,他们应该能够添加到现有的选择中

我已经部分实现了这个,但它似乎没有正常工作,就好像没有选择类别一样,消息的下拉列表显示类别列表。但是,如果选择了类别,则会根据需要显示正确的消息。

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

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"
         }

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

urls.py

re_path(r'^category-autocomplete/$', CategoryAutocomplete.as_view(create_field='name'), name='category_autocomplete'),


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

提前致谢

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

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

我相信我已经找到了解决这个问题的方法,我想我应该把它分享给那些对所提供的教程感到困惑的人。

在达到我上面的阶段(即工作自动完成)后,您必须包含一个 get_create_option 方法以允许视图在检索 create_field.[=15 时了解要做什么=]

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

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


注意: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


在我的 views.py 中的 CategoryAutocomplete class 中包含此方法后,终于可以在搜索中创建新类别了!

我现在很难用之前选择的类别作为外键创建消息对象,因为这也没有很好的记录。如果找到解决方案,我会更新此答案。

希望对大家有所帮助!

更新

虽然有点hack,但我还是成功设置了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()

也许问题是用户没有 add permission that get_create_option checks

如果将此添加到视图中是否有效?

def has_add_permission(self, request): return True

我有模型

 class sites(models.Model): #Site Owner for standard sites will be system_1
    site = models.CharField(max_length=100)
    site_owner = models.ForeignKey(User, on_delete=models.CASCADE, blank = True, null=True)
    def __str__(self):
        return self.site

我希望用户能够通过自动完成功能添加新网站并记录哪个用户创建了网站

在dal\views.py - class BaseQuerySetView(ViewMixin, BaseListView): 有以下

def create_object(self, text):
    """Create an object given a text."""
    return self.get_queryset().get_or_create(
        **{self.create_field: text,})[0]

所以我在我的自动完成 class 中用

覆盖了这个
def create_object(self, text):
    """Create an object given a text."""        
    return self.get_queryset().get_or_create(site_owner=self.request.user,
        site=text)[0]

这可以进一步扩展,然后在需要时更新模型中的多行,在您的情况下,您应该能够将之前选择的类别传递到此定义中。