From f17fb6e466e62c0fb6dc33cb9a97f5729e5ced27 Mon Sep 17 00:00:00 2001 From: Skia Date: Wed, 2 Dec 2015 16:43:40 +0100 Subject: [PATCH] Add lock handling in Wiki --- core/migrations/0015_remove_page_is_locked.py | 18 +++++ core/models.py | 72 ++++++++++++++++++- core/views/page.py | 16 +++-- 3 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 core/migrations/0015_remove_page_is_locked.py diff --git a/core/migrations/0015_remove_page_is_locked.py b/core/migrations/0015_remove_page_is_locked.py new file mode 100644 index 00000000..2ac2c0bc --- /dev/null +++ b/core/migrations/0015_remove_page_is_locked.py @@ -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', + ), + ] diff --git a/core/models.py b/core/models.py index ec4b5a7f..50657968 100644 --- a/core/models.py +++ b/core/models.py @@ -5,7 +5,7 @@ from django.utils import timezone from django.core import validators from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse -from datetime import datetime +from datetime import datetime, timedelta class User(AbstractBaseUser, PermissionsMixin): """ @@ -139,6 +139,18 @@ class GroupManagedObject(models.Model): class Meta: 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): """ 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! """ 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) # 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! full_name = models.CharField(_('page name'), max_length=255, blank=True) + lock_mutex = {} class Meta: @@ -202,6 +214,11 @@ class Page(GroupManagedObject, models.Model): return l 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() # This reset the full_name just before saving to maintain a coherent field quicker for queries than the # recursive method @@ -210,6 +227,53 @@ class Page(GroupManagedObject, models.Model): for c in self.children.all(): c.save() 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): """ @@ -252,4 +316,8 @@ class PageRev(models.Model): def __str__(self): 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() diff --git a/core/views/page.py b/core/views/page.py index dd48c2da..9b99fbc1 100644 --- a/core/views/page.py +++ b/core/views/page.py @@ -5,7 +5,7 @@ from django.views.generic.edit import UpdateView from django.contrib.auth.decorators import login_required, permission_required 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 import CanViewMixin, CanEditMixin, CanEditPropMixin @@ -77,6 +77,10 @@ class PagePropView(CanEditPropMixin, UpdateView): parent = Page.get_page_by_full_name(parent_name) p = Page(name=name, parent=parent) self.page = p + try: + self.page.set_lock_recursive(self.request.user) + except LockError as e: + raise e return self.page def get_context_data(self, **kwargs): @@ -98,6 +102,10 @@ class PageEditView(CanEditMixin, UpdateView): rev = PageRev(author=request.user) rev.save() 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 None @@ -110,17 +118,13 @@ class PageEditView(CanEditMixin, UpdateView): return context def form_valid(self, form): - form.instance.author = self.request.user - form.instance.page = self.page + # TODO : factor that, but first make some tests rev = form.instance new_rev = PageRev(title=rev.title, content=rev.content, ) new_rev.author = self.request.user new_rev.page = self.page - print(form.instance) - new_rev.save() form.instance = new_rev - print(form.instance) return super(PageEditView, self).form_valid(form) -- GitLab