Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending forms in django?

I recently tried extending django's registration form with the following but i can only see the default four fields. IS there anything i'm missing?

Or should i be creating my own registration backend if i were to create a custom form?

class RegistrationForm(forms.Form):

    username = forms.RegexField(regex=r'^\w+$',
                                max_length=30,
                                widget=forms.TextInput(attrs=attrs_dict),
                                label=_(u'Username'))
    email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
                                                               maxlength=75)),
                             label=_(u'Email address'))
    first_name =forms.CharField(widget=forms.TextInput(attrs=attrs_dict),label=_(u'First Name')) 
    last_name =forms.CharField(widget=forms.TextInput(attrs=attrs_dict),label=_(u'Last Name'))
    password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
                                label=_(u'Password'))
    password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
                                label=_(u'Password (again)'))
    keywords = forms.ModelMultipleChoiceField(queryset=Keyword.objects.all())
    #keywords = forms.ModelChoiceField(queryset=Keyword.objects.all())

    def clean_username(self):
        try:
            user = User.objects.get(username__iexact=self.cleaned_data['username'])
        except User.DoesNotExist:
            return self.cleaned_data['username']
        raise forms.ValidationError(_(u'This username is already taken. Please choose another.'))

    def clean(self):
        if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
            if self.cleaned_data['password1'] != self.cleaned_data['password2']:
                raise forms.ValidationError(_(u'You must type the same password each time'))
        return self.cleaned_data

    def save(self, profile_callback=None):
        new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],password=self.cleaned_data['password1'],email=self.cleaned_data['email'],profile_callback=profile_callback)
    new_profile = UserProfile(user=new_user,username=self.cleaned_data['username'], keywords_subscribed=self.cleaned_data['keywords'],first_name=self.cleaned_data['first_name'],last_name=self.cleaned_data['last_name'],email=self.cleaned_data['email'])
    new_profile.save()       
        return new_user

Added the template code:

The template code is added for reference.

It's referencing from the forms.py in the registration module

<html>
    <body>
        <div id="popupLayer_login" style="visibility: visible; position: fixed;">
            <div id="content-home" style="width: 700px; margin-left: -300px; top: 60px; position: fixed;">
                <br />
                {% block title %}<h2 style="margin: 0px; margin-bottom: 20px; text-align: center">Register for an account</h2>{% endblock %}
                {% block content %}
                <table style="margin-left: 100px; width: 500px;">
                    <tbody>
                        <form method='post' action=''>
                            {% csrf_token %}
                            {{ form }}
                            <tr>
                                <td style="border-width: 0px;"></td>
                                <td style="border-width: 0px;">
                                <input type="submit" value="Send activation email" />
                                </td>
                            </tr>
                        </form>
                    </tbody>
                </table>
                {% endblock %}
            </div>
        </div>
    </body>
</html>

This is my urls.py

urlpatterns = patterns('',
                       # Activation keys get matched by \w+ instead of the more specific
                       # [a-fA-F0-9]{40} because a bad activation key should still get to the view;
                       # that way it can return a sensible "invalid key" message instead of a
                       # confusing 404.
                       url(r'^activate/(?P<activation_key>\w+)/$',
                           activate,
                           name='registration_activate'),
                       url(r'^login/$',
                           auth_views.login,
                           {'template_name': 'registration/login.html'},
                           name='auth_login'),
                       url(r'^logout/$',
                           auth_views.logout,
                           {'template_name': 'registration/logout.html'},
                           name='auth_logout'),
                       url(r'^password/change/$',
                           auth_views.password_change,
                           name='auth_password_change'),
                       url(r'^password/change/done/$',
                           auth_views.password_change_done,
                           name='auth_password_change_done'),
                       url(r'^password/reset/$',
                           auth_views.password_reset,
                           name='auth_password_reset'),
                       url(r'^password/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
                           auth_views.password_reset_confirm,
                           name='auth_password_reset_confirm'),
                       url(r'^password/reset/complete/$',
                           auth_views.password_reset_complete,
                           name='auth_password_reset_complete'),
                       url(r'^password/reset/done/$',
                           auth_views.password_reset_done,
                           name='auth_password_reset_done'),
                       url(r'^register/$',
                           register,
                           name='registration_register'),
                       url(r'^register/complete/$',
                           direct_to_template,
                           {'template': 'registration/registration_complete.html'},
                           name='registration_complete'),
                       )

and my views.py

def register(request, success_url=None,
             form_class=RegistrationForm, profile_callback=None,
             template_name='registration/registration_form.html',
             extra_context=None):
    if request.method == 'POST':
        form = form_class(data=request.POST, files=request.FILES)
        if form.is_valid():
            new_user = form.save(profile_callback=profile_callback)
            # success_url needs to be dynamically generated here; setting a
            # a default value using reverse() will cause circular-import
            # problems with the default URLConf for this application, which
            # imports this file.
            return HttpResponseRedirect(success_url or reverse('registration_complete'))
    else:
        form = form_class()

    if extra_context is None:
        extra_context = {}
    context = RequestContext(request)
    for key, value in extra_context.items():
        context[key] = callable(value) and value() or value
    return render_to_response(template_name,
                              { 'form': form },
                              context_instance=context)
like image 326
Stanwin Siow Avatar asked Jan 18 '23 08:01

Stanwin Siow


1 Answers

Actually, you are not supposed to modify the code of an external app, unless you have a really good reason to - which apparently this case doesn't. Because that's called a fork and requires more maintenance: they do an update, you'd have to reflect the updates.

You should always try to reuse the external app without touching its code. In this case, it is perfectly possible to extend the registration form without touching their code. That said, it requires a little voodoo. Note that this works for any sane app:

  1. Check for a form_class argument in the view signature, the view in question has such a signature: request(request, success_url=None, form_class=RegistrationForm, profile_callback=None, template_name='registration/registration_form.html', extra_context=None). That's pretty cool, it means that you can reuse the view with different success urls, profile callbacks, templates, extra context and most importantly in your case: form_class.

  2. Subclass the form, create another form which inherits from RegistrationForm

  3. Override the URL to pass your form class, create another url which passes your form class

Create a forms.py in your project directory:

from django import forms

from registration.forms import RegistrationForm

class ProjectSpecificRegistrationForm(RegistrationForm):
    keywords = forms.ModelMultipleChoiceField(queryset=Keyword.objects.all())
    first_name =forms.CharField(widget=forms.TextInput(attrs=attrs_dict),label=_(u'First Name')) 
    last_name =forms.CharField(widget=forms.TextInput(attrs=attrs_dict),label=_(u'Last Name'))

Then, in your urls.py, you should have something like:

urlpatterns = patterns('',
    url(r'registration/', include('registration.urls'),
)

Override the url named "registration_register" with absolute path /registration/register/ url as such:

import forms

urlpatterns = patterns('',
    url(r'^registration/register/$', 'views.registration.register', {
        'form_class': forms.ProjectSpecificRegistrationForm}, 'registration_register'),
    url(r'^registration/', include('registration.urls'),
)

What's going on here

The url() function has such a signature: url(regex, view, kwargs=None, name=None, prefix=''). In the above definition, we are passing a dict with form_class to kwargs. So the view will be called with form_class=your form class. It's quite interesting really because you could also add extra context like:

    url(r'^registration/register/$', 'views.registration.register', {
        'form_class': forms.ProjectSpecificRegistrationForm,
        # provided that you imported SomeModel
        'extra_context':  {'models': SomeModel.objects.all()}}, 'registration_register'),

Anyway next time you open /registration/register/, it will use your url, which passes your form class.

Note that you could also create a an app like project_specific where you'd put all the code that is really specific to your project and has no reason to be reused.

like image 183
3 revs Avatar answered Jan 28 '23 04:01

3 revs