Skip to content

Commit

Permalink
fixes random postgres errors
Browse files Browse the repository at this point in the history
  • Loading branch information
saxix committed Oct 7, 2013
1 parent 7c0d91a commit f9750a8
Show file tree
Hide file tree
Showing 9 changed files with 48 additions and 21 deletions.
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ DJANGO_14=django==1.4.8
DJANGO_15=django==1.5.4
DJANGO_16=https://www.djangoproject.com/m/releases/1.6/Django-1.6b4.tar.gz
DJANGO_DEV=git+git://github.com/django/django.git
DBENGINE=sqlite


mkbuilddir:
mkdir -p ${BUILDDIR}
Expand All @@ -31,10 +31,11 @@ test:

init-db:
@sh -c "if [ '${DBENGINE}' = 'mysql' ]; then mysql -e 'DROP DATABASE IF EXISTS concurrency;'; fi"
@sh -c "if [ '${DBENGINE}' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS concurrency;'; fi"
@sh -c "if [ '${DBENGINE}' = 'mysql' ]; then pip install MySQL-python; fi"
@sh -c "if [ '${DBENGINE}' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS concurrency;'; fi"

@sh -c "if [ '${DBENGINE}' = 'pg' ]; then psql -c 'DROP DATABASE IF EXISTS concurrency;' -U postgres; fi"
@sh -c "if [ '${DBENGINE}' = 'pg' ]; then psql -c 'DROP DATABASE IF EXISTS test_concurrency;' -U postgres; fi"
@sh -c "if [ '${DBENGINE}' = 'pg' ]; then psql -c 'CREATE DATABASE concurrency;' -U postgres; fi"
@sh -c "if [ '${DBENGINE}' = 'pg' ]; then pip install -q psycopg2; fi"

Expand All @@ -48,6 +49,7 @@ ci:
@echo "Database:" ${DBENGINE}

coverage run demo/manage.py test concurrency --noinput --settings=${DJANGO_SETTINGS_MODULE} --failfast
#demo/manage.py test concurrency --settings=${DJANGO_SETTINGS_MODULE} --noinput


coverage: mkbuilddir
Expand Down
2 changes: 2 additions & 0 deletions concurrency/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import operator
import re
from functools import reduce
from django.db import transaction
from django.utils.encoding import force_text
from django.contrib import admin, messages
from django.core.exceptions import ImproperlyConfigured, ValidationError
Expand Down Expand Up @@ -180,6 +181,7 @@ def save_model(self, request, obj, form, change):
super(ConcurrencyListEditableMixin, self).save_model(request, obj, form, change)
except RecordModifiedError:
self._add_conflict(request, obj)

# If policy is set to 'silent' the user will be informed using message_user
# raise Exception if not silent.
# NOTE:
Expand Down
7 changes: 5 additions & 2 deletions concurrency/core.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import absolute_import
import logging
from functools import update_wrapper
from django.db import connections, router
from django.db import connections, router, transaction
from django.utils.translation import ugettext as _
from concurrency.config import conf, CONCURRENCY_POLICY_CALLBACK
from concurrency.exceptions import RecordModifiedError, InconsistencyError
Expand Down Expand Up @@ -63,6 +63,7 @@ def _wrap_model_save(model, force=False):
if force or not model._concurrencymeta._versioned_save:
logger.debug('Wrapping save method of %s' % model)
old_save = getattr(model, 'save')
#print 111.5, getattr(old_save, 'concurrency', None)
setattr(model, 'save', _wrap_save(old_save))
from concurrency.api import get_version, get_object_with_version
#setattr(model._default_manager,
Expand All @@ -74,14 +75,16 @@ def _wrap_model_save(model, force=False):

def _wrap_save(func):
from concurrency.api import concurrency_check

#print 111.1, '_wrap_save'
def inner(self, force_insert=False, force_update=False, using=None, **kwargs):
#print 111.2, self, 'inner'
if self._concurrencymeta.enabled:
concurrency_check(self, force_insert, force_update, using, **kwargs)
return func(self, force_insert, force_update, using, **kwargs)

return update_wrapper(inner, func)

_wrap_save.concurrency=1

# def _versioned_save(self, force_insert=False, force_update=False, using=None):
# if force_insert and force_update:
Expand Down
2 changes: 2 additions & 0 deletions concurrency/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def class_prepared_concurrency_handler(sender, **kwargs):
sender._concurrencymeta.enabled = getattr(sender.ConcurrencyMeta, 'enabled')
sender._concurrencymeta.sanity_check = getattr(sender.ConcurrencyMeta, 'sanity_check')


if not (sender._concurrencymeta._manually):
_wrap_model_save(sender)
from concurrency.api import get_version, get_object_with_version
Expand Down Expand Up @@ -77,6 +78,7 @@ def contribute_to_class(self, cls, name):
cls._concurrencymeta._base = cls
cls._concurrencymeta._manually = self.manually


def _set_version_value(self, model_instance, value):
setattr(model_instance, self.attname, int(value))

Expand Down
9 changes: 5 additions & 4 deletions concurrency/tests/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
from django.test import TransactionTestCase
from django.db import transaction
from django.test import TransactionTestCase, TestCase
import django.core.management
from django.contrib.admin.options import ModelAdmin
from django.contrib.admin.sites import NotRegistered
Expand Down Expand Up @@ -87,8 +88,8 @@ def setUp(self):
is_active=True,
email='sax@example.com',
username='sax')
for i in range(1, 10):
ConcurrentModel.objects.get_or_create(id=i, version=0, dummy_char=str(i))
#for i in range(1, 10):
# ConcurrentModel.objects.get_or_create(id=i, defaults=dict(version=0, dummy_char=str(i)))

admin_register(ConcurrentModel, ActionsModelAdmin)
admin_register(ListEditableConcurrentModel, ListEditableModelAdmin)
Expand All @@ -98,7 +99,7 @@ def setUp(self):

def tearDown(self):
super(AdminTestCase, self).tearDown()

#transaction.rollback()

class DjangoAdminTestCase(TransactionTestCase):
urls = 'concurrency.tests.urls'
Expand Down
3 changes: 3 additions & 0 deletions concurrency/tests/test_admin_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def _create_conflict(self, pk):
u.save()

def test_dummy_action(self):
ConcurrentModel.objects.get_or_create(id=1)
res = self.app.get('/admin/', user='sax')
res = res.click('^ConcurrentModels')
assert 'ConcurrentModel #1' in res # sanity check
Expand All @@ -27,6 +28,7 @@ def test_dummy_action(self):
self.assertNotIn('**action_update**', res)

def test_delete_allowed_if_no_updates(self):
ConcurrentModel.objects.get_or_create(id=1)
res = self.app.get('/admin/', user='sax')
res = res.click('^ConcurrentModels')
assert 'ConcurrentModel #1' in res # sanity check
Expand All @@ -42,6 +44,7 @@ def test_delete_allowed_if_no_updates(self):
self.assertNotIn('ConcurrentModel #1', res)

def test_delete_not_allowed_if_updates(self):
ConcurrentModel.objects.get_or_create(id=1)
res = self.app.get('/admin/', user='sax')
res = res.click('^ConcurrentModels')
assert 'ConcurrentModel #1' in res # sanity check
Expand Down
14 changes: 7 additions & 7 deletions concurrency/tests/test_admin_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@


class TestConcurrentModelAdmin(AdminTestCase):
def setUp(self):
super(TestConcurrentModelAdmin, self).setUp()
assert isinstance(site._registry[ConcurrentModel], ConcurrentModelAdmin)
#def setUp(self):
# super(TestConcurrentModelAdmin, self).setUp()
# assert isinstance(site._registry[ConcurrentModel], ConcurrentModelAdmin)

def test_standard_update(self):
target, __ = ConcurrentModel.objects.get_or_create(dummy_char='aaa')
Expand Down Expand Up @@ -38,8 +38,8 @@ def test_conflict(self):
target, __ = ConcurrentModel.objects.get_or_create(dummy_char='aaa')
url = reverse('admin:concurrency_concurrentmodel_change', args=[target.pk])
res = self.app.get(url, user='sax')
form = res.form

form = res.form
target.save() # create conflict here

res = form.submit()
Expand All @@ -52,9 +52,9 @@ def test_conflict(self):


class TestAdminEdit(AdminTestCase):
def setUp(self):
super(TestAdminEdit, self).setUp()
assert isinstance(site._registry[TestModel1], ModelAdmin)
#def setUp(self):
# super(TestAdminEdit, self).setUp()
# assert isinstance(site._registry[TestModel1], ModelAdmin)

def _create_conflict(self, pk):
u = TestModel1.objects.get(pk=pk)
Expand Down
24 changes: 19 additions & 5 deletions concurrency/tests/test_admin_list_editable.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from django.db import transaction
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.models import ContentType
from django.utils.encoding import force_text
Expand All @@ -11,12 +12,17 @@
class TestListEditable(AdminTestCase):
TARGET = ListEditableConcurrentModel

#def setUp(self):
# super(TestListEditable, self).setUp()


def _create_conflict(self, pk):
u = self.TARGET.objects.get(pk=pk)
u.dummy_char = SENTINEL
u.save()

def test_normal_add(self):
#self.TARGET.objects.get_or_create(pk=1)
res = self.app.get('/admin/', user='sax')
res = res.click(self.TARGET._meta.verbose_name_plural)
res = res.click('Add')
Expand All @@ -25,6 +31,7 @@ def test_normal_add(self):
res = form.submit().follow()

def test_normal_update(self):
self.TARGET.objects.get_or_create(pk=1)
res = self.app.get('/admin/', user='sax')
res = res.click(self.TARGET._meta.verbose_name_plural)
form = res.forms['changelist-form']
Expand All @@ -33,10 +40,11 @@ def test_normal_update(self):
self.assertTrue(self.TARGET.objects.filter(dummy_char='CHAR').exists())

def test_concurrency(self):
self.TARGET.objects.get_or_create(pk=8)

res = self.app.get('/admin/', user='sax')
res = res.click(self.TARGET._meta.verbose_name_plural)

self._create_conflict(1)
self._create_conflict(8)

form = res.forms['changelist-form']
form['form-0-dummy_char'] = 'CHAR'
Expand All @@ -45,6 +53,8 @@ def test_concurrency(self):
self.assertFalse(self.TARGET.objects.filter(dummy_char='CHAR').exists())

def test_message_user(self):
self.TARGET.objects.get_or_create(pk=1)
self.TARGET.objects.get_or_create(pk=2)
res = self.app.get('/admin/', user='sax')
res = res.click(self.TARGET._meta.verbose_name_plural)

Expand All @@ -63,10 +73,12 @@ def test_message_user(self):
messages)

def test_message_user_no_changes(self):
self.TARGET.objects.get_or_create(pk=5)

res = self.app.get('/admin/', user='sax')
res = res.click(self.TARGET._meta.verbose_name_plural)

self._create_conflict(1)
self._create_conflict(5)

form = res.forms['changelist-form']
form['form-0-dummy_char'] = 'CHAR1'
Expand All @@ -79,21 +91,23 @@ def test_message_user_no_changes(self):
self.assertEqual(len(messages), 1)

def test_log_change(self):
self.TARGET.objects.get_or_create(pk=10)

res = self.app.get('/admin/', user='sax')
res = res.click(self.TARGET._meta.verbose_name_plural)
log_filter = dict(user__username='sax',
content_type=ContentType.objects.get_for_model(self.TARGET))

logs = list(LogEntry.objects.filter(**log_filter).values_list('pk', flat=True))

self._create_conflict(1)
self._create_conflict(10)

form = res.forms['changelist-form']
form['form-0-dummy_char'] = 'CHAR1'
res = form.submit('_save').follow()
new_logs = LogEntry.objects.filter(**log_filter).exclude(id__in=logs).exists()
self.assertFalse(new_logs, "LogEntry created even if conflict error")

transaction.rollback()

class TestListEditableWithNoActions(TestListEditable):
TARGET = NoActionsConcurrentModel
2 changes: 1 addition & 1 deletion demo/demoproject/settings_mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
'NAME': 'concurrency',
'HOST': '127.0.0.1',
'PORT': '',
'USER': 'root',
'USER': '',
'PASSWORD': ''}}

0 comments on commit f9750a8

Please sign in to comment.