• No se han encontrado resultados

5. Lineamientos para la implementación de la NTP-ISO 2451

5.3. Tabla para los métodos de ensayo

There are actually several ways to create the transaction in Django 1.6. Let’s go through a couple.

The recommended way

According to Django 1.6 documentation, atomic can be used as both a decorator or as a

context_manager. So if we use it as a context manager, the code in our register function would look like this:

1 def register(request): 2 3 ... snip ... 4 5 cd = form.cleaned_data 6 try: 7 with transaction.atomic():

8 user = User.create(cd['name'], cd['email'], cd['password'], 9 cd['last_4_digits'], stripe_id="") 10 11 if customer: 12 user.stripe_id = customer.id 13 user.save() 14 else:

15 UnpaidUsers(email=cd['email']).save() 16

17 except IntegrityError: 18 import traceback

19 form.addError(cd['email'] + ' is already a member' +

20 traceback.format_exc())

21 user = None

22 else:

23 request.session['user'] = user.pk 24 return HttpResponseRedirect('/') 25

26 ... snip ...

1 from django.db import transaction

Note the line with transaction.atomic():. All code inside that block will be executed

inside a transaction. Re-run our tests, and they all pass!

Using a decorator

We can also try addingatomicas a decorator. But if we do and rerun our tests… they fail

with the same error we had before putting any transactions in at all! Why is that?

Why didn’t the transaction roll back correctly? The reason is becausetransaction.atomic

is looking for some sort of DatabaseError and, well, we caught that error (e.g., the

IntegrityErrorin our try/except block), sotransaction.atomicnever saw it and thus

the standardAUTOCOMMITfunctionality took over.

But removing the try/except will cause the exception to just be thrown up the call chain and most likely blow up somewhere else, so we can’t do that either.

The trick is to put the atomic context manager inside of the try/except block, which is what we did in our first solution.

Looking at the correct code again:

1 from django.db import transaction 2

3 try:

4 with transaction.atomic():

5 user = User.create(cd['name'], cd['email'], cd['password'], 6 cd['last_4_digits'], stripe_id="") 7 8 if customer: 9 user.stripe_id = customer.id 10 user.save() 11 else:

12 UnpaidUsers(email=cd['email']).save() 13

14 except IntegrityError: 15 import traceback

16 form.addError(cd['email'] + ' is already a member' +

When UnpaidUsers fires the IntegrityError, the transaction.atomic() con- text_manager will catch it and perform the rollback. By the time our code executes in

the exception handler (e.g., the form.addError line), the rollback will be complete and

we can safely make database calls if necessary. Also note any database calls before or after the transaction.atomic() context manager will be unaffected regardless of the final outcome of the context_manager.

Transaction per HTTP Request

Django < 1.6 (like 1.5) also allows you to operate in a “Transaction per request” mode. In this mode, Django will automatically wrap your view function in a transaction. If the func- tion throws an exception, Django will roll back the transaction, otherwise it will commit the transaction.

To get it set up you have to setATOMIC_REQUESTto True in the database configuration for

each database that you want to have this behavior. In oursettings.pywe make the change

like this:

1 DATABASES = { 2 'default': {

3 'ENGINE': 'django.db.backends.sqlite3', 4 'NAME': os.path.join(SITE_ROOT, 'test.db'), 5 'ATOMIC_REQUEST': True,

6 }

7 }

But in practice this just behaves exactly as if you put the decorator on your view function yourself, so it doesn’t serve our purposes here. It is however worthwhile to note that with bothAUTOMIC_REQUESTS and [email protected] decorator it is possible to still catch and then handle those errors after they are thrown from the view. In order to catch those errors you would have to implement some custom middleware, or you could override

SavePoints

We can also further break down transactions into savepoints. Think of savepoints as partial transactions. If you have a transaction that takes four database statements to complete, you could create a savepoint after the second statement. Once that savepoint is created, then if the 3rd or 4th statements fails you can do a partial rollback, getting rid of the 3rd and 4th statement but keeping the first two.

It’s basically like splitting a transaction into smaller lightweight transactions, allowing you to do partial rollbacks or commits. But do keep in mind if the main transaction were to get

rolled back (perhaps because of anIntegrityErrorthat gets raised but not caught), then

all savepoints will get rolled back as well.

Let’s look at an example of how savepoints work:

1 @transaction.atomic()

2 def save_points(self,save=True): 3

4 user = User.create('jj','inception','jj','1234') 5 sp1 = transaction.savepoint()

6

7 user.name = 'staring down the rabbit hole' 8 user.stripe_id = 4 9 user.save() 10 11 if save: 12 transaction.savepoint_commit(sp1) 13 else: 14 transaction.savepoint_rollback(sp1)

Here the entire function is in a transaction. After creating a new user we create a savepoint and get a reference to the savepoint. The next three statements:

1 user.name = 'staring down the rabbit hole' 2 user.stripe_id = 4

3 user.save()

Are not part of the existing savepoint, so they stand the potential of being part of the next

savepoint_rollback, orsavepoint_commit. In the case of a savepoint_rollback.

The lineuser = User.create('jj','inception','jj','1234')will still be commit-

ted to the database even though the rest of the updates won’t.

1 def test_savepoint_rollbacks(self): 2

3 self.save_points(False) 4

5 #verify that everything was stored

6 users = User.objects.filter(email="inception") 7 self.assertEquals(len(users), 1)

8

9 #note the values here are from the original create call 10 self.assertEquals(users[0].stripe_id, '') 11 self.assertEquals(users[0].name, 'jj') 12 13 14 def test_savepoint_commit(self): 15 self.save_points(True) 16

17 #verify that everything was stored

18 users = User.objects.filter(email="inception") 19 self.assertEquals(len(users), 1)

20

21 #note the values here are from the update calls 22 self.assertEquals(users[0].stripe_id, '4')

23 self.assertEquals(users[0].name, 'staring down the rabbit hole')

After we commit or rollback a savepoint, we can continue to do work in the same transaction, and that work will be unaffected by the outcome of the previous savepoint.

For example, if we update oursave_pointsfunction as such:

1 @transaction.atomic()

2 def save_points(self,save=True): 3

4 user = User.create('jj','inception','jj','1234') 5 sp1 = transaction.savepoint()

6

7 user.name = 'staring down the rabbit hole' 8 user.save()

9

10 user.stripe_id = 4 11 user.save()

13 if save:

14 transaction.savepoint_commit(sp1) 15 else:

16 transaction.savepoint_rollback(sp1) 17

18 user.create('limbo','illbehere@forever','mind blown',

19 '1111')

Now regardless of whethersavepoint_commitorsavepoint_rollback was called, the

“limbo” user will still be created successfully, unless something else causes the entire trans- action to be rolled-back.

Documento similar