开发者

field added dynamically to a ModelForm at __init__ does not save

开发者 https://www.devze.com 2023-02-06 19:21 出处:网络
I\'m using Django profiles and was inspired by James Bennett to create a dynamic form (http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/ )

I'm using Django profiles and was inspired by James Bennett to create a dynamic form (http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/ )

What I need is a company field that only shows up on my user profile form when the user_type is 'pro'. Basically my model and form look like:

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

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

And I add the company_name field in init like James Bennett showed:

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(...

The problem is that, when I try to save() an instance of UserProfileForm, the field 'company_name' is not saved...

I have gone around this by calling the field explicitly in the save() method:

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

But I am not happy with this solution (what if there was more fields ? what with Django's beauty ? etc.). It kept me up at night trying to make the modelform aware of the new company_name field at init .

And that's the story of how I ended up on stackoverflow posting this...


I would remove this logic from form and move it to factory. If your logic is in factory, you can have two forms:

  • UserProfileForm
  • ProUserProfileForm

ProUserProfileForm inherits from UserProfileForm and changes only "exclude" constant.

You will have then following factory:

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)


It seems I found a solution:

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)   


When are you expecting the user_type property to be set? This seems like something that should be handled by javascript rather than trying to do funny things with the model form.

If you want the company_name field to appear on the client after they've designated themselves as a pro, then you can 'unhide' the field using javascript.

If instead, they've already been designated a pro user, then use another form that includes the company_name field. You can sub-class the original model form in the following manner.

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

Then in your view, you can decide which form to render:

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=...)

This would be the preferred way to do it in my opinion. It doesn't rely on anything fancy. There is very little code duplication. It is clear, and expected.

Edit: (The below proposed solution does NOT work)

You could try changing the exclude of the meta class, and hope that it uses the instances version of exclude when trying to determine whether to include the field or not. Given an instance of a form:

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

Not sure if that will work or not. I believe that the _meta field is what is used after instantiation, but I haven't verified this. If it doesn't work, try reversing the situation.

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

And remove the exclude fields altogether in the model form declaration. The reason I mention this alternative, is because it looks like the meta class (python sense of Meta Class) will exclude the field even before the __init__ function is called. But if you declare the field to be excluded afterwards, it will exist but not be rendered.. maybe. I'm not 100% with my python Meta Class knowledge. Best of luck.


What about removing exclude = ('company_name',) from Meta class? I'd think that it is the reason why save() doesn't save company_name field

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号