diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index 7170924ebb..0504592ebb 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -271,9 +271,7 @@ def create_generic_related_manager(superclass): def create(self, **kwargs): kwargs[self.content_type_field_name] = self.content_type kwargs[self.object_id_field_name] = self.pk_val - obj = self.model(**kwargs) - obj.save() - return obj + return super(GenericRelatedObjectManager, self).create(**kwargs) create.alters_data = True return GenericRelatedObjectManager diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 679f63c995..1763bf2523 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -306,9 +306,8 @@ class ForeignRelatedObjectsDescriptor(object): add.alters_data = True def create(self, **kwargs): - new_obj = self.model(**kwargs) - self.add(new_obj) - return new_obj + kwargs.update({rel_field.name: instance}) + return super(RelatedManager, self).create(**kwargs) create.alters_data = True def get_or_create(self, **kwargs): @@ -410,8 +409,7 @@ def create_many_related_manager(superclass, through=False): # from the method lookup table, as we do with add and remove. if through is not None: raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through - new_obj = self.model(**kwargs) - new_obj.save() + new_obj = super(ManyRelatedManager, self).create(**kwargs) self.add(new_obj) return new_obj create.alters_data = True diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py index e3edf3187e..091f7f32b4 100644 --- a/tests/modeltests/custom_pk/models.py +++ b/tests/modeltests/custom_pk/models.py @@ -6,11 +6,11 @@ By default, Django adds an ``"id"`` field to each model. But you can override this behavior by explicitly adding ``primary_key=True`` to a field. """ -from django.db import models +from django.conf import settings +from django.db import models, transaction, IntegrityError class Employee(models.Model): - employee_code = models.CharField(max_length=10, primary_key=True, - db_column = 'code') + employee_code = models.IntegerField(primary_key=True, db_column = 'code') first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=20) class Meta: @@ -29,50 +29,50 @@ class Business(models.Model): return self.name __test__ = {'API_TESTS':""" ->>> dan = Employee(employee_code='ABC123', first_name='Dan', last_name='Jones') +>>> dan = Employee(employee_code=123, first_name='Dan', last_name='Jones') >>> dan.save() >>> Employee.objects.all() [] ->>> fran = Employee(employee_code='XYZ456', first_name='Fran', last_name='Bones') +>>> fran = Employee(employee_code=456, first_name='Fran', last_name='Bones') >>> fran.save() >>> Employee.objects.all() [, ] ->>> Employee.objects.get(pk='ABC123') +>>> Employee.objects.get(pk=123) ->>> Employee.objects.get(pk='XYZ456') +>>> Employee.objects.get(pk=456) ->>> Employee.objects.get(pk='foo') +>>> Employee.objects.get(pk=42) Traceback (most recent call last): ... DoesNotExist: Employee matching query does not exist. # Use the name of the primary key, rather than pk. ->>> Employee.objects.get(employee_code__exact='ABC123') +>>> Employee.objects.get(employee_code__exact=123) # pk can be used as a substitute for the primary key. ->>> Employee.objects.filter(pk__in=['ABC123','XYZ456']) +>>> Employee.objects.filter(pk__in=[123, 456]) [, ] # The primary key can be accessed via the pk property on the model. ->>> e = Employee.objects.get(pk='ABC123') +>>> e = Employee.objects.get(pk=123) >>> e.pk -u'ABC123' +123 # Or we can use the real attribute name for the primary key: >>> e.employee_code -u'ABC123' +123 # Fran got married and changed her last name. ->>> fran = Employee.objects.get(pk='XYZ456') +>>> fran = Employee.objects.get(pk=456) >>> fran.last_name = 'Jones' >>> fran.save() >>> Employee.objects.filter(last_name__exact='Jones') [, ] ->>> emps = Employee.objects.in_bulk(['ABC123', 'XYZ456']) ->>> emps['ABC123'] +>>> emps = Employee.objects.in_bulk([123, 456]) +>>> emps[123] >>> b = Business(name='Sears') @@ -96,15 +96,52 @@ u'ABC123' >>> Employee.objects.filter(business__pk='Sears') [, ] ->>> Business.objects.filter(employees__employee_code__exact='ABC123') +>>> Business.objects.filter(employees__employee_code__exact=123) [] ->>> Business.objects.filter(employees__pk='ABC123') +>>> Business.objects.filter(employees__pk=123) [] >>> Business.objects.filter(employees__first_name__startswith='Fran') [] # Primary key may be unicode string ->>> emp = Employee(employee_code='jaźń') ->>> emp.save() +>>> bus = Business(name=u'jaźń') +>>> bus.save() + +# The primary key must also obviously be unique, so trying to create a new +# object with the same primary key will fail. +>>> try: +... sid = transaction.savepoint() +... Employee.objects.create(employee_code=123, first_name='Fred', last_name='Jones') +... transaction.savepoint_commit(sid) +... except Exception, e: +... if isinstance(e, IntegrityError): +... transaction.savepoint_rollback(sid) +... print "Pass" +... else: +... print "Fail with %s" % type(e) +Pass """} + +# SQLite lets objects be saved with an empty primary key, even though an +# integer is expected. So we can't check for an error being raised in that case +# for SQLite. Remove it from the suite for this next bit. +if settings.DATABASE_ENGINE != 'sqlite3': + __test__["API_TESTS"] += """ +# The primary key must be specified, so an error is raised if you try to create +# an object without it. +>>> try: +... sid = transaction.savepoint() +... Employee.objects.create(first_name='Tom', last_name='Smith') +... print 'hello' +... transaction.savepoint_commit(sid) +... print 'hello2' +... except Exception, e: +... if isinstance(e, IntegrityError): +... transaction.savepoint_rollback(sid) +... print "Pass" +... else: +... print "Fail with %s" % type(e) +Pass + +""" diff --git a/tests/modeltests/get_or_create/models.py b/tests/modeltests/get_or_create/models.py index f0faf54002..56baa5c1ed 100644 --- a/tests/modeltests/get_or_create/models.py +++ b/tests/modeltests/get_or_create/models.py @@ -16,6 +16,10 @@ class Person(models.Model): def __unicode__(self): return u'%s %s' % (self.first_name, self.last_name) +class ManualPrimaryKeyTest(models.Model): + id = models.IntegerField(primary_key=True) + data = models.CharField(max_length=100) + __test__ = {'API_TESTS':""" # Acting as a divine being, create an Person. >>> from datetime import date @@ -61,4 +65,19 @@ False ... else: ... print "Fail with %s" % type(e) Pass + +# If you specify an existing primary key, but different other fields, then you +# will get an error and data will not be updated. +>>> m = ManualPrimaryKeyTest(id=1, data='Original') +>>> m.save() +>>> try: +... m, created = ManualPrimaryKeyTest.objects.get_or_create(id=1, data='Different') +... except Exception, e: +... if isinstance(e, IntegrityError): +... print "Pass" +... else: +... print "Fail with %s" % type(e) +Pass +>>> ManualPrimaryKeyTest.objects.get(id=1).data == 'Original' +True """}