Skip to content

Commit

Permalink
Merge pull request #34 from vikingco/pre-django-1.8-merge-master
Browse files Browse the repository at this point in the history
Trying to merge the branches
  • Loading branch information
fredvdvd committed May 16, 2017
2 parents a293ef8 + 0976bbf commit 1a0bfcd
Show file tree
Hide file tree
Showing 15 changed files with 98 additions and 55 deletions.
3 changes: 3 additions & 0 deletions README.rst
Expand Up @@ -45,6 +45,9 @@ Wishlist

Releases
--------
v2.0.0:
Merging of master and pre-django-1.8 branches
Removes management command in favor of a celery task
v1.2.1:
Fix problem in migration to UUIDField for PostGres
v1.2.0:
Expand Down
4 changes: 4 additions & 0 deletions circle.yml
Expand Up @@ -2,6 +2,10 @@ machine:
python:
version: 2.7.6

dependencies:
override:
- pip install -U tox virtualenv

test:
override:
- tox
Expand Down
2 changes: 2 additions & 0 deletions deployment/requirements_test.txt
Expand Up @@ -2,3 +2,5 @@ Django
freezegun
tox
coverage
celery
amqp>=1.3.0,<2.1.0
2 changes: 1 addition & 1 deletion locking/__init__.py
@@ -1 +1 @@
__version__ = '1.2.1'
__version__ = '2.0.0'
2 changes: 2 additions & 0 deletions locking/admin.py
Expand Up @@ -6,4 +6,6 @@
class NonBlockingLockAdmin(admin.ModelAdmin):
date_hierarchy = 'created_on'
list_display = ('locked_object', 'created_on')


admin.site.register(NonBlockingLock, NonBlockingLockAdmin)
Empty file removed locking/management/__init__.py
Empty file.
Empty file.
24 changes: 0 additions & 24 deletions locking/management/commands/clean_expired_locks.py

This file was deleted.

20 changes: 20 additions & 0 deletions locking/migrations/0004_auto_20160907_0942.py
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-09-07 09:42
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('locking', '0003_optimize_queries'),
]

operations = [
migrations.AlterField(
model_name='nonblockinglock',
name='max_age',
field=models.PositiveIntegerField(default=600, help_text='The age of a lock before it can be overwritten. 0 means indefinitely.', verbose_name='Maximum lock age'),
),
]
16 changes: 16 additions & 0 deletions locking/migrations/0005_merge_20170504_1024.py
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-04 10:24
from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('locking', '0004_auto_20160907_0942'),
('locking', '0004_auto_20151115_0703'),
]

operations = [
]
41 changes: 25 additions & 16 deletions locking/models.py
Expand Up @@ -3,7 +3,7 @@
from datetime import timedelta

from django.utils import timezone
from django.db import models, IntegrityError
from django.db import models, IntegrityError, transaction
from django.db.models import Q
from django.db.models.signals import pre_save
from django.dispatch import receiver
Expand Down Expand Up @@ -50,21 +50,30 @@ def acquire_lock(self, obj=None, max_age=None, lock_name=''):
if obj is not None:
lock_name = _get_lock_name(obj)

try:
lock, created = self.get_or_create(locked_object=lock_name,
defaults={'max_age': max_age})
if not created:
# check whether lock is expired
if lock.is_expired:
# Create a new lock to provide a new id for renewal.
# This ensures the owner of the previous lock doesn't
# remain in possession of the active lock id.
lock.release()
lock = self.create(locked_object=lock_name, max_age=max_age)
else:
raise AlreadyLocked()
except IntegrityError:
raise AlreadyLocked()
with transaction.atomic():
try:
now = timezone.now()

defaults = {'max_age': max_age,
'created_on': now,
'renewed_on': now,
'expires_on': now + timedelta(seconds=max_age)}

lock, created = self.get_or_create(locked_object=lock_name,
defaults=defaults)
if not created:
# check whether lock is expired
if lock.is_expired:
# Create a new lock to provide a new id for renewal.
# This ensures the owner of the previous lock doesn't
# remain in possession of the active lock id.
lock.release()
lock = self.create(locked_object=lock_name, max_age=max_age)
else:
raise AlreadyLocked()

except IntegrityError:
raise AlreadyLocked()

return lock

Expand Down
11 changes: 11 additions & 0 deletions locking/tasks.py
@@ -0,0 +1,11 @@
from celery import shared_task

from .models import NonBlockingLock


@shared_task
def clean_expired_locks():
"""
Delete all expired locks.
"""
NonBlockingLock.objects.get_expired_locks().delete()
24 changes: 12 additions & 12 deletions locking/tests.py
Expand Up @@ -5,13 +5,12 @@

from freezegun import freeze_time

from django.core.management import call_command
from django.test import TestCase

from django.contrib.auth.models import User
from django.test import TestCase

from .exceptions import AlreadyLocked, RenewalError
from .models import NonBlockingLock, _get_lock_name
from .tasks import clean_expired_locks


class NonBlockingLockTest(TestCase):
Expand Down Expand Up @@ -122,15 +121,16 @@ def test_context_manager(self):


class CleanExpiredLocksTest(TestCase):
"""Tests correct functioning of the task that cleans expired locks."""
def setUp(self):
self.user = User.objects.create(username='CleanExpiredLocks test')
self.user = User.objects.create(username='hellofoo')

def test_clean_locks(self):
def test_clean(self):
"""Make expired lock, ensure the management command cleans it up."""
with freeze_time("2015-01-01 10:00"):
NonBlockingLock.objects.acquire_lock(self.user, max_age=1)

self.assertEqual(1, NonBlockingLock.objects.get_expired_locks().count())
call_command('clean_expired_locks', dry_run=True)
self.assertEqual(1, NonBlockingLock.objects.get_expired_locks().count())
call_command('clean_expired_locks')
self.assertEqual(0, NonBlockingLock.objects.get_expired_locks().count())
lock_to_be_released = NonBlockingLock.objects.acquire_lock(self.user, max_age=0)
NonBlockingLock.objects.acquire_lock(lock_to_be_released, max_age=1)
with freeze_time("2015-01-01 11:00"):
# Only the non-expired lock should remain
clean_expired_locks()
self.assertEqual(NonBlockingLock.objects.get(), lock_to_be_released)
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -39,7 +39,7 @@ def run_tests(self):
author='VikingCo',
packages=find_packages('.'),
cmdclass={'test': RunTestsCommand},
tests_require=['django', 'freezegun'],
tests_require=['django', 'freezegun', 'celery'],
classifiers=[
'Intended Audience :: Developers',
'Programming Language :: Python',
Expand Down
2 changes: 1 addition & 1 deletion test_project/test_project/urls.py
Expand Up @@ -17,5 +17,5 @@
from django.contrib import admin

urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^admin/', admin.site.urls),
]

0 comments on commit 1a0bfcd

Please sign in to comment.