Add lock handling in Wiki

This commit is contained in:
Skia 2015-12-02 16:43:40 +01:00
parent 92f68f5b42
commit f17fb6e466
3 changed files with 98 additions and 8 deletions

View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0014_remove_page_revision'),
]
operations = [
migrations.RemoveField(
model_name='page',
name='is_locked',
),
]

View File

@ -5,7 +5,7 @@ from django.utils import timezone
from django.core import validators from django.core import validators
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from datetime import datetime from datetime import datetime, timedelta
class User(AbstractBaseUser, PermissionsMixin): class User(AbstractBaseUser, PermissionsMixin):
""" """
@ -139,6 +139,18 @@ class GroupManagedObject(models.Model):
class Meta: class Meta:
abstract = True abstract = True
class LockError(Exception):
"""There was a lock error on the object"""
pass
class AlreadyLocked(LockError):
"""The object is already locked"""
pass
class NotLocked(LockError):
"""The object is not locked"""
pass
class Page(GroupManagedObject, models.Model): class Page(GroupManagedObject, models.Model):
""" """
The page class to build a Wiki The page class to build a Wiki
@ -151,11 +163,11 @@ class Page(GroupManagedObject, models.Model):
query, but don't rely on it when playing with a Page object, use get_full_name() instead! query, but don't rely on it when playing with a Page object, use get_full_name() instead!
""" """
name = models.CharField(_('page name'), max_length=30, blank=False) name = models.CharField(_('page name'), max_length=30, blank=False)
is_locked = models.BooleanField(_("page mutex"), default=False)
parent = models.ForeignKey('self', related_name="children", null=True, blank=True, on_delete=models.SET_NULL) parent = models.ForeignKey('self', related_name="children", null=True, blank=True, on_delete=models.SET_NULL)
# Attention: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when # Attention: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when
# playing with a Page object, use get_full_name() instead! # playing with a Page object, use get_full_name() instead!
full_name = models.CharField(_('page name'), max_length=255, blank=True) full_name = models.CharField(_('page name'), max_length=255, blank=True)
lock_mutex = {}
class Meta: class Meta:
@ -202,6 +214,11 @@ class Page(GroupManagedObject, models.Model):
return l return l
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""
Performs some needed actions before and after saving a page in database
"""
if not self.is_locked():
raise NotLocked("The page is not locked and thus can not be saved")
self.full_clean() self.full_clean()
# This reset the full_name just before saving to maintain a coherent field quicker for queries than the # This reset the full_name just before saving to maintain a coherent field quicker for queries than the
# recursive method # recursive method
@ -210,6 +227,53 @@ class Page(GroupManagedObject, models.Model):
for c in self.children.all(): for c in self.children.all():
c.save() c.save()
super(Page, self).save(*args, **kwargs) super(Page, self).save(*args, **kwargs)
self.unset_lock()
def is_locked(self):
"""
Is True if the page is locked, False otherwise
This is where the timeout is handled, so a locked page for which the timeout is reach will be unlocked and this
function will return False
"""
if self.pk not in Page.lock_mutex.keys():
# print("Page mutex does not exists")
return False
if (timezone.now()-Page.lock_mutex[self.pk]['time']) > timedelta(seconds=5):
# print("Lock timed out")
self.unset_lock()
return False
return True
def set_lock(self, user):
"""
Sets a lock on the current page or raise an AlreadyLocked exception
"""
if self.is_locked() and self.get_lock()['user'] != user:
raise AlreadyLocked("The page is already locked by someone else")
Page.lock_mutex[self.pk] = {'user': user,
'time': timezone.now()}
# print("Locking page")
def set_lock_recursive(self, user):
"""
Locks recursively all the child pages for editing properties
"""
for p in self.children.all():
p.set_lock_recursive(user)
self.set_lock(user)
def unset_lock(self):
"""Always try to unlock, even if there is no lock"""
Page.lock_mutex.pop(self.pk, None)
# print("Unlocking page")
def get_lock(self):
"""
Returns the page's mutex containing the time and the user in a dict
"""
if self.is_locked():
return Page.lock_mutex[self.pk]
raise NotLocked("The page is not locked and thus can not return its mutex")
def get_absolute_url(self): def get_absolute_url(self):
""" """
@ -252,4 +316,8 @@ class PageRev(models.Model):
def __str__(self): def __str__(self):
return str(self.__dict__) return str(self.__dict__)
def save(self, *args, **kwargs):
super(PageRev, self).save(*args, **kwargs)
# Don't forget to unlock, otherwise, people will have to wait for the page's timeout
self.page.unset_lock()

View File

@ -5,7 +5,7 @@ from django.views.generic.edit import UpdateView
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from core.models import Page, PageRev from core.models import Page, PageRev, LockError
from core.views.forms import PagePropForm from core.views.forms import PagePropForm
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin
@ -77,6 +77,10 @@ class PagePropView(CanEditPropMixin, UpdateView):
parent = Page.get_page_by_full_name(parent_name) parent = Page.get_page_by_full_name(parent_name)
p = Page(name=name, parent=parent) p = Page(name=name, parent=parent)
self.page = p self.page = p
try:
self.page.set_lock_recursive(self.request.user)
except LockError as e:
raise e
return self.page return self.page
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -98,6 +102,10 @@ class PageEditView(CanEditMixin, UpdateView):
rev = PageRev(author=request.user) rev = PageRev(author=request.user)
rev.save() rev.save()
self.page.revisions.add(rev) self.page.revisions.add(rev)
try:
self.page.set_lock(self.request.user)
except LockError as e:
raise e
return self.page.revisions.all().last() return self.page.revisions.all().last()
return None return None
@ -110,17 +118,13 @@ class PageEditView(CanEditMixin, UpdateView):
return context return context
def form_valid(self, form): def form_valid(self, form):
form.instance.author = self.request.user # TODO : factor that, but first make some tests
form.instance.page = self.page
rev = form.instance rev = form.instance
new_rev = PageRev(title=rev.title, new_rev = PageRev(title=rev.title,
content=rev.content, content=rev.content,
) )
new_rev.author = self.request.user new_rev.author = self.request.user
new_rev.page = self.page new_rev.page = self.page
print(form.instance)
new_rev.save()
form.instance = new_rev form.instance = new_rev
print(form.instance)
return super(PageEditView, self).form_valid(form) return super(PageEditView, self).form_valid(form)