adamnfish
adamnfish

Reputation: 11255

Override Django form field's name attr

I've built a Django form that submits to a page on another domain (that I don't control). The idea is that I have a nicely styled, neatly generated form that fits neatly into my own site and takes the user elsewhere when it is submitted.

However,

Is there a way to specify a different string to use for the 'name' attribute when the field is displayed? Thus, decoupling the HTML form from the class that represents it.

This would need two components:

  1. Override the name value in the widget
  2. Make the form read this value from request.POST when it is bound

I only need step 1 in this case, but step 2 is relevant to solve the problems I listed above more generally

Upvotes: 22

Views: 23219

Answers (5)

Bruno Cordeiro
Bruno Cordeiro

Reputation: 7

It's simple. Just use the attribute 'labels' of the class Meta:

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        labels = {
            'name': 'new_name',
        }

where 'new name' will be the name displayed in the html.

Upvotes: -1

Conan Albrecht
Conan Albrecht

Reputation: 41

I realize this question is old, but here's another (possibly cleaner) way to do it. Most of the answers here involve monkey patching or overriding a superclass method. This does as well, but I think it's a clean way to do it.

Create a new Widget that extends the widget you need, such as TextInput:

class TextInputSansName(forms.TextInput):
    def build_attrs(self, extra_attrs=None, **kwargs):
        if extra_attrs:
            extra_attrs.pop('name', None)
        kwargs.pop('name', None)
        return super().build_attrs(extra_attrs, **kwargs)

Django calls build_attrs on a widget during the render() operation. In the method, I'm removing 'name' if it is in either of the dictionaries. This effectively removes the name just before it renders.

This answer overrides as little as possible of the Django API.

In one of my sites, the payment processor needs inputs to be without names. Here's the control declaration in the form class:

card_number = forms.IntegerField(
    label='Card Number', 
    widget=TextInputSansName(attrs={ 'data-stripe': 'number' })
) 

Cheers.

Upvotes: 1

GaretJax
GaretJax

Reputation: 7780

I've implemented a simple function which overwrites the widget render method and assigns a custom name:

def namedWidget(input_name, widget=forms.CharField):
    if isinstance(widget, type):
        widget = widget()

    render = widget.render

    widget.render = lambda name, value, attrs=None: \
        render(input_name, value, attrs)

    return widget

The usage is simple:

class AliasCreationForm(forms.Form):
    merchant_id = forms.CharField(
        max_length=30,
        widget=namedWidget('PSPID', forms.HiddenInput),
    )

Upvotes: 6

Daniel Roseman
Daniel Roseman

Reputation: 599480

This is a pretty horrible abuse of the API, but there is a form method called add_prefix that is called to determine what the HTML name of each field should be, taking into account the form's prefix if any. You could override that so that it looks up the field name in a dictionary somewhere and returns the name you want - not forgetting to preserve the existing prefix behaviour:

FIELD_NAME_MAPPING = {
    'field1': 'html_field1',
    'field2': 'html_field2'
}

class MyForm(forms.ModelForm):
    def add_prefix(self, field_name):
        # look up field name; return original if not found
        field_name = FIELD_NAME_MAPPING.get(field_name, field_name)
        return super(MyForm, self).add_prefix(field_name)

Upvotes: 35

S.Lott
S.Lott

Reputation: 391818

the name attr is coupled to the name of the property used for the field.

From your description ("If that other form changes the names of any of its fields, I need to change the names of the fields in my form and then change those names everywhere else in my application - since ", "If the remote form uses silly names then my form object must also have properties with silly names which pollutes my application's code.") you recognize this as a fragile design decision.

You should consider that your view function totally solves this problem in a trivial way.

Rather than align names between the remote application and your application, use your view functions to map from your nice names to their horrible names.

This is what view functions are for.

To take it a step further, your view function does three things.

  1. Validate inputs. Perhaps persist them in some local database.
  2. Map data from your form to their request structure.
  3. Make the remote request (via httplib or urllib2 or whatever).

Items 1 and 3 don't change much.

Item 2 is a field-by-field mapping from the request.POST to a dictionary which you then url lib.urlencode to create the POST request. (Or whatever the protocol is.)

So break out item 2 into a flexible thing that you specify in your settings.

settings

MY_MAPPING_FUNCTION = module.function

In your views.py

def submit( request ):
   if request.method == POST:
       form = SomeForm( request.POST )
       if is_valid(form):
           form.save() 
           to_be_submitted = settings.MY_MAPPING_FUNCTION( form )
           remote_post( to_be_submitted ) # or whatever your protocol is

Add the mapping module to your application

module.py

def version_1_2( form ):
    return { 
        'silly_name_1': form.cleaned_data['your_nice_name'], 
        'from': form.cleaned_data['another_nice_name'],
    }

def version_2_1( form ):
    return {
         'much_worse_name': form.cleaned_data['your_nice_name'], 
         'from': form.cleaned_data['another_nice_name'],
    }

Upvotes: 3

Related Questions