Ticket #2259: 2259_r13817.diff

File 2259_r13817.diff, 11.4 KB (added by Carl Meyer, 14 years ago)
  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    index 3b6e2b7..f1b89ba 100644
    a b class ModelAdmin(BaseModelAdmin):  
    600600        """
    601601        Given a model instance save it to the database.
    602602        """
    603         obj.save()
     603        obj.save(original_pk=form.initial.get(self.model._meta.pk.name))
    604604
    605605    def save_formset(self, request, form, formset, change):
    606606        """
    class ModelAdmin(BaseModelAdmin):  
    680680        """
    681681        opts = obj._meta
    682682        pk_value = obj._get_pk_val()
     683        same_obj_url = "../%s/" % pk_value
    683684
    684685        msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
    685686        if request.POST.has_key("_continue"):
    686687            self.message_user(request, msg + ' ' + _("You may edit it again below."))
    687688            if request.REQUEST.has_key('_popup'):
    688                 return HttpResponseRedirect(request.path + "?_popup=1")
     689                return HttpResponseRedirect(same_obj_url + "?_popup=1")
    689690            else:
    690                 return HttpResponseRedirect(request.path)
     691                return HttpResponseRedirect(same_obj_url)
    691692        elif request.POST.has_key("_saveasnew"):
    692693            msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj}
    693694            self.message_user(request, msg)
    694             return HttpResponseRedirect("../%s/" % pk_value)
     695            return HttpResponseRedirect(same_obj_url)
    695696        elif request.POST.has_key("_addanother"):
    696697            self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
    697698            return HttpResponseRedirect("../add/")
    class ModelAdmin(BaseModelAdmin):  
    875876            return self.add_view(request, form_url='../add/')
    876877
    877878        ModelForm = self.get_form(request, obj)
     879
    878880        formsets = []
    879881        if request.method == 'POST':
    880882            form = ModelForm(request.POST, request.FILES, instance=obj)
  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index b3deda1..1b13a88 100644
    a b class Model(object):  
    420420            return getattr(self, field_name)
    421421        return getattr(self, field.attname)
    422422
    423     def save(self, force_insert=False, force_update=False, using=None):
     423    def save(self, force_insert=False, force_update=False, using=None, original_pk=None):
    424424        """
    425425        Saves the current instance. Override this in a subclass if you want to
    426426        control the saving process.
    class Model(object):  
    428428        The 'force_insert' and 'force_update' parameters can be used to insist
    429429        that the "save" must be an SQL insert or update (or equivalent for
    430430        non-SQL backends), respectively. Normally, they should not be set.
     431
     432        The 'original_pk' argument allows updating the primary key of an
     433        existing row. Assuming the actual instance pk attribute has already
     434        been updated to the new value, passing in the original pk will update
     435        that row.
    431436        """
    432437        if force_insert and force_update:
    433438            raise ValueError("Cannot force both insert and updating in model saving.")
    434         self.save_base(using=using, force_insert=force_insert, force_update=force_update)
     439        if force_insert and original_pk is not None:
     440            raise ValueError("Cannot force insert and provide an original primary key.")
     441        self.save_base(using=using, force_insert=force_insert, force_update=force_update, original_pk=original_pk)
    435442
    436443    save.alters_data = True
    437444
    438445    def save_base(self, raw=False, cls=None, origin=None, force_insert=False,
    439             force_update=False, using=None):
     446            force_update=False, using=None, original_pk=None):
    440447        """
    441448        Does the heavy-lifting involved in saving. Subclasses shouldn't need to
    442449        override this method. It's separate from save() in order to hide the
    class Model(object):  
    483490                return
    484491
    485492        if not meta.proxy:
    486             non_pks = [f for f in meta.local_fields if not f.primary_key]
     493            fields_to_update = meta.local_fields
    487494
    488495            # First, try an UPDATE. If that doesn't update anything, do an INSERT.
    489496            pk_val = self._get_pk_val(meta)
    490497            pk_set = pk_val is not None
     498
     499            if original_pk is not None:
     500                if not pk_set:
     501                    raise ValueError("Cannot update existing row primary key to None")
     502                pk_val = original_pk
     503            else:
     504                fields_to_update = [f for f in fields_to_update if not f.primary_key]
     505
    491506            record_exists = True
    492507            manager = cls._base_manager
    493508            if pk_set:
    class Model(object):  
    495510                if (force_update or (not force_insert and
    496511                        manager.using(using).filter(pk=pk_val).exists())):
    497512                    # It does already exist, so do an UPDATE.
    498                     if force_update or non_pks:
    499                         values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
     513                    if force_update or fields_to_update:
     514                        values = [
     515                            (f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False)))
     516                            for f in fields_to_update
     517                        ]
    500518                        rows = manager.using(using).filter(pk=pk_val)._update(values)
    501519                        if force_update and not rows:
    502520                            raise DatabaseError("Forced update did not affect any rows.")
  • tests/regressiontests/admin_views/tests.py

    diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
    index 725369a..6de4b52 100644
    a b class SaveAsTests(TestCase):  
    326326        response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
    327327        self.assertEqual(response.context['form_url'], '../add/')
    328328
     329class UpdatePrimaryKeyTests(TestCase):
     330    fixtures = ['admin-views-users.xml', 'string-primary-key.xml']
     331    model_class = ModelWithStringPrimaryKey
     332    model_pk = """abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 -_.!~*'() ;/?:@&=+$, <>#%" {}|\^[]`"""
     333
     334    def setUp(self):
     335        self.client.login(username='super', password='secret')
     336        self.instance = self._get_by_pk(self.model_pk)
     337
     338    def tearDown(self):
     339        self.client.logout()
     340
     341    def _get_by_pk(self, pk_val):
     342        return self._manager.get(pk=pk_val)
     343
     344    @property
     345    def _manager(self):
     346        return self.model_class._default_manager
     347
     348    def _pk_name(self):
     349        return self.model_class._meta.pk.attname
     350
     351    def _change_url(self, pk=None):
     352        pk = pk or self.instance.pk
     353        return ('/test_admin/admin/admin_views/%s/%s/'
     354                % (self.model_class._meta.module_name,
     355                   iri_to_uri(quote(pk))))
     356
     357    def test_update_pk(self):
     358        post_data = {
     359            self._pk_name(): 'new pk value',
     360            }
     361        count = self._manager.count()
     362        response = self.client.post(self._change_url(), data=post_data)
     363        self.assertEqual(response.status_code, 302)
     364        self.assertEqual(self._manager.filter(pk='new pk value').count(), 1)
     365        self.assertEqual(self._manager.count(), count)
     366        self.assertFalse(self._manager.filter(pk=self.model_pk).exists())
     367
     368    def test_update_pk_continue_redirect(self):
     369        post_data = {
     370            self._pk_name(): 'new pk value',
     371            '_continue': 'Save and continue editing',
     372            }
     373        response = self.client.post(self._change_url(), data=post_data)
     374        self.assertRedirects(response, self._change_url('new pk value'))
     375
    329376class CustomModelAdminTest(AdminViewBasicTest):
    330377    urlbit = "admin2"
    331378
  • new file tests/regressiontests/model_save/models.py

    diff --git a/tests/regressiontests/model_save/__init__.py b/tests/regressiontests/model_save/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/regressiontests/model_save/models.py b/tests/regressiontests/model_save/models.py
    new file mode 100644
    index 0000000..c9848d0
    - +  
     1from django.db import models
     2
     3class Beverage(models.Model):
     4    id = models.IntegerField(primary_key=True)
     5    name = models.CharField(max_length=100)
     6
     7class Train(models.Model):
     8    name = models.CharField(max_length=100, primary_key=True)
     9
     10class TrainCar(models.Model):
     11    train = models.ForeignKey(Train)
     12    number = models.IntegerField()
  • new file tests/regressiontests/model_save/tests.py

    diff --git a/tests/regressiontests/model_save/tests.py b/tests/regressiontests/model_save/tests.py
    new file mode 100644
    index 0000000..2e6a026
    - +  
     1from django.db import DatabaseError
     2from django.test import TestCase, TransactionTestCase
     3
     4from models import Beverage, Train, TrainCar
     5
     6class UpdatePKTests(TestCase):
     7    def setUp(self):
     8        self.b = Beverage.objects.create(id=1, name='Water')
     9
     10    def test_update_pk(self):
     11        self.b.id = 2
     12        self.b.save(original_pk=1)
     13        self.assertEqual(Beverage.objects.count(), 1)
     14        self.assertEqual(Beverage.objects.filter(id=2).count(), 1)
     15        self.assertEqual(Beverage.objects.filter(id=1).count(), 0)
     16
     17    def test_update_pk_to_none(self):
     18        """
     19        Trying to update a row to a PK of None is totally not legit.
     20
     21        """
     22        self.b.id = None
     23        self.assertRaises(ValueError, self.b.save, original_pk=1)
     24
     25    def test_bogus_original_pk(self):
     26        """
     27        Asking to update a row with a nonexistent original_pk results in a
     28        DatabaseError (same as combining force_update with a nonexistent pk).
     29
     30        """
     31        self.assertRaises(DatabaseError, self.b.save, original_pk=7)
     32
     33    def test_force_insert_and_original_pk(self):
     34        """
     35        Asking to update a row with an original_pk and force_insert is
     36        nonsensical and results in a ValueError.
     37
     38        """
     39        self.b.id = 2
     40        self.assertRaises(ValueError, self.b.save, original_pk=1, force_insert=True)
     41
     42    def test_force_update_and_original_pk(self):
     43        """
     44        Combining force_update and original_pk is just redundant, not
     45        problematic.
     46
     47        """
     48        self.b.id = 2
     49        self.b.save(original_pk=1, force_update=True)
     50        self.assertEqual(Beverage.objects.count(), 1)
     51        self.assertEqual(Beverage.objects.filter(id=2).count(), 1)
     52        self.assertEqual(Beverage.objects.filter(id=1).count(), 0)
     53
     54    def test_update_pk_cascade(self):
     55        """
     56        Updating the primary key of an object that is the referent of
     57        ForeignKeys needs to cascade the update to referring row, or
     58        referential integrity is broken.
     59
     60        XXX This test currently fails because we have neither ON UPDATE CASCADE
     61        support nor any emulation of it.
     62
     63        """
     64        train = Train.objects.create(name='California Zephyr')
     65        car = TrainCar.objects.create(train=train, number=12)
     66
     67        train.name = 'Empire Builder'
     68        train.save(original_pk='California Zephyr')
     69        car = TrainCar.objects.get(pk=car.pk)
     70        self.assertEqual(car.train.name, 'Empire Builder')
Back to Top