Skip to content

Commit

Permalink
bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
saxix committed Nov 5, 2015
1 parent f0479ad commit bc6ced6
Show file tree
Hide file tree
Showing 16 changed files with 148 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
˜*
.appveyor.yml
.coverage
coverage.xml
~build
Expand All @@ -19,4 +20,3 @@ docs/build/
*.pyc
*.egg-info
*.sqlite
*/settings/active.py
33 changes: 30 additions & 3 deletions src/concurrency/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
from functools import update_wrapper

from django.db.models.fields import Field
try:
from django.apps import apps
get_model = apps.get_model
except ImportError:
from django.db.models.loading import get_model
from django.utils.translation import ugettext_lazy as _

from concurrency import forms
Expand Down Expand Up @@ -49,7 +54,27 @@ def post_syncdb_concurrency_handler(sender, **kwargs):

class_prepared.connect(class_prepared_concurrency_handler, dispatch_uid='class_prepared_concurrency_handler')

_TRIGGERS = []
class TriggerRegistry(object):
# FIXME: this is very bad. it seems required only by tests
# see
# https://github.com/pytest-dev/pytest-django/issues/75
# https://code.djangoproject.com/ticket/22280#comment:20

_fields = []

def append(self, field):
self._fields.append([field.model._meta.app_label, field.model.__name__])

def __iter__(self):
return iter([get_model(*i)._concurrencymeta.field for i in self._fields])

def __contains__(self, field):
target = [field.model._meta.app_label, field.model.__name__]
return target in self._fields

_TRIGGERS = TriggerRegistry()
# _TRIGGERS = []

if not conf.MANUAL_TRIGGERS:
post_migrate.connect(post_syncdb_concurrency_handler, dispatch_uid='post_syncdb_concurrency_handler')

Expand Down Expand Up @@ -193,12 +218,14 @@ class TriggerVersionField(VersionField):

def __init__(self, *args, **kwargs):
self._trigger_name = kwargs.pop('trigger_name', None)
self._trigger_exists = False
super(TriggerVersionField, self).__init__(*args, **kwargs)

def contribute_to_class(self, cls, name, virtual_only=False):
if not cls._meta.abstract:
_TRIGGERS.append(self)
super(TriggerVersionField, self).contribute_to_class(cls, name)
if not cls._meta.abstract or cls._meta.proxy:
if not self in _TRIGGERS:
_TRIGGERS.append(self)

def check(self, **kwargs):
errors = []
Expand Down
2 changes: 1 addition & 1 deletion src/concurrency/management/commands/triggers.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def handle(self, cmd='list', *args, **options):
self.stdout.write(" Created {0[2]} for {0[1]}".format(trigger))
self.stdout.write('')
elif cmd == 'drop':
for alias, triggers in drop_triggers(databases).items():
for alias, triggers in drop_triggers(*databases).items():
self.stdout.write("Database: {}".format(alias))
for trigger in triggers:
self.stdout.write(" Dropped {0[2]}".format(trigger))
Expand Down
21 changes: 10 additions & 11 deletions src/concurrency/triggers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ def get_trigger_name(field):
:param field: Field instance
:return: unicode
"""
opts = field.model._meta
if field._trigger_name:
name = field._trigger_name
else:
name = '{1.db_table}_{0.name}'.format(field, opts)
name = '{1.db_table}_{0.name}'.format(field, field.model._meta)
return 'concurrency_{}'.format(name)


def get_triggers(databases):
def get_triggers(databases=None):
if databases is None:
databases = [alias for alias in connections]

Expand All @@ -36,10 +35,9 @@ def get_triggers(databases):
return ret


def drop_triggers(databases):
def drop_triggers(*databases):
global _TRIGGERS
ret = defaultdict(lambda: [])

for field in set(_TRIGGERS):
model = field.model
alias = router.db_for_write(model)
Expand All @@ -60,12 +58,13 @@ def create_triggers(databases):
model = field.model
alias = router.db_for_write(model)
if alias in databases:
# if not field._trigger_exists:
connection = connections[alias]
f = factory(connection)
f.create(field)
ret[alias].append([model, field, field.trigger_name])
_TRIGGERS = []
if not field._trigger_exists:
field._trigger_exists = True
connection = connections[alias]
f = factory(connection)
f.create(field)
ret[alias].append([model, field, field.trigger_name])
# _TRIGGERS = []
return ret


Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import sys

import django
import pytest

Expand All @@ -13,7 +12,8 @@

def pytest_configure():
from django.contrib.auth.models import Group

from django.conf import settings
settings.SILENCED_SYSTEM_CHECKS = ['concurrency.W001']
if django.VERSION[:2] == (1.6):
from concurrency.api import apply_concurrency_check
from concurrency.fields import IntegerVersionField
Expand Down
1 change: 1 addition & 0 deletions tests/demoapp/demo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'demo.apps.ConcurrencyTestConfig'
4 changes: 2 additions & 2 deletions tests/demoapp/demo/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from concurrency.admin import ConcurrentModelAdmin
from concurrency.api import disable_concurrency

from .models import * # noqa
from .models import (
from demo.models import * # noqa
from demo.models import (
ListEditableConcurrentModel, NoActionsConcurrentModel, ReversionConcurrentModel
)

Expand Down
4 changes: 2 additions & 2 deletions tests/demoapp/demo/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@


class ConcurrencyTestConfig(AppConfig):
name = 'tests'
label = 'tests'
name = 'demo'
label = 'demo'
verbose_name = 'Concurrency Tests'
2 changes: 1 addition & 1 deletion tests/demoapp/demo/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from concurrency.api import apply_concurrency_check
from concurrency.fields import IntegerVersionField

from .admin import admin_register_models
from demo.admin import admin_register_models

SENTINEL = '**concurrent_update**'

Expand Down
2 changes: 1 addition & 1 deletion tests/demoapp/demo/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.contrib import admin
from django.views.generic.edit import UpdateView

from .models import SimpleConcurrentModel
from demo.models import SimpleConcurrentModel

# try:
# from django.apps import AppConfig # noqa
Expand Down
5 changes: 4 additions & 1 deletion tests/demoapp/demo/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
from contextlib import contextmanager
from functools import partial, update_wrapper
from itertools import count

import pytest
from django import db

from demo.models import (
AutoIncConcurrentModel, ConcreteModel, CustomSaveModel, InheritedModel, ProxyModel,
SimpleConcurrentModel, TriggerConcurrentModel
)
from django import db

from concurrency.config import conf


Expand Down
26 changes: 26 additions & 0 deletions tests/test_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals

import logging

import django
import pytest

from demo.models import TriggerConcurrentModel

logger = logging.getLogger(__name__)


@pytest.fixture
def obj():
return TriggerConcurrentModel.objects.create()


@pytest.mark.skipif(django.VERSION[:2]<(1,7),
reason="Skip if django< 1.7")
@pytest.mark.django_db
def test_check(obj, monkeypatch):
from django.core.checks import Warning
monkeypatch.setattr(obj._concurrencymeta.field, '_trigger_name', 'test')

assert isinstance(obj._concurrencymeta.field.check()[0], Warning)
31 changes: 31 additions & 0 deletions tests/test_templatetags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals, print_function
import logging

import pytest

from concurrency.templatetags.concurrency import identity, version, is_version
from demo.models import SimpleConcurrentModel
from django.utils.translation import ugettext as _

logger = logging.getLogger(__name__)


@pytest.fixture
def obj():
return SimpleConcurrentModel.objects.create()


@pytest.mark.django_db
def test_identity(obj):
assert identity(obj).split(',') == [str(obj.pk), str(obj.version)]


@pytest.mark.django_db
def test_version(obj):
assert version(obj) == obj.version


@pytest.mark.django_db
def test_is_version(obj):
assert is_version(obj._concurrencymeta.field)
34 changes: 28 additions & 6 deletions tests/test_triggers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
# -*- coding: utf-8 -*-
import logging

import pytest
from django.db import connections

from demo.models import DropTriggerConcurrentModel

from concurrency.triggers import factory
from demo.models import DropTriggerConcurrentModel, TriggerConcurrentModel # noqa
from concurrency.triggers import factory, drop_triggers

logger = logging.getLogger(__name__)

Expand All @@ -21,9 +18,34 @@ def test_list_triggers():


@pytest.mark.django_db
def test_drop_triggers():
def test_get_trigger(monkeypatch):
conn = connections['default']
f = factory(conn)
version_field = TriggerConcurrentModel._concurrencymeta.field
trigger = f.get_trigger(version_field)
assert trigger == 'concurrency_demo_triggerconcurrentmodel_version'

monkeypatch.setattr(version_field, '_trigger_name', 'aaa')
assert f.get_trigger(version_field) is None


@pytest.mark.skipif('connections["default"].vendor=="mysql"',
reason="Mysql is not able to drop tringger inside trasaction")
@pytest.mark.django_db
def test_drop_trigger():
conn = connections['default']
f = [f for f in DropTriggerConcurrentModel._meta.fields if f.name == 'version'][0]
ret = factory(conn).drop(f)
assert ret == [u'concurrency_demo_droptriggerconcurrentmodel_version']
assert factory(conn).get_list() == [u'concurrency_demo_triggerconcurrentmodel_version']


@pytest.mark.skipif('connections["default"].vendor=="mysql"',
reason="Mysql is not able to drop tringger inside trasaction")
@pytest.mark.django_db
def test_drop_triggers(db):
conn = connections['default']
ret = drop_triggers('default')
assert sorted([i[0].__name__ for i in ret['default']]) == ['DropTriggerConcurrentModel',
'TriggerConcurrentModel']
assert factory(conn).get_list() == []
5 changes: 5 additions & 0 deletions tests/test_triggerversionfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,18 @@ def test_trigger():
instance.save() # update
assert instance.version == 3

instance.username = next(nextname)
instance.save(refetch=True) # update
assert instance.version == 4

copy = refetch(instance)
copy.save()

with pytest.raises(RecordModifiedError):
instance.save()



@pytest.mark.django_db
def test_trigger_do_not_increase_version_if_error():
instance = TriggerConcurrentModel()
Expand Down
5 changes: 3 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ envlist = py{27}-d{16,17,18,19}-{pg,sqlite,mysql},

[pytest]
python_paths=./tests/demoapp/
django_find_project = false
DJANGO_SETTINGS_MODULE=demo.settings
norecursedirs = .tox concurrency docs
norecursedirs = .tox docs ./demoapp/
python_files=tests/test_*.py
addopts =
-q
; --reuse-db
Expand All @@ -17,7 +19,6 @@ addopts =
--echo-version django
--echo-attr django.conf.settings.DATABASES.default.ENGINE

python_files=tests/test_*.py
pep8ignore = * ALL
markers =
functional: mark a test as functional
Expand Down

0 comments on commit bc6ced6

Please sign in to comment.