我已经查看了较旧的问题但未找到可以解决我问题的答案。
好的,我们假设我有一个名为'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中,因此用户可以选择适当的项目。有没有一种简单的方法来实现这一目标?为了更好的可读性,缩进子项目的名称也是很好的。
到目前为止感谢!
答案 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)
它在管理员中的表现: