=== modified file 'django/contrib/auth/admin.py'
--- django/contrib/auth/admin.py	2009-12-09 16:57:23 +0000
+++ django/contrib/auth/admin.py	2010-01-09 16:59:16 +0000
@@ -41,7 +41,7 @@
         if url.endswith('password'):
             return self.user_change_password(request, url.split('/')[0])
         return super(UserAdmin, self).__call__(request, url)
-    
+
     def get_urls(self):
         from django.conf.urls.defaults import patterns
         return patterns('',
@@ -94,7 +94,7 @@
             'save_as': False,
             'username_help_text': self.model._meta.get_field('username').help_text,
             'root_path': self.admin_site.root_path,
-            'app_label': self.model._meta.app_label,            
+            'app_label': self.model._meta.app_label,
         }, context_instance=template.RequestContext(request))
 
     def user_change_password(self, request, id):

=== modified file 'django/contrib/auth/forms.py'
--- django/contrib/auth/forms.py	2010-01-05 03:56:19 +0000
+++ django/contrib/auth/forms.py	2010-01-09 17:26:41 +0000
@@ -1,4 +1,4 @@
-from django.contrib.auth.models import User, UNUSABLE_PASSWORD
+from django.contrib.auth.models import User
 from django.contrib.auth import authenticate
 from django.contrib.auth.tokens import default_token_generator
 from django.contrib.sites.models import Site
@@ -21,12 +21,6 @@
         model = User
         fields = ("username",)
 
-    def clean(self):
-        # Fill the password field so model validation won't complain about it
-        # being blank. We'll set it with the real value below.
-        self.instance.password = UNUSABLE_PASSWORD
-        super(UserCreationForm, self).clean()
-
     def clean_username(self):
         username = self.cleaned_data["username"]
         try:
@@ -40,14 +34,17 @@
         password2 = self.cleaned_data["password2"]
         if password1 != password2:
             raise forms.ValidationError(_("The two password fields didn't match."))
-        self.instance.set_password(password1)
         return password2
 
+    def save(self, *args, **kwargs):
+        self.instance.set_password(self.cleaned_data["password1"])
+        return super(UserCreationForm, self).save(*args, **kwargs)
+
 class UserChangeForm(forms.ModelForm):
     username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^\w+$',
         help_text = _("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."),
         error_message = _("This value must contain only letters, numbers and underscores."))
-    
+
     class Meta:
         model = User
 

=== modified file 'tests/modeltests/model_forms/models.py'
--- tests/modeltests/model_forms/models.py	2010-01-05 03:56:19 +0000
+++ tests/modeltests/model_forms/models.py	2010-01-09 18:04:50 +0000
@@ -1152,7 +1152,7 @@
 >>> class BigIntForm(forms.ModelForm):
 ...     class Meta:
 ...         model = BigInt
-... 
+...
 >>> bif = BigIntForm({'biggie': '-9223372036854775808'})
 >>> bif.is_valid()
 True
@@ -1425,16 +1425,43 @@
 >>> form._errors
 {'__all__': [u'Price with this Price and Quantity already exists.']}
 
-# This form is never valid because quantity is blank=False.
+This Price instance generated by this form is not valid because the quantity
+field is required, but the form is valid because the field is excluded from
+the form. This is for backwards compatibility.
+
 >>> class PriceForm(ModelForm):
 ...     class Meta:
 ...         model = Price
 ...         exclude = ('quantity',)
 >>> form = PriceForm({'price': '6.00'})
 >>> form.is_valid()
+True
+>>> price = form.save(commit=False)
+>>> price.full_validate()
 Traceback (most recent call last):
   ...
-UnresolvableValidationError: {'quantity': [u'This field cannot be null.']}
+ValidationError: {'quantity': [u'This field cannot be null.']}
+
+The form should not validate fields that it doesn't contain even if they are
+specified using 'fields', not 'exclude'.
+
+>>> class PriceForm(ModelForm):
+...     class Meta:
+...         model = Price
+...         fields = ('price',)
+>>> form = PriceForm({'price': '6.00'})
+>>> form.is_valid()
+True
+
+The form should still have an instance of a model that is not complete and
+not saved into a DB yet.
+
+>>> form.instance.price
+Decimal('6.00')
+>>> form.instance.quantity is None
+True
+>>> form.instance.pk is None
+True
 
 # Unique & unique together with null values
 >>> class BookForm(ModelForm):

=== modified file 'tests/regressiontests/admin_views/tests.py'
--- tests/regressiontests/admin_views/tests.py	2010-01-05 18:24:27 +0000
+++ tests/regressiontests/admin_views/tests.py	2010-01-09 17:47:11 +0000
@@ -4,7 +4,8 @@
 import datetime
 from django.core.files import temp as tempfile
 from django.test import TestCase
-from django.contrib.auth.models import User, Permission
+from django.contrib.auth.models import User, Permission, UNUSABLE_PASSWORD
+from django.contrib.auth import admin
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.admin.models import LogEntry, DELETION
 from django.contrib.admin.sites import LOGIN_FORM_KEY
@@ -622,7 +623,7 @@
         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/history/' % quote(self.pk))
         self.assertContains(response, escape(self.pk))
         self.failUnlessEqual(response.status_code, 200)
- 
+
     def test_get_change_view(self):
         "Retrieving the object using urlencoded form of primary key should work"
         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(self.pk))
@@ -1753,3 +1754,37 @@
         self.assertEqual(Post.objects.count(), 2)
         p = Post.objects.order_by('-id')[0]
         self.assertEqual(p.posted, datetime.date.today())
+
+class IncompleteFormTest(TestCase):
+    """
+    Tests validation of a ModelForm that doesn't explicitly have all data
+    corresponding to model fields. Model validation shouldn't fail
+    such a forms.
+    """
+    fixtures = ['admin-views-users.xml']
+
+    def setUp(self):
+        self.client.login(username='super', password='secret')
+
+    def tearDown(self):
+        self.client.logout()
+
+    def test_user_creation(self):
+        response = self.client.post('/test_admin/admin/auth/user/add/', {
+            'username': 'newuser',
+            'password1': 'newpassword',
+            'password2': 'newpassword',
+        })
+        new_user = User.objects.order_by('-id')[0]
+        self.assertRedirects(response, '/test_admin/admin/auth/user/%s/' % new_user.pk)
+        self.assertNotEquals(new_user.password, UNUSABLE_PASSWORD)
+
+    def test_password_mismatch(self):
+        response = self.client.post('/test_admin/admin/auth/user/add/', {
+            'username': 'newuser',
+            'password1': 'newpassword',
+            'password2': 'mismatch',
+        })
+        self.assertEquals(response.status_code, 200)
+        self.assert_('password' not in response.context['form'].errors)
+        self.assertFormError(response, 'form', 'password2', ["The two password fields didn't match."])

