Django:将两个ForeignKeys组合成一个字段

时间:2014-02-26 02:46:22

标签: python django django-models django-forms foreign-keys

我需要实现以下内容:

将向用户显示一个表格,该表格将包含由属性名称组成的下拉选项菜单。有两种类型的属性:常规属性,即所有用户和自定义属性共有的属性,即每个用户在此之前定义的属性。模型看起来像这样:

class GeneralPropertyName(models.Model):
    name = models.CharField(max_length=20)

class CustomPropertyName(models.Model):
    user = models.ForeignKey(User)
    name = models.CharField(max_length=20)

下拉菜单应包含所有常规属性,并且只包含与用户相关的自定义属性。

第一个问题:如何定义这样的模型? 我需要:1。以某种方式统一两个属性,2。仅从CustomPropertyName中获取与用户相关的那些项

class SpecData(models.Model):
    user = models.ForeignKey(User)
    selection_title = models.CharField(max_length=20)
    property = ForeignKey(GeneralPropertyName)  ??UNIFY??? ForeignKey(CustomPropertyName)

其次,ModelForm需要做些什么特别的事情吗?

class SpecDataForm(ModelForm):

    class Meta:
        model = SpecData

第三个问题是视图中需要做什么?我将需要使用内联表单集,因为我将有一些这样的动态表单。

def index(request):

    user = User.objects.get(username=request.user.username)

    specdataFormSet = inlineformset_factory(User, SpecData, form=SpecDataForm, extra=30)

    ...     

    specdata_formset = specdataFormSet(instance=user, prefix='specdata_set')

    ...

感谢。

编辑:调整了juliocesar建议包含formsets。不知何故,我收到以下错误消息:无法将关键字'property'解析为字段。选项包括:id,name,selection_title,user

def index(request):
    user = User.objects.get(username=request.user.username) 
    user_specdata_form = UserSpecDataForm(user=user)
    SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)

4 个答案:

答案 0 :(得分:1)

1a)你看过django的ContentType framework这将允许你拥有通用外键,你可以限制存储可接受的模型类型。

1b)我认为接受哪种外键可接受的验证不应该在你的模型中,而应该在保存之前成为表单验证的一部分。

2)如果您确实使用了模型表单,则必须为自由字段定义自己的custom widget。这意味着您可能必须编写自己的渲染函数来渲染字段中的html。您还应该在表单上定义自己的验证功能,以确保只有适当的数据才能保存。

3)我认为你不必做任何你在视图中没有做过的事情

答案 1 :(得分:1)

使用GenericForeignKey:

class SpecData(models.Model):
    user = models.ForeignKey(User)
    selection_title = models.CharField(max_length=20)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    property = GenericForeignKey('content_type', 'object_id')

您可以使用this将两个字段(类型和ID)组合到一个选项字段中。

答案 2 :(得分:1)

您可以使用GenericForeignKey来处理它,但您仍需要更多解决方案和视图的更多问题。

我已经举例说明了如何解决问题(已记录的用户可以从常规属性和他的自定义属性中选择,非登录用户只能选择常规属性)。我使用模型继承作为属性(在您的示例代码中,似乎CustomPropertyNamePropertyName与其他字段)。我认为继承是比ContentTypes更容易和更基本的概念,它符合您的需求。

注意:我删除了一些代码,例如导入以简化代码。

1)models.py文件:

class PropertyName(models.Model):
    name = models.CharField(max_length=20)

    def __unicode__(self):
        return self.name

class CustomPropertyName(PropertyName): # <-- Inheritance!!
    user = models.ForeignKey(User)

    def __unicode__(self):
    return self.name


class SpecData(models.Model):
    user = models.ForeignKey(User)
    selection_title = models.CharField(max_length=20)
    property = models.ForeignKey(PropertyName)

注意:字段SpecData.property指向PropertyName,因为所有属性都保存在PropertyName的数据库表中。

2)forms.py文件:

from django import forms
from django.db.models import Q
from models import SpecData, PropertyName

def UserSpecDataForm(user=None):

    UserPropertiesQueryset = PropertyName.objects.filter(Q(custompropertyname__user=None) | Q(custompropertyname__user__id=user.id))

    class SpecDataForm(forms.ModelForm):
        property = forms.ModelChoiceField(queryset=UserPropertiesQueryset)

        class Meta:
            model = SpecData
            exclude = ('user',)

    return SpecDataForm

注意:这里的技巧是通过根据参数中指定的用户过滤属性来动态生成表单SpecDataForm

3)views.py文件:

from forms import UserSpecDataForm

def index(request):
    if request.POST:
        form = UserSpecDataForm(request.user)(request.POST)  # instance=user
        if form.is_valid():
            spec_data = form.save(commit=False)
            spec_data.user = request.user
            spec_data.save()
    else:
        form = UserSpecDataForm(request.user)()
    return render_to_response('properties.html', {'form': form}, context_instance=RequestContext(request))

注意:这里没什么特别的,只是调用form.UserSpecDataForm(request.user)返回表单类然后实例化。同时将已登录用户设置为save上返回的对象,因为form中已将其排除在前端未显示。

按照这个基本示例,如果需要,可以对formset执行相同的操作。

<强>更新

可以通过向视图添加以下代码来使用Formset:

user_specdata_form = UserSpecDataForm(user=request.user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)

可以从http://ge.tt/904Wg7O1/v/0

下载完整的项目示例

希望这有帮助

答案 3 :(得分:1)

一种方法是你只有一个模型,让用户可以为空:

class PropertyName(models.Model):
    user = models.ForeignKey(User, null=True, blank=True)
    name = models.CharField(max_length=20)

class SpecData(models.Model):
    user = models.ForeignKey(User)
    selection_title = models.CharField(max_length=20)
    property = ForeignKey(PropertyName)

因此,如果未设置用户,则它是一般属性。如果已设置,则与该用户相关。

但是,请注意,如果您需要唯一的属性名称,那么NULL!= NULL。

当然,建议的GenericForeignKey解决方案在某些情况下更好。

此外,您可以轻松地使用您描述的正常(非模型)表单,并将表单逻辑与模型逻辑分开。