在__init__动态添加到ModelForm的字段不保存

时间:2011-01-23 11:01:41

标签: django django-forms

我正在使用Django个人资料,并受到James Bennett的启发,创建了一个动态表格(http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/)

我需要的是一个公司字段,当user_type为'pro'时,该字段仅显示在我的用户个人资料表单中。 基本上我的模型和形式看起来像:

class UserProfile(models.Model):
    user_type = models.CharField(...
    company_name = models.CharField(...

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        exclude = ('company_name',)

我在 init 中添加了company_name字段,就像James Bennett所示:

def __init__(self, *args, **kwargs):
    super(UserProfileForm, self).__init__(*args,**kwargs)
    if (self.instance.pk is None) or (self.instance.user_type == 'pro'):
        self.fields['company_name'] = forms.CharField(...

问题在于,当我尝试保存()UserProfileForm的实例时,不保存字段'company_name'...

我通过在save()方法中显式调用该字段来解决这个问题:

def save(self, commit=True):
    upf = super(UserProfileForm, self).save(commit=False)
    if 'company_name' in self.fields:
        upf.company_name = self.cleaned_data['company_name']
    if commit:
        upf.save()
    return upf

但是我对这个解决方案不满意(如果有更多的领域会怎么样?Django的美丽是什么?等等)。它让我在晚上试图让模型表识别 init 中的新company_name字段。

这就是我最终在stackoverflow上发布此内容的故事......

4 个答案:

答案 0 :(得分:3)

我会从表单中删除此逻辑并将其移至工厂。如果您的逻辑是在工厂,您可以有两种形式:

  • UserProfileForm
  • ProUserProfileForm

ProUserProfileForm继承自UserProfileForm并仅更改“exclude”常量。

您将拥有以下工厂:

def user_profile_form_factory(*args, instance=None, **kwargs):
   if (self.instance.pk is None) or (self.instance.user_type == 'pro'):
       cls = ProUserProfileForm
   else:
       cls = UserProfileForm
   return cls(*args, instance, **kwargs)

答案 1 :(得分:1)

您希望何时设置user_type属性?这似乎应该由javascript处理,而不是试图用模型形式做有趣的事情。

如果您希望company_name字段在客户端指定为pro后显示在客户端上,则可以使用javascript“取消隐藏”该字段。

如果相反,他们已被指定为pro用户,则使用包含company_name字段的其他表单。您可以按以下方式对原始模型表单进行子类化。

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        exclude = ('company_name',)

class UserProfileProForm(UserProfileForm):
    class Meta:
        exclude = None # or maybe tuple() you should test it

然后在您的视图中,您可以决定要呈现的表单:

def display_profile_view(request):
    if user.get_profile().user_type == 'Pro':
        display_form = UserProfileProForm()
    else:
        display_form = UserProfileForm()
    return render_to_response('profile.html', {'form':display_form}, request_context=...)

在我看来,这是首选方式。它不依赖任何花哨的东西。代码重复很少。很清楚,也很期待。

修改:(以下建议的解决方案不起作用)

您可以尝试更改元类的exclude,并希望在尝试确定是否包含该字段时,它会使用exclude的实例版本。给定一个表单的实例:

def __init__(self, *args, **kwargs):
    if self.instance.user_type == 'pro':
        self._meta.exclude = None

不确定这是否有效。我相信_meta字段是实例化后使用的字段,但我没有验证这一点。如果它不起作用,请尝试撤消这种情况。

def __init__(self, *args, **kwargs):
    if self.instance.user_type != 'pro':
        self._meta.exclude = ('company_name',)

并在模型表单声明中完全删除排除字段。我提到这个替代方案的原因是因为看起来元类(元类的python意义)甚至在调用__init__函数之前就会排除字段。但是如果你声明要在之后排除该字段,它将存在但不会被渲染..也许。我不是100%使用我的python Meta Class知识。祝你好运。

答案 2 :(得分:1)

我似乎找到了解决方案:

def AccountFormCreator(p_fields):
  class AccountForm(forms.ModelForm):
    class Meta:
      model = User
      fields = p_fields 
      widgets = {
        'photo': ImageWidget()
      }
  return AccountForm    
#...
AccountForm = AccountFormCreator( ('email', 'first_name', 'last_name', 'photo', 'region') )
if request.POST.get('acforms', False):
  acform = AccountForm(request.POST, request.FILES, instance=request.u)
  if acform.is_valid():
    u = acform.save()
    u.save()
    ac_saved = True
else:
  acform = AccountForm(instance = request.u)   

答案 3 :(得分:0)

如何从Meta类中删除 exclude =('company_name',)?我认为这就是 save()不保存 company_name 字段的原因