Django Autocomplete Light创造新选择

时间:2018-11-17 16:29:17

标签: 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

文档指出,我应该能够添加一项功能,该功能允许用户在无法使用所需选项的情况下在表单中创建新选项。但是,本教程在解释如何执行此操作时并不清楚。

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

  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可能没有最有效/最干净的代码,并欢迎提出改进建议。我尝试定义 init 方法,但是无法成功完成此操作。

预先感谢

3 个答案:

答案 0 :(得分:2)

在搜索完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')


注意:必须将create_field变量设置为相关模型的主键。在我的情况下,类别模型的主键是名称

下一步是本教程中未明确说明的内容。在查看以下文件后:

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类中包含此方法后,终于可以在搜索中创建新类别了!

我现在很难用先前选择的类别作为外键来创建Message对象,因为该文档也没有得到很好的记录。如果找到解决方案,我将更新此答案。

希望这对某人有帮助!

更新

尽管有点麻烦,但我设法设置了消息模型的外键。我只是访问创建的Message并在表单验证本身中设置其category字段:

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()

答案 1 :(得分:1)

也许问题在于用户没有add permissionget_create_option checks吗?

如果将其添加到视图中,是否可行?

def has_add_permission(self, request): return True

答案 2 :(得分:0)

我有一个模特

 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中的

-类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]

所以我用

在我的自动完成课程中覆盖了这一点
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]

如果需要,可以将其进一步扩展为更新模型中的多行,并且在您的情况下,您应该能够将先前选择的类别传递到此定义中。