Skip to content

Commit

Permalink
On branch master
Browse files Browse the repository at this point in the history
Initial commit
  • Loading branch information
Stijn Debrouwere authored and Stijn Debrouwere committed Jun 1, 2010
0 parents commit 616d0ad
Show file tree
Hide file tree
Showing 40 changed files with 1,963 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
*.pyc
docs/_build
.DS_Store
25 changes: 25 additions & 0 deletions LICENSE
@@ -0,0 +1,25 @@
Copyright 2010 Stijn Debrouwere. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.

THIS SOFTWARE IS PROVIDED BY STIJN DEBROUWERE ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL STIJN DEBROUWERE OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of Stijn Debrouwere.
13 changes: 13 additions & 0 deletions README
@@ -0,0 +1,13 @@
## What the heck is this?

Django has seen great adoption in the content management sphere, especially among the newspaper crowd. One of the trickier things to get right, is to make sure that nobody steps on each others toes while editing and modifying existing content. Newspaper editors might not always be aware of what other editors are up to, and this goes double for distributed teams. When different people work on the same content, the one who saves last will win the day, while the other edits are overwritten.

`django-locking` provides a system that makes concurrent editing impossible, and informs users of what other users are working on and for how long that content will remain locked. Users can still read locked content, but cannot modify or save it.

## Documentation

`django-locking` is well-documented. Check out the docs!

## License

django-locking comes with the simplified BSD license, see the included LICENSE file for details.
8 changes: 8 additions & 0 deletions __init__.py
@@ -0,0 +1,8 @@
import sys
import logging
from django.conf import settings

LOCK_TIMEOUT = getattr(settings, 'LOCK_TIMEOUT', 1800)
LOCKING_LOG_LEVEL = getattr(settings, 'LOCKING_LOG_LEVEL', logging.INFO)

logging.basicConfig(stream=sys.stderr, level=LOCKING_LOG_LEVEL)
63 changes: 63 additions & 0 deletions admin.py
@@ -0,0 +1,63 @@
# encoding: utf-8

from datetime import datetime

from django.contrib import admin
from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django import forms

from locking import LOCK_TIMEOUT, views

class LockableAdmin(admin.ModelAdmin):
@property
def media(self):
# because reverse() doesn't yet work when this module is first loaded
# (the urlconf still has to load at that point) the media definition
# has to be dynamic, and we can't simply add a Media class to the
# ModelAdmin as you usually would.
#
# Doing so would result in an ImproperlyConfigured exception, stating
# "The included urlconf doesn't have any patterns in it."
#
# See http://docs.djangoproject.com/en/dev/topics/forms/media/#media-as-a-dynamic-property
# for more information about dynamic media definitions.

css = {
'all': ('locking/css/locking.css',)
}
js = (
'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js',
'locking/js/jquery.url.packed.js',
#reverse('django.views.i18n.javascript_catalog'),
reverse('locking_variables'),
'locking/js/admin.locking.js',
)

return forms.Media(css=css, js=js)

# niet vergeten js en dit hier te documenteren,
# gezien overrides in subklassen zonder supers het anders kapot zouden kunnen maken
def changelist_view(self, request, extra_context=None):
# we need the request objects in a few places where it's usually not present,
# so we're tacking it on to the LockableAdmin class
self.request = request
return super(LockableAdmin, self).changelist_view(request, extra_context)

def lock(self, obj):
print obj.is_locked
if obj.is_locked:
seconds_remaining = obj.lock_seconds_remaining
minutes_remaining = seconds_remaining/60
locked_until = _("Still locked for %s minutes by %s") % (minutes_remaining, obj.locked_by)

if self.request.user == obj.locked_by:
return '<img src="%slocking/img/page_edit.png" title="%s" />' % (settings.MEDIA_URL, locked_until)
else:
return '<img src="%slocking/img/lock.png" title="%s" />' % (settings.MEDIA_URL, locked_until)
else:
return ''
lock.allow_tags = True

list_display = ('lock', )
40 changes: 40 additions & 0 deletions decorators.py
@@ -0,0 +1,40 @@
# encoding: utf-8

from django.http import HttpResponse
from django.contrib.contenttypes.models import ContentType

from locking.models import LockableModel
from locking import logging

def user_may_change_model(fn):
def view(request, app, model, *vargs, **kwargs):
may_change = '%s.change_%s' % (app, model)
if not request.user.has_perm(may_change):
return HttpResponse(status=401)
else:
return fn(request, app, model, *vargs, **kwargs)

return view

def is_lockable(fn):
def view(request, app, model, *vargs, **kwargs):
try:
cls = ContentType.objects.get(app_label=app, model=model).model_class()
if issubclass(cls, LockableModel):
lockable = True
except ContentType.DoesNotExist:
lockable = False

if lockable:
return fn(request, app, model, *vargs, **kwargs)
else:
return HttpResponse(status=404)
return view

def log(view):
def decorated_view(*vargs, **kwargs):
response = view(*vargs, **kwargs)
logging.debug("Sending a request: \n\t%s" % (response.content))
return response

return decorated_view
89 changes: 89 additions & 0 deletions docs/Makefile
@@ -0,0 +1,89 @@
# Makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build

# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .

.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest

help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"

clean:
-rm -rf $(BUILDDIR)/*

html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."

pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."

json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."

htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."

qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-locking.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-locking.qhc"

latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."

changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."

linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."

doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

0 comments on commit 616d0ad

Please sign in to comment.