【问题标题】:Django: combine two ForeignKeys into one fieldDjango:将两个 ForeignKeys 组合成一个字段
【发布时间】:2014-03-28 15:35:01
【问题描述】:

我需要实现以下内容:

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

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 的建议以包含表单集。不知何故,我收到以下错误消息:无法将关键字“属性”解析为字段。选项有: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)

【问题讨论】:

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


    【解决方案1】:

    您可以使用GenericForeignKey 来处理它,但您仍然需要更多来解决有关表单和视图的更多问题。

    我已经举了一个例子来说明你如何解决你的问题(登录用户可以从常规属性和他的自定义属性中选择,非登录用户只能选择常规属性)。我对属性使用了模型继承(在您的示例代码中,CustomPropertyName 似乎是带有其他字段的PropertyName)。我认为继承是比 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:

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

    完整的项目示例可以从http://ge.tt/904Wg7O1/v/0下载

    希望对你有帮助

    【讨论】:

    • 谢谢。我一直在尝试使用表单集提出您的建议:请参阅问题上方的“编辑”。不知何故,我收到以下错误:无法将关键字“属性”解析为字段。选项有:selection_title、id、name、user。知道会发生什么吗?
    • 我在我的示例中添加了您的代码并且它有效,所以也许您在其他地方遗漏了一些东西,我将项目上传到ge.tt/904Wg7O1/v/0,以便您可以对其进行测试并与您的代码进行比较,运行django 服务器并访问根页面,您将获得 30 个表单集。我删除了user = User.objects.get(username=request.user.username),因为它不是必需的,并且在没有用户登录时会生成错误。
    • 我给了你赏金。我仍然在保存该字段时遇到一些问题,因为它无法说“该字段是必需的”,但总体而言,您的解决方案似乎正在运行。谢谢。
    • 我创建了一个 SO 问题,我对这个解决方案的 formset 有疑问,它有完整的代码,所以很容易检查。你能再帮我一次吗?无论如何,谢谢。问题在这里:stackoverflow.com/questions/22274895/…
    【解决方案2】:

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

    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 解决方案在某些情况下会更好。

    此外,您可以使用您描述的内容轻松制作普通(非模型)表单,并将表单逻辑与模型逻辑分开。

    【讨论】:

      【解决方案3】:

      使用 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)组合成一个选择字段。

      【讨论】:

        【解决方案4】:

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

        1b) 我认为接受什么类型的外键是可接受的验证不应该在您的模型中,而应该是保存前表单验证的一部分。

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

        3) 我认为您不必做任何您在视图中尚未做的事情

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-10-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多