Ticket #2259: updatable_pk.diff

File updatable_pk.diff, 15.0 KB (added by akaariai, 4 years ago)
  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    index cf7ea83..81a7772 100644
    a b class ModelAdmin(BaseModelAdmin): 
    793793            verbose_name = opts_.verbose_name
    794794
    795795        pk_value = obj._get_pk_val()
     796        same_obj_url = "../%s/" % pk_value
    796797
    797798        msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(verbose_name), 'obj': force_unicode(obj)}
    798799        if "_continue" in request.POST:
    799800            self.message_user(request, msg + ' ' + _("You may edit it again below."))
    800801            if "_popup" in request.REQUEST:
    801                 return HttpResponseRedirect(request.path + "?_popup=1")
     802                return HttpResponseRedirect(same_obj_url + "?_popup=1")
    802803            else:
    803                 return HttpResponseRedirect(request.path)
     804                return HttpResponseRedirect(same_obj_url)
    804805        elif "_saveasnew" in request.POST:
    805806            msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(verbose_name), 'obj': obj}
    806807            self.message_user(request, msg)
    807             return HttpResponseRedirect("../%s/" % pk_value)
     808            return HttpResponseRedirect(same_obj_url)
    808809        elif "_addanother" in request.POST:
    809810            self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(verbose_name)))
    810811            return HttpResponseRedirect("../add/")
  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 71fd1f7..ddf7f4c 100644
    a b class ModelState(object): 
    271271        # Necessary for correct validation of new instances of objects with explicit (non-auto) PKs.
    272272        # This impacts validation only; it has no effect on the actual save.
    273273        self.adding = True
     274        # This if set when an object is fetched from the database and used in saving when the pk
     275        # has been changed.
     276        self.original_pk = None
    274277
    275278class Model(object):
    276279    __metaclass__ = ModelBase
    class Model(object): 
    520523            pk_set = pk_val is not None
    521524            record_exists = True
    522525            manager = cls._base_manager
     526            original_pk = self._state.original_pk
     527            pk_changed = original_pk is not None and original_pk <> pk_val
     528            use_original_pk = False
    523529            if pk_set:
    524                 # Determine whether a record with the primary key already exists.
    525                 if (force_update or (not force_insert and
    526                         manager.using(using).filter(pk=pk_val).exists())):
    527                     # It does already exist, so do an UPDATE.
     530                if force_insert:
     531                    record_exists = False
     532                # Determine whether a record with our current primary key already exists.
     533                elif (force_update and not pk_changed) or manager.using(using).filter(pk=pk_val).exists():
     534                    # It does already exist, so we are UPDATEing.
     535                   
     536                    # If original PK is different from current PK, then we are trying to change
     537                    # our PK to an already existing one. This is an error if force_update
     538                    # is not set.
     539                    if pk_changed and not force_update:
     540                        raise DatabaseError("Trying to change the primary key to already existing value. If this is wanted, use force_update") 
    528541                    if force_update or non_pks:
    529542                        values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
    530543                        rows = manager.using(using).filter(pk=pk_val)._update(values)
    531544                        if force_update and not rows:
    532545                            raise DatabaseError("Forced update did not affect any rows.")
     546                        if not rows:
     547                            raise DatabaseError('And existing row with pk "%s" was changed concurrently' % pk_val)
     548                # There was no record with our current primary key. Lets see if our primary
     549                # key has changed, and there is a record with the original primary key.
     550                elif pk_changed and manager.using(using).filter(pk=original_pk).exists():
     551                    # So we are changing pk and the old one exists still
     552                    # in the DB. Lets update it.
     553                    # TODO: related object handling.
     554                    values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in meta.local_fields]
     555                    rows = manager.using(using).filter(pk=original_pk)._update(values)
     556                    # TODO: I am could be a little more DRY
     557                    if force_update and not rows:
     558                        raise DatabaseError("Forced update did not affect any rows.")
     559                    if not rows:
     560                        raise DatabaseError('An existing row with pk %s was changed concurrently.' % original_pk)
     561                # Neither the old nor the new pk was found from the DB.
     562                elif force_update:
     563                    raise DatabaseError("Forced update did not affect any rows.")
    533564                else:
    534565                    record_exists = False
    535566            if not pk_set or not record_exists:
    class Model(object): 
    560591                    result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True, using=using)
    561592
    562593                if update_pk:
     594                    pk_val = result
    563595                    setattr(self, meta.pk.attname, result)
    564596            transaction.commit_unless_managed(using=using)
    565597
    class Model(object): 
    567599        self._state.db = using
    568600        # Once saved, this is no longer a to-be-added instance.
    569601        self._state.adding = False
     602        self._state.original_pk = pk_val
    570603
    571604        # Signal that the save is complete
    572605        if origin and not meta.auto_created:
  • django/db/models/query.py

    diff --git a/django/db/models/query.py b/django/db/models/query.py
    index ff5289c..88fcc38 100644
    a b class QuerySet(object): 
    281281                obj._state.db = db
    282282                # This object came from the database; it's not being added.
    283283                obj._state.adding = False
     284                obj._state.original_pk = obj.pk
    284285
    285286            if extra_select:
    286287                for i, k in enumerate(extra_select):
    def get_cached_row(klass, row, index_start, using, max_depth=0, cur_depth=0, 
    12311232    if obj:
    12321233        obj._state.db = using
    12331234        obj._state.adding = False
     1235        obj._state.original_pk = obj.pk
    12341236
    12351237    index_end = index_start + field_count + offset
    12361238    # Iterate over each related object, populating any
    class RawQuerySet(object): 
    13811383
    13821384            instance._state.db = db
    13831385            instance._state.adding = False
     1386            instance._state.original_pk = instance.pk
    13841387
    13851388            yield instance
    13861389
  • django/db/models/query_utils.py

    diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
    index a56ab5c..42f088d 100644
    a b class DeferredAttribute(object): 
    9595            # We use only() instead of values() here because we want the
    9696            # various data coersion methods (to_python(), etc.) to be called
    9797            # here.
    98             val = getattr(
    99                 cls._base_manager.filter(pk=instance.pk).only(name).using(
    100                     instance._state.db).get(),
    101                 self.field_name
    102             )
     98            try:
     99                val = getattr(
     100                    cls._base_manager.filter(pk=instance.pk).only(name).using(
     101                        instance._state.db).get(),
     102                    self.field_name
     103                )
     104            except cls.DoesNotExist, e:
     105                if (instance._state.original_pk and
     106                        instance._state.original_pk <> instance.pk):
     107                    val = getattr(
     108                        cls._base_manager.filter(pk=instance._state.original_pk).only(name).using(
     109                            instance._state.db).get(),
     110                        self.field_name
     111                    )
     112                else:
     113                    raise e
    103114            data[self.field_name] = val
    104115        return data[self.field_name]
    105116
  • tests/modeltests/custom_pk/tests.py

    diff --git a/tests/modeltests/custom_pk/tests.py b/tests/modeltests/custom_pk/tests.py
    index 6b94b6d..b07e91c 100644
    a b  
    11# -*- coding: utf-8 -*-
    2 from django.db import transaction, IntegrityError
     2from django.db import transaction, IntegrityError, DatabaseError
    33from django.test import TestCase, skipIfDBFeature
    44
    55from models import Employee, Business, Bar, Foo
    class CustomPKTests(TestCase): 
    3535            lambda: Employee.objects.get(pk=42)
    3636        )
    3737
     38        # lets change Fran's employee_code, and see that the old row has
     39        # been changed.
     40        fran.employee_code = 42
     41        fran.save()
     42        self.assertEqual(Employee.objects.get(pk=42), fran)
     43        self.assertEqual(2, Employee.objects.count())
     44        # Change the employee code back.
     45        fran.employee_code = 456
     46        fran.save()
     47        self.assertEqual(Employee.objects.get(pk=456), fran)
     48        self.assertEqual(2, Employee.objects.count())
     49        # Lets see what happens when we take Fran from queryset
     50        # and change pk.
     51        fran2 = Employee.objects.get(pk=456)
     52        fran2.employee_code = 42
     53        fran2.save()
     54        self.assertEqual(Employee.objects.get(pk=42), fran2)
     55        self.assertEqual(2, Employee.objects.count())
     56        fran2.employee_code = 456
     57        fran2.save()
     58        # fran2 can also come from a raw query.
     59        fran2 = Employee.objects.raw("""
     60            select code
     61              from custom_pk_employee
     62             where code = 456
     63        """)[0]
     64        fran2.employee_code = 42
     65        fran2.save()
     66        # Deferred fran is not equivalent to fran, so check names
     67        self.assertEqual(Employee.objects.get(pk=42).first_name, fran2.first_name)
     68        self.assertEqual(2, Employee.objects.count())
     69        fran2.employee_code = 456
     70        fran2.save()
     71       
     72        # Lets see what happens if we try to save fran as dan
     73        try:
     74            fran.employee_code = 123
     75            fran.save()
     76            # Probably a better way exists, but there should
     77            # have been an exception...
     78            self.assertTrue(False)
     79        except DatabaseError:
     80            pass
     81        # lets force_insert fran as 234
     82        fran.employee_code = 234
     83        fran.save(force_insert=True)
     84        # now we have duplicated fran.
     85        self.assertEqual(3, Employee.objects.count())
     86        self.assertEqual(Employee.objects.get(pk=234), fran)
     87        # we can force_update dan to be fran
     88        dan.employee_code = 234
     89        dan.save(force_update=True)
     90        # The old dan did not go anywhere, we forcefully updated
     91        # the row with pk=234, but the old record is not deleted.
     92        self.assertEqual(3, Employee.objects.count())
     93        self.assertEqual(
     94            Employee.objects.get(pk=234).first_name,
     95            Employee.objects.get(pk=123).first_name
     96        )
     97        dan.delete()
     98        dan = Employee.objects.get(pk=123)
     99       
     100     
     101
     102
     103
    38104        # Use the name of the primary key, rather than pk.
    39105        self.assertEqual(Employee.objects.get(employee_code=123), dan)
    40106        # pk can be used as a substitute for the primary key.
  • tests/modeltests/one_to_one/tests.py

    diff --git a/tests/modeltests/one_to_one/tests.py b/tests/modeltests/one_to_one/tests.py
    index c3e1704..cbd6389 100644
    a b class OneToOneTests(TestCase): 
    2424        # Set the place using assignment notation. Because place is the primary
    2525        # key on Restaurant, the save will create a new restaurant
    2626        self.r.place = self.p2
    27         self.r.save()
     27        self.r.save(force_insert=True)
    2828        self.assertEqual(repr(self.p2.restaurant), '<Restaurant: Ace Hardware the restaurant>')
    2929        self.assertEqual(repr(self.r.place), '<Place: Ace Hardware the place>')
    3030        self.assertEqual(self.p2.pk, self.r.pk)
  • tests/modeltests/signals/tests.py

    diff --git a/tests/modeltests/signals/tests.py b/tests/modeltests/signals/tests.py
    index 9b8bce0..12967a0 100644
    a b class SignalTests(TestCase): 
    123123        ])
    124124        data[:] = []
    125125
     126       
     127        p2 = Person(first_name="James", last_name="Jones")
    126128        p2.id = 99998
    127129        p2.save()
    128130        self.assertEqual(data, [
  • tests/regressiontests/admin_views/tests.py

    diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
    index 8daa466..098aabf 100644
    a b class SaveAsTests(TestCase): 
    587587        response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
    588588        self.assertEqual(response.context['form_url'], '../add/')
    589589
     590class UpdatePrimaryKeyTests(TestCase):
     591    fixtures = ['admin-views-users.xml', 'string-primary-key.xml']
     592    model_class = ModelWithStringPrimaryKey
     593    model_pk = """abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 -_.!~*'() ;/?:@&=+$, <>#%" {}|\^[]`"""
     594
     595    def setUp(self):
     596        self.client.login(username='super', password='secret')
     597        self.instance = self._get_by_pk(self.model_pk)
     598
     599    def tearDown(self):
     600        self.client.logout()
     601
     602    def _get_by_pk(self, pk_val):
     603        return self._manager.get(pk=pk_val)
     604
     605    @property
     606    def _manager(self):
     607        return self.model_class._default_manager
     608
     609    def _pk_name(self):
     610        return self.model_class._meta.pk.attname
     611
     612    def _change_url(self, pk=None):
     613        pk = pk or self.instance.pk
     614        return ('/test_admin/admin/admin_views/%s/%s/'
     615                % (self.model_class._meta.module_name,
     616                   iri_to_uri(quote(pk))))
     617
     618    def test_update_pk(self):
     619        post_data = {
     620            self._pk_name(): 'new pk value',
     621            }
     622        count = self._manager.count()
     623        response = self.client.post(self._change_url(), data=post_data)
     624        self.assertEqual(response.status_code, 302)
     625        self.assertEqual(self._manager.filter(pk='new pk value').count(), 1)
     626        self.assertEqual(self._manager.count(), count)
     627        self.assertFalse(self._manager.filter(pk=self.model_pk).exists())
     628
     629    def test_update_pk_continue_redirect(self):
     630        post_data = {
     631            self._pk_name(): 'new pk value',
     632            '_continue': 'Save and continue editing',
     633            }
     634        response = self.client.post(self._change_url(), data=post_data)
     635        self.assertRedirects(response, self._change_url('new pk value'))
     636 
    590637class CustomModelAdminTest(AdminViewBasicTest):
    591638    urlbit = "admin2"
    592639
Back to Top