ModelChoiceField

时间:2016-01-05 13:26:55

标签: python django database sorting

我已经查看了较旧的问题但未找到可以解决我问题的答案。

好的,我们假设我有一个名为'Project'的模型:

class Project(models.Model):
  name = models.CharField(max_length=50)
  parentproject = models.ForeignKey('self', related_name="parentproject_related", blank=True, null=True,
                                  verbose_name="parent project")

  ... a lot more ...

因此,通过这个模型,我可以创建项目和子项目。一个项目可以有几个子项目,可以由parentproject字段识别。

DB内所有项目都在一个表中:

project1
project2
subproject1
subprojectA
project3
subprojectB
subproject2

借助两个for循环,可以在数据模型上创建真实视图:

project1
  subproject1
  subproject2

project2
  subprojectA
  subprojectB

project3

现在我希望这个排序列表显示在ChoiceField中,因此用户可以选择适当的项目。有没有一种简单的方法来实现这一目标?为了更好的可读性,缩进子项目的名称也是很好的。

到目前为止感谢!

2 个答案:

答案 0 :(得分:1)

您应该考虑使用django-mptt项目来帮助您。它支持树结构,因此您可以非常轻松地创建项目和子项目关系。您还可以在一个SQL查询中获取子树而无需遍历树。

以下是他们如何建立父母关系的官方示例:http://django-mptt.github.io/django-mptt/tutorial.html#set-up-your-model

答案 1 :(得分:1)

我已经大量使用了,我同意如果你不需要多个级别的深度,那么最好避免使用它,因为它确实会带来一些限制和树损坏的潜在问题。

以下是我在mptt's tree field的启发下解决这个问题的方法:

<强> forms.py

from django import forms

from .fields import ProjectChoiceField
from .models import Project


    class ProjectForm(forms.ModelForm):

        parentproject = ProjectChoiceField(
            queryset=Project.objects.all(), required=False)

        class Meta:
            fields = ['name', 'parentproject']
            model = Project

<强> fields.py

import itertools
from collections import OrderedDict

from django import forms
from django.forms.fields import ChoiceField
from django.forms.models import ModelChoiceIterator
from django.utils.encoding import smart_text
from django.utils.html import conditional_escape
from django.utils.text import mark_safe


class NestedModelChoiceIterator(ModelChoiceIterator):

    def __iter__(self):
        if self.field.empty_label is not None:
            yield ("", self.field.empty_label)

        for obj in self.get_linear_tree(self.queryset):
            yield self.choice(obj)

    def get_linear_tree(self, queryset):
        # We use ordered dict because otherwise the queryset ordering goes kaput.
        project_tree = OrderedDict()

        for project in queryset.select_related('parentproject'):
            parent_project_id = project.parentproject_id

            if parent_project_id:
                project_tree.setdefault(parent_project_id, []).append(project)
            else:
                project_tree.setdefault(project.pk, []).append(project)

        flattened_tree = itertools.chain(*project_tree.itervalues())
        return list(flattened_tree)


class ProjectChoiceField(forms.ModelChoiceField):

    def __init__(self, queryset, *args, **kwargs):
        # you can make this into a mixin for more flexibility
        self.level_indicator = kwargs.pop('level_indicator', '---')
        super(ProjectChoiceField, self).__init__(queryset, *args, **kwargs)

    def _get_level_indicator(self, obj):
        # If you plan to have multiple levels
        # then you'll need to go up the tree here.
        if obj.parentproject_id:
            level = 2
        else:
            level = 1
        return mark_safe(conditional_escape(self.level_indicator) * level)

    def label_from_instance(self, obj):
        """
        Creates labels which represent the tree level of each node when
        generating option labels.
        """
        level_indicator = self._get_level_indicator(obj)
        return mark_safe(level_indicator + ' ' + conditional_escape(smart_text(obj)))

    def _get_choices(self):
        # If self._choices is set, then somebody must have manually set
        # the property self.choices. In this case, just return self._choices.
        if hasattr(self, '_choices'):
            return self._choices

        # Otherwise, execute the QuerySet in self.queryset to determine the
        # choices dynamically. Return a fresh ModelChoiceIterator that has not been
        # consumed. Note that we're instantiating a new ModelChoiceIterator *each*
        # time _get_choices() is called (and, thus, each time self.choices is
        # accessed) so that we can ensure the QuerySet has not been consumed. This
        # construct might look complicated but it allows for lazy evaluation of
        # the queryset.
        return NestedModelChoiceIterator(self)

    choices = property(_get_choices, ChoiceField._set_choices)

它在管理员中的表现:

admin