10

This is my second post on SO, as well as my second post on Django--please be gentle.

I am working on a simple User model. Users should be able to sign up for the site, and after signing up they should be able to change their account settings upon login. However, if I do the following, Users see a huge form with all kinds of info I don't want them to edit:

views.py

from django.shortcuts import render_to_response, get_object_or_404
from django.core import urlresolvers
from django.http import HttpResponseRedirect
from django.template import RequestContext
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import UserChangeForm

@login_required 
def settings( request, template_name = 'accounts/settings.html' ):
    """
    Processes requests for the settings page, where users
    can edit their profiles.
    """
    page_title = 'Account Settings'
    if request.method == 'POST':
        postdata = request.POST.copy()
        form = UserChangeForm( postdata )
        if form.is_valid():
            form.save()
    else:
        form = UserChangeForm()
    title = 'Settings'
    return render_to_response( template_name, locals(), context_instance = RequestContext( request) )

As if that wasn't bad enough, this form doesn't allow any changes to be made, citing "User with this Username already exists" as an error. This confuses me--I am attempting to save a UserChangeForm, so wouldn't it already existing be a given?

I've been looking online for a while now, attempting making my own custom forms, based on other questions I've seen on SO, like so:

forms.py

class CustomUserChangeForm( UserChangeForm ):
    def __init__( self, *args, **kwargs ):
        super( CustomUserChangeForm, self ).__init__( *args, **kwargs )
        if self.instance and self.instance.pk:
            # Since the pk is set this is not a new instance
            self.fields['username'] = self.instance.username
            self.fields['username'].widgets.attrs['readonly'] = True

Unfortunately this hasn't worked out. I'm kind of at a loss as to what to do, so any help would be greatly appreciated. Below are my urls.py and template:

urls.py

urlpatterns = patterns('appname.accounts.views',

    url(r'^settings/$', 'settings', { 
            'template_name': 'accounts/settings.html' 
        }, 'settings'
    ),
)

template

{% extends "accounts/base.html" %}

{% block main %}
    <h1>Welcome, {{ request.user.pk }}{{ request.user.username }} to accounts.templates.accounts.settings.html.</h1>
    <h2>Here you can update your user settings. You can change your password <a href="{% url change_password %}">here</a>.</h2>
    <form action="." method="post" accept-charset="utf-8">
        {% csrf_token %}
        {{ form.as_p }}
        <p><input type="submit" value="Update &rarr;"></p>
    </form>
{% endblock %}

Thanks in advance for any help!

Community
  • 1
  • 1
Brian Gesiak
  • 6,648
  • 4
  • 35
  • 50

7 Answers7

13

First: to limit which fields are in the form, follow the documentation. There are basically two options to filter fields: fields and exclude.

Second: you are always creating a new user. Initialize form with the User instance, not only you will be able to save a form, but you will have an initial data set.

Your view code should read:

# in the POST:
form = UserChangeForm(request.POST, instance=request.user)

# otherwise
form = UserChangeForm(instance=request.user)

and remove value assignment from the form.py.

Ctrl-C
  • 4,132
  • 1
  • 26
  • 29
Jerzyk
  • 3,662
  • 23
  • 40
  • Thanks for your help! I should have checked the documentation more thoroughly, sorry about that. As per your suggestions I changed the form to take an instance of User. However, it seems like when inheriting UserChangeForm, I can't `exclude` User.username. Regardless of whether I set fields in the Meta, or exclude it specifically, the username field shows up on the form. I am wondering whether it might have something to do with [this ticket](http://code.djangoproject.com/ticket/8620). Your thoughts? – Brian Gesiak Apr 02 '11 at 18:58
  • I've got same issue some time ago... it looks like first `Meta` class definitions in the class-chain is kept without override – Jerzyk Apr 02 '11 at 19:17
  • That doesn't seem to be true. I found out you apparently can't exclude `username` and `password` neither through `exclude` nor `fields`. Any Ideas on how to achieve that? – codingjoe May 02 '13 at 11:45
  • 1
    You can't exclude fields for UserChangeForm – Lutfar Rahman Milu Mar 16 '16 at 17:50
5

I think I'm pretty late on this one, but still I will post my bits for the sake of future readers.

class UserUpdateForm(UserChangeForm):
    password = None

    class Meta:
        model = CustomUser
        fields = ['username', 'email', 'first_name', 'last_name', 'description', ]
        widgets = {
            'description': forms.Textarea(attrs={'rows': 3}),
            'username': forms.TextInput(attrs={'readonly': 'readonly'}),
            'email': forms.TextInput(attrs={'readonly': 'readonly'})
        }

This will create a custom form with username and email fields as readonly as you do not want your users to change these unique fields once the user account has been created. It also prevents the password field to be displayed as it does not make sense to show hased password on profile page.

Rohit
  • 3,659
  • 3
  • 35
  • 57
4

This needs to be updated: You cannot exclude fields in UserChangeForm()

Too bad.

Rob L
  • 3,634
  • 2
  • 19
  • 38
4

Three things. First of all, you should set self.fields['username'] to a form field, like forms.CharField(initial=self.instance.username). Second of all, you're using UserChangeForm instead of CustomUserChangeForm, in your view. And thirdly, http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#using-a-subset-of-fields-on-the-form (a proper link now, not one to someone's hard drive...

gcbirzan
  • 1,494
  • 11
  • 17
1

The way it worked for me was to add a form class (in forms.py) overriding the UserChangeForm:

from django.contrib.auth.forms import UserChangeForm    

class UserChangeForm(UserChangeForm):
"""Overriding visible fields."""
    class Meta:
        model = User
        fields = ('username', 'password', 'email', 'first_name', 'last_name',)

Finally import this version on views.py instead of the contrib.auth.forms

Oirad
  • 51
  • 1
  • 3
1

It looks like you have not read the documentation carefully.

Let me help you with that, check out using-a-subset-of-fields-on-the-form.

Basically you just need to add exclude = ('fieldname',) to your modelforms meta.

Jerzyk
  • 3,662
  • 23
  • 40
Myth
  • 1,562
  • 5
  • 15
  • 25
1

I also was facing a problem to exclude the password field and that's how I landed here.

And after checking code of some good projects I found a way to exclude the password field (without using exclude)

Just put password = None like shown in the code below.

from django.contrib.auth.forms import UserChangeForm    

class CustomUserChangeForm(UserChangeForm):

    # make the password as None for removing the password field from the form
    password = None

    class Meta:
        model = User

        # select the fields that you want to display
        fields = ('email', 'first_name', 'last_name',)
Nijo Ninan
  • 26
  • 4