diff --git a/apps/checkouts/__init__.py b/apps/checkouts/__init__.py new file mode 100644 index 0000000000..99fdc80c60 --- /dev/null +++ b/apps/checkouts/__init__.py @@ -0,0 +1,39 @@ +from __future__ import absolute_import + +from django.utils.translation import ugettext_lazy as _ + +from navigation.api import (register_links, register_top_menu, + register_multi_item_links, register_sidebar_template) + +from documents.models import Document +from documents.permissions import PERMISSION_DOCUMENT_VIEW +from acls.api import class_permissions + +from .permissions import (PERMISSION_DOCUMENT_CHECKOUT, PERMISSION_DOCUMENT_CHECKIN) +from .links import checkout_list, checkout_document, checkout_info, checkin_document +from .models import DocumentCheckout + + +def initialize_document_checkout_extra_methods(): + Document.add_to_class('is_checked_out', lambda document: DocumentCheckout.objects.is_document_checked_out(document)) + Document.add_to_class('check_in', lambda document: DocumentCheckout.objects.check_in_document(document)) + +#register_multi_item_links(['folder_view'], [folder_document_multiple_remove]) +#register_links(Folder, [folder_view, folder_edit, folder_delete, folder_acl_list]) +#register_links([Folder, 'folder_list', 'folder_create'], [folder_list, folder_create], menu_name='secondary_menu') +register_top_menu(name='checkouts', link=checkout_list)#, children_views=['folder_list', 'folder_create', 'folder_edit', 'folder_delete', 'folder_view', 'folder_document_multiple_remove']) +register_links(Document, [checkout_info], menu_name='form_header') +#register_sidebar_template(['folder_list'], 'folders_help.html') +register_links(['checkout_info', 'checkout_document', 'checkin_document'], [checkout_document, checkin_document], menu_name="sidebar") + +class_permissions(Document, [ + PERMISSION_DOCUMENT_CHECKOUT, + PERMISSION_DOCUMENT_CHECKIN, +]) + +initialize_document_checkout_extra_methods() + + +#TODO: default checkout time +#TODO: forcefull check in +#TODO: specify checkout option check (document.allows_new_versions()) diff --git a/apps/checkouts/exceptions.py b/apps/checkouts/exceptions.py new file mode 100644 index 0000000000..e27b9645de --- /dev/null +++ b/apps/checkouts/exceptions.py @@ -0,0 +1,11 @@ +class DocumentNotCheckedOut(Exception): + """ + Raised when trying to checkin a document that is not checkedout + """ + pass + +class DocumentAlreadyCheckedOut(Exception): + """ + Raised when trying to checkout an already checkedout document + """ + pass diff --git a/apps/checkouts/forms.py b/apps/checkouts/forms.py new file mode 100644 index 0000000000..205f386d86 --- /dev/null +++ b/apps/checkouts/forms.py @@ -0,0 +1,21 @@ +from __future__ import absolute_import + +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from .models import DocumentCheckout + + +class DocumentCheckoutForm(forms.ModelForm): + days = forms.IntegerField(min_value=0, label=_(u'Days'), help_text=_(u'Amount of time to hold the document checked out in days.'), required=False, widget=forms.widgets.TextInput(attrs={'maxlength': 3, 'style':'width: 10em;'})) + hours = forms.IntegerField(min_value=0, label=_(u'Hours'), help_text=_(u'Amount of time to hold the document checked out in hours.'), required=False, widget=forms.widgets.TextInput(attrs={'maxlength': 3, 'style':'width: 10em;'})) + minutes = forms.IntegerField(min_value=0, label=_(u'Minutes'), help_text=_(u'Amount of time to hold the document checked out in minutes.'), required=False, widget=forms.widgets.TextInput(attrs={'maxlength': 3, 'style':'width: 10em;'})) + + class Meta: + model = DocumentCheckout + exclude = ('expiration_datetime', ) + #fields = ('username', 'first_name', 'last_name', 'email', 'is_staff', 'is_superuser', 'last_login', 'date_joined', 'groups') + + #def clean(self): + + diff --git a/apps/checkouts/links.py b/apps/checkouts/links.py new file mode 100644 index 0000000000..a72c0adea9 --- /dev/null +++ b/apps/checkouts/links.py @@ -0,0 +1,12 @@ +from __future__ import absolute_import + +from django.utils.translation import ugettext_lazy as _ + +from documents.permissions import PERMISSION_DOCUMENT_VIEW + +from .permissions import (PERMISSION_DOCUMENT_CHECKOUT, PERMISSION_DOCUMENT_CHECKIN) + +checkout_list = {'text': _(u'check in/out'), 'view': 'checkout_list', 'famfam': 'basket'} +checkout_document = {'text': _('check out document'), 'view': 'checkout_document', 'args': 'object.pk', 'famfam': 'basket_put'}#, 'permissions': [PERMISSION_DOCUMENT_CHECKOUT]} +checkin_document = {'text': _('check out document'), 'view': 'checkout_document', 'args': 'object.pk', 'famfam': 'basket_remove'}#, 'permissions': [PERMISSION_DOCUMENT_CHECKIN]} +checkout_info = {'text': _('check in/out'), 'view': 'checkout_info', 'args': 'object.pk', 'famfam': 'basket', 'children_views': ['checkout_document', 'checkin_document']}#, 'permissions': [PERMISSION_DOCUMENT_CHECKIN]} diff --git a/apps/checkouts/literals.py b/apps/checkouts/literals.py new file mode 100644 index 0000000000..c3961685ab --- /dev/null +++ b/apps/checkouts/literals.py @@ -0,0 +1 @@ +from __future__ import absolute_import diff --git a/apps/checkouts/managers.py b/apps/checkouts/managers.py new file mode 100644 index 0000000000..8fc1f69276 --- /dev/null +++ b/apps/checkouts/managers.py @@ -0,0 +1,26 @@ +from __future__ import absolute_import + +from django.db import models + +from documents.models import Document + +from .exceptions import DocumentNotCheckedOut + + +class DocumentCheckoutManager(models.Manager): + def checked_out(self): + return Document.objects.filter(pk__in=self.model.objects.all().values_list('pk', flat=True)) + + def is_document_checked_out(self, document): + if self.model.objects.filter(document=document): + return True + else: + return False + + def check_in_document(self, document): + try: + document_checkout = self.model.objects.get(document=document) + except self.model.DoesNotExist: + raise DocumentNotCheckedOut + else: + document_checkout.delete() diff --git a/apps/checkouts/migrations/0001_initial.py b/apps/checkouts/migrations/0001_initial.py new file mode 100644 index 0000000000..a4ff9d11a2 --- /dev/null +++ b/apps/checkouts/migrations/0001_initial.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'DocumentCheckout' + db.create_table('checkouts_documentcheckout', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('document', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['documents.Document'])), + ('checkout_datetime', self.gf('django.db.models.fields.DateTimeField')()), + ('expiration_datetime', self.gf('django.db.models.fields.DateTimeField')()), + ('block_new_version', self.gf('django.db.models.fields.BooleanField')(default=False)), + )) + db.send_create_signal('checkouts', ['DocumentCheckout']) + + + def backwards(self, orm): + # Deleting model 'DocumentCheckout' + db.delete_table('checkouts_documentcheckout') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'checkouts.documentcheckout': { + 'Meta': {'object_name': 'DocumentCheckout'}, + 'block_new_version': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'checkout_datetime': ('django.db.models.fields.DateTimeField', [], {}), + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.Document']"}), + 'expiration_datetime': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'comments.comment': { + 'Meta': {'ordering': "('submit_date',)", 'object_name': 'Comment', 'db_table': "'django_comments'"}, + 'comment': ('django.db.models.fields.TextField', [], {'max_length': '3000'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_comment'", 'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_removed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_pk': ('django.db.models.fields.TextField', [], {}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), + 'submit_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comment_comments'", 'null': 'True', 'to': "orm['auth.User']"}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'user_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'user_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'documents.document': { + 'Meta': {'ordering': "['-date_added']", 'object_name': 'Document'}, + 'date_added': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'document_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentType']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '48', 'blank': 'True'}) + }, + 'documents.documenttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'DocumentType'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'taggit.tag': { + 'Meta': {'object_name': 'Tag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}) + }, + 'taggit.taggeditem': { + 'Meta': {'object_name': 'TaggedItem'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"}) + } + } + + complete_apps = ['checkouts'] \ No newline at end of file diff --git a/apps/checkouts/migrations/0002_auto__add_unique_documentcheckout_document.py b/apps/checkouts/migrations/0002_auto__add_unique_documentcheckout_document.py new file mode 100644 index 0000000000..bbdb79b350 --- /dev/null +++ b/apps/checkouts/migrations/0002_auto__add_unique_documentcheckout_document.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding unique constraint on 'DocumentCheckout', fields ['document'] + db.create_unique('checkouts_documentcheckout', ['document_id']) + + + def backwards(self, orm): + # Removing unique constraint on 'DocumentCheckout', fields ['document'] + db.delete_unique('checkouts_documentcheckout', ['document_id']) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'checkouts.documentcheckout': { + 'Meta': {'object_name': 'DocumentCheckout'}, + 'block_new_version': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'checkout_datetime': ('django.db.models.fields.DateTimeField', [], {}), + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.Document']", 'unique': 'True'}), + 'expiration_datetime': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'comments.comment': { + 'Meta': {'ordering': "('submit_date',)", 'object_name': 'Comment', 'db_table': "'django_comments'"}, + 'comment': ('django.db.models.fields.TextField', [], {'max_length': '3000'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_comment'", 'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_removed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_pk': ('django.db.models.fields.TextField', [], {}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), + 'submit_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comment_comments'", 'null': 'True', 'to': "orm['auth.User']"}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'user_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'user_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'documents.document': { + 'Meta': {'ordering': "['-date_added']", 'object_name': 'Document'}, + 'date_added': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'document_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentType']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '48', 'blank': 'True'}) + }, + 'documents.documenttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'DocumentType'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'taggit.tag': { + 'Meta': {'object_name': 'Tag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}) + }, + 'taggit.taggeditem': { + 'Meta': {'object_name': 'TaggedItem'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"}) + } + } + + complete_apps = ['checkouts'] \ No newline at end of file diff --git a/apps/checkouts/migrations/__init__.py b/apps/checkouts/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/checkouts/models.py b/apps/checkouts/models.py new file mode 100644 index 0000000000..6de45fca9b --- /dev/null +++ b/apps/checkouts/models.py @@ -0,0 +1,48 @@ +from __future__ import absolute_import + +import logging +import datetime + +from django.db import models, IntegrityError +from django.utils.translation import ugettext_lazy as _ + +from documents.models import Document + +from .managers import DocumentCheckoutManager +from .exceptions import DocumentAlreadyCheckedOut + +logger = logging.getLogger(__name__) + + +class DocumentCheckout(models.Model): + """ + Model to store the state and information of a document checkout + """ + document = models.ForeignKey(Document, verbose_name=_(u'document'), unique=True, editable=False) + checkout_datetime = models.DateTimeField(verbose_name=_(u'checkout date and time'), editable=False) + expiration_datetime = models.DateTimeField(verbose_name=_(u'checkout expiration date and time')) + block_new_version = models.BooleanField(verbose_name=_(u'block new version upload'), help_text=_(u'Do not allow new version of this document to be uploaded.')) + #block_metadata + #block_editing + #block tag add/remove + + objects = DocumentCheckoutManager() + + def __unicode__(self): + return unicode(self.document) + + def save(self, *args, **kwargs): + if not self.pk: + self.checkout_date = datetime.datetime.now() + try: + return super(DocumentCheckout, self).save(*args, **kwargs) + except IntegrityError: + raise DocumentAlreadyCheckedOut + + @models.permalink + def get_absolute_url(self): + return ('checkout_info', [self.document.pk]) + + class Meta: + verbose_name = _(u'document checkout') + verbose_name_plural = _(u'document checkouts') diff --git a/apps/checkouts/permissions.py b/apps/checkouts/permissions.py new file mode 100644 index 0000000000..2a24c9d47d --- /dev/null +++ b/apps/checkouts/permissions.py @@ -0,0 +1,12 @@ +from __future__ import absolute_import + +from django.utils.translation import ugettext_lazy as _ + +from permissions.models import PermissionNamespace, Permission + +namespace = PermissionNamespace('checkouts', _(u'Document checkout')) + +PERMISSION_DOCUMENT_CHECKOUT = Permission.objects.register(namespace, 'checkout_document', _(u'Check out documents')) +PERMISSION_DOCUMENT_CHECKIN = Permission.objects.register(namespace, 'checkin_document', _(u'Check in documents')) +PERMISSION_DOCUMENT_CHECKIN_OVERRIDE = Permission.objects.register(namespace, 'checkin_document_override', _(u'Forcefully check in documents')) + diff --git a/apps/checkouts/urls.py b/apps/checkouts/urls.py new file mode 100644 index 0000000000..6dd1d8fdd8 --- /dev/null +++ b/apps/checkouts/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls.defaults import patterns, url + +urlpatterns = patterns('checkouts.views', + url(r'^list/$', 'checkout_list', (), 'checkout_list'), + url(r'^(?P\d+)/check/out/$', 'checkout_document', (), 'checkout_document'), + url(r'^(?P\d+)/check/in/$', 'checkin_document', (), 'checkin_document'), + url(r'^(?P\d+)/check/info/$', 'checkout_info', (), 'checkout_info'), +) diff --git a/apps/checkouts/views.py b/apps/checkouts/views.py new file mode 100644 index 0000000000..fb43d2f430 --- /dev/null +++ b/apps/checkouts/views.py @@ -0,0 +1,103 @@ +from __future__ import absolute_import + +from django.utils.translation import ugettext_lazy as _ +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext +from django.contrib import messages +from django.core.urlresolvers import reverse +#from django.utils.html import mark_safe +from django.conf import settings + +from documents.views import document_list +from documents.models import Document +from permissions.exceptions import PermissionDenied +from permissions.models import Permission +from acls.models import AccessEntry + +from .models import DocumentCheckout +from .permissions import PERMISSION_DOCUMENT_CHECKOUT, PERMISSION_DOCUMENT_CHECKIN +from .forms import DocumentCheckoutForm +from .exceptions import DocumentAlreadyCheckedOut + + +def checkout_list(request): + return document_list(request, object_list=DocumentCheckout.objects.checked_out(), title=_(u'checked out documents')) + + +def checkout_info(request, document_pk): + document = get_object_or_404(Document, pk=document_pk) + try: + Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_CHECKOUT, PERMISSION_DOCUMENT_CHECKIN]) + except PermissionDenied: + AccessEntry.objects.check_access([PERMISSION_DOCUMENT_CHECKOUT, PERMISSION_DOCUMENT_CHECKIN], request.user, document) + + if document.is_checked_out(): + content = 'checkedout' + else: + content = _(u'Document has not been checked out.') + #

{{ content|safe }}

+ #{% endif %} + + #{% for paragraph in paragraphs %} + #

{{ paragraph|safe }}

# + + return render_to_response('generic_template.html', { + 'content': content, + 'object': document, + 'title': _(u'Check out details for document: %s') % document + }, context_instance=RequestContext(request)) + + +def checkout_document(request, document_pk): + document = get_object_or_404(Document, pk=document_pk) + try: + Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_CHECKOUT]) + except PermissionDenied: + AccessEntry.objects.check_access(PERMISSION_DOCUMENT_CHECKOUT, request.user, document) + + if request.method == 'POST': + form = DocumentCheckoutForm(request.POST) + if form.is_valid(): + try: + document_checkout = form.save() + except DocumentAlreadyCheckedOut: + messages.error(request, _(u'Document already checked out.')) + except Exception, exc: + messages.error(request, _(u'Error trying to check out document; %s') % exc) + else: + messages.success(request, _(u'Document "%s" checked out successfully.') % document) + return HttpResponseRedirect(document_checkout.get_absolute_url()) + else: + form = DocumentCheckoutForm()#document=document, initial={ + #'new_filename': document.filename}) + + return render_to_response('generic_form.html', { + 'form': form, + 'object': document, + 'title': _(u'Check out document: %s') % document + }, context_instance=RequestContext(request)) + + +def checkin_document(request, document_pk): + document = get_object_or_404(Document, pk=document_pk) + try: + Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_CHECKIN]) + except PermissionDenied: + AccessEntry.objects.check_access(PERMISSION_DOCUMENT_CHECKIN, request.user, document) + + if request.method == 'POST': + try: + document.check_in() + except DocumentAlreadyCheckedOut: + messages.error(request, _(u'Document already checked out.')) + except Exception, exc: + messages.error(request, _(u'Error trying to check in document; %s') % exc) + else: + messages.success(request, _(u'Document "%s" checked out successfully.') % document) + return HttpResponseRedirect(reverse('checkout_info', args=[document.pk])) + + return render_to_response('generic_form.html', { + 'object': document, + 'title': _(u'Check in document: %s') % document + }, context_instance=RequestContext(request)) diff --git a/apps/permissions/exceptions.py b/apps/permissions/exceptions.py new file mode 100644 index 0000000000..14c1eb54f6 --- /dev/null +++ b/apps/permissions/exceptions.py @@ -0,0 +1,5 @@ +from django.core.exceptions import PermissionDenied as DjangoPermissionDenied + + +class PermissionDenied(DjangoPermissionDenied): + pass diff --git a/settings.py b/settings.py index a0da8da709..6132d57479 100644 --- a/settings.py +++ b/settings.py @@ -174,6 +174,7 @@ INSTALLED_APPS = ( 'main', 'rest_api', 'document_signatures', + 'checkouts', # Has to be last so the other apps can register it's signals 'signaler', diff --git a/urls.py b/urls.py index a6ae677f51..4f3430b786 100644 --- a/urls.py +++ b/urls.py @@ -32,6 +32,7 @@ urlpatterns = patterns('', (r'^gpg/', include('django_gpg.urls')), (r'^documents/signatures/', include('document_signatures.urls')), (r'^feedback/', include('feedback.urls')), + (r'^checkouts/', include('checkouts.urls')), )