diff --git a/apps/folders/__init__.py b/apps/folders/__init__.py new file mode 100644 index 0000000000..e430bac058 --- /dev/null +++ b/apps/folders/__init__.py @@ -0,0 +1,43 @@ +from django.utils.translation import ugettext_lazy as _ + +from navigation.api import register_links, register_menu, \ + register_model_list_columns, register_multi_item_links +#from main.api import register_diagnostic +from permissions.api import register_permissions + +from models import Folder + +folder_list = {'text': _(u'folder list'), 'view': 'folder_list', 'famfam': 'folder'} +folder_create = {'text': _('create folder'), 'view': 'folder_create', 'famfam': 'folder_add'} +folder_edit = {'text': _('edit'), 'view': 'folder_edit', 'args': 'object.id', 'famfam': 'folder_edit'} +folder_delete = {'text': _('delete'), 'view': 'folder_delete', 'args': 'object.id', 'famfam': 'folder_delete'} + +#document_create_multiple = {'text': _('upload multiple new documents'), 'view': 'document_create_multiple', 'famfam': 'page_add', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_CREATE]}} +#document_create_sibling = {'text': _('upload new document using same metadata'), 'view': 'document_create_sibling', 'args': 'object.id', 'famfam': 'page_copy', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_CREATE]}} +#document_view = {'text': _('details (advanced)'), 'view': 'document_view', 'args': 'object.id', 'famfam': 'page', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}} +#document_view_simple = {'text': _('details (simple)'), 'view': 'document_view_simple', 'args': 'object.id', 'famfam': 'page', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}} +#document_multiple_delete = {'text': _('delete'), 'view': 'document_multiple_delete', 'famfam': 'page_delete', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_DELETE]}} +#document_edit_metadata = {'text': _('edit metadata'), 'view': 'document_edit_metadata', 'args': 'object.id', 'famfam': 'page_edit', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_METADATA_EDIT]}} +#document_multiple_edit_metadata = {'text': _('edit metadata'), 'view': 'document_multiple_edit_metadata', 'famfam': 'page_edit', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_METADATA_EDIT]}} +#document_preview = {'text': _('preview'), 'class': 'fancybox', 'view': 'document_preview', 'args': 'object.id', 'famfam': 'magnifier', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}} +#document_download = {'text': _('download'), 'view': 'document_download', 'args': 'object.id', 'famfam': 'page_save', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_DOWNLOAD]}} +#document_find_duplicates = {'text': _('find duplicates'), 'view': 'document_find_duplicates', 'args': 'object.id', 'famfam': 'page_refresh', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}} +#document_find_all_duplicates = {'text': _('find all duplicates'), 'view': 'document_find_all_duplicates', 'famfam': 'page_refresh', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}} +#document_clear_transformations = {'text': _('clear all transformations'), 'view': 'document_clear_transformations', 'args': 'object.id', 'famfam': 'page_paintbrush', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_TRANSFORM]}} +#document_multiple_clear_transformations = {'text': _('clear all transformations'), 'view': 'document_multiple_clear_transformations', 'famfam': 'page_paintbrush', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_TRANSFORM]}} + +#register_links(Document, [document_view_simple, document_view, document_edit, document_edit_metadata, document_delete, document_download, document_find_duplicates, document_clear_transformations], menu_name='sidebar') +#register_links(Document, [document_list_recent, document_list, document_create, document_create_multiple, document_create_sibling], menu_name='sidebar') +#register_multi_item_links(['document_list'], [document_multiple_clear_transformations, document_multiple_edit_metadata, document_multiple_delete]) + +#####register_links(['folder_list', 'document_list_recent', 'document_list', 'document_create', 'document_create_multiple', 'upload_document_with_type', 'upload_multiple_documents_with_type'], [folder_list, folder_create], menu_name='sidebar') + +register_links(Folder, [folder_edit, folder_delete]) + +register_links(['folder_edit', 'folder_delete', 'folder_list', 'folder_create'], [folder_list, folder_create], menu_name='sidebar') + + +register_menu([ + {'text': _('folders'), 'view': 'folder_list', 'links': [ + folder_list, folder_create + ], 'famfam': 'folder', 'position': 2}]) diff --git a/apps/folders/admin.py b/apps/folders/admin.py new file mode 100644 index 0000000000..783a5f6623 --- /dev/null +++ b/apps/folders/admin.py @@ -0,0 +1,17 @@ +from django.contrib import admin + +from models import Folder, FolderDocument + + +class FolderDocumentInline(admin.StackedInline): + model = FolderDocument + extra = 1 + classes = ('collapse-open',) + allow_add = True + + +class FolderAdmin(admin.ModelAdmin): + inlines = [FolderDocumentInline] + + +admin.site.register(Folder, FolderAdmin) diff --git a/apps/folders/forms.py b/apps/folders/forms.py new file mode 100644 index 0000000000..7aade6167b --- /dev/null +++ b/apps/folders/forms.py @@ -0,0 +1,11 @@ +from django import forms +from django.utils.translation import ugettext as _ +#from django.template.defaultfilters import capfirst + +from models import Folder + + +class FolderForm(forms.ModelForm): + class Meta: + model = Folder + fields = ('title',) diff --git a/apps/folders/models.py b/apps/folders/models.py new file mode 100644 index 0000000000..eeeaa8ead0 --- /dev/null +++ b/apps/folders/models.py @@ -0,0 +1,43 @@ +from datetime import datetime + +from django.db import models +from django.utils.translation import ugettext as _ +from django.contrib.auth.models import User + +from documents.models import Document + + +class Folder(models.Model): + title = models.CharField(max_length=32, verbose_name=_(u'title'), db_index=True) + user = models.ForeignKey(User, verbose_name=_(u'user')) + datetime_created = models.DateTimeField(verbose_name=_(u'datetime created')) + + def __unicode__(self): + return self.title + + def save(self, *args, **kwargs): + if not self.pk: + self.datetime_created = datetime.now() + super(Folder, self).save(*args, **kwargs) + + @models.permalink + def get_absolute_url(self): + return ('folder_view', [self.pk]) + + class Meta: + unique_together = ('title', 'user') + ordering = ('title',) + verbose_name = _(u'folder') + verbose_name_plural = _(u'folders') + + +class FolderDocument(models.Model): + folder = models.ForeignKey(Folder, verbose_name=_('folder')) + document = models.ForeignKey(Document, verbose_name=_('document')) + + def __unicode__(self): + return unicode(self.document) + + class Meta: + verbose_name = _(u'folder document') + verbose_name_plural = _(u'folders documents') diff --git a/apps/folders/tests.py b/apps/folders/tests.py new file mode 100644 index 0000000000..2247054b35 --- /dev/null +++ b/apps/folders/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/apps/folders/urls.py b/apps/folders/urls.py new file mode 100644 index 0000000000..b9cf136ba9 --- /dev/null +++ b/apps/folders/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls.defaults import patterns, url + + +urlpatterns = patterns('folders.views', + url(r'^list/$', 'folder_list', (), 'folder_list'), + url(r'^create/$', 'folder_create', (), 'folder_create'), + url(r'^(?P\d+)/edit/$', 'folder_edit', (), 'folder_edit'), + url(r'^(?P\d+)/delete/$', 'folder_delete', (), 'folder_delete'), + url(r'^(?P\d+)/$', 'folder_view', (), 'folder_view'), +) diff --git a/apps/folders/views.py b/apps/folders/views.py new file mode 100644 index 0000000000..2f9666ecb3 --- /dev/null +++ b/apps/folders/views.py @@ -0,0 +1,135 @@ +from django.utils.translation import ugettext as _ +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext +from django.contrib import messages +from django.views.generic.list_detail import object_detail, object_list +from django.core.urlresolvers import reverse +from django.views.generic.create_update import create_object, delete_object, update_object + +from documents import PERMISSION_DOCUMENT_VIEW +from documents.models import Document +from permissions.api import check_permissions + +from models import Folder, FolderDocument +from forms import FolderForm + + +def folder_list(request): + return object_list( + request, + queryset=Folder.objects.filter(user=request.user), + template_name='generic_list.html', + extra_context={ + 'title': _(u'folders'), + 'multi_select_as_buttons': True, + 'extra_columns': [ + {'name': _(u'created'), 'attribute': 'datetime_created'}, + {'name': _(u'documents'), 'attribute': lambda x: x.folderdocument_set.count()} + ] + }, + ) + + +def folder_create(request): + if request.method == 'POST': + form = FolderForm(request.POST) + if form.is_valid(): + folder, created = Folder.objects.get_or_create(user=request.user, title=form.cleaned_data['title']) + if created: + messages.success(request, _(u'Folder created successfully')) + return HttpResponseRedirect(reverse('folder_list')) + else: + messages.error(request, _(u'A folder named: %s, already exists.') % form.cleaned_data['title']) + else: + form = FolderForm() + + return render_to_response('generic_form.html', { + 'title': _(u'create folder'), + 'form': form, + }, + context_instance=RequestContext(request)) + + +def folder_edit(request, folder_id): + folder = get_object_or_404(Folder, pk=folder_id) + + if request.method == 'POST': + form = FolderForm(request.POST) + if form.is_valid(): + folder.title=form.cleaned_data['title'] + try: + folder.save() + messages.success(request, _(u'Folder edited successfully')) + return HttpResponseRedirect(reverse('folder_list')) + except Exception, e: + messages.error(request, _(u'Error editing folder; %s') % e) + else: + form = FolderForm(instance=folder) + + return render_to_response('generic_form.html', { + 'title': _(u'edit folder: %s') % folder, + 'form': form, + 'object': folder, + }, + context_instance=RequestContext(request)) + + +def folder_delete(request, folder_id): + folder = get_object_or_404(Folder, pk=folder_id) + + post_action_redirect = reverse('folder_list') + + previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', '/'))) + next = request.POST.get('next', request.GET.get('next', post_action_redirect if post_action_redirect else request.META.get('HTTP_REFERER', '/'))) + + if request.method == 'POST': + try: + folder.delete() + messages.success(request, _(u'Folder: %s deleted successfully.') % folder) + except Exception, e: + messages.error(request, _(u'Folder: %(folder)s delete error: %(error)s') % { + 'folder': folder, 'error': e}) + + return HttpResponseRedirect(next) + + context = { + 'object_name': _(u'folder'), + 'delete_view': True, + 'previous': previous, + 'next': next, + 'object': folder, + 'title': _(u'Are you sure you with to delete the folder: %s?') % folder + } + + return render_to_response('generic_confirm.html', context, + context_instance=RequestContext(request)) + + +def folder_view(request, folder_id): + folder = get_object_or_404(Folder, pk=folder_id) + + return render_to_response('generic_list.html', { + 'object_list': [folder_document.document for folder_document in folder.folderdocument_set.all()], + 'title': _(u'documents in folder: %s') % folder, + 'multi_select_as_buttons': True, + }, context_instance=RequestContext(request)) + + +def folder_add_document(request, folder_id, document_id): + folder = get_object_or_404(Folder, pk=folder_id) + document = get_object_or_404(Document, pk=document_id) + check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) + + if request.method == 'POST': + previous = request.META.get('HTTP_REFERER', '/') + + folder_document, created = FolderDocument.objects.get_or_create(folder=folder, document=document) + if created: + messages.success(request, _(u'Document: %(document)s added to folder: %(folder)s successfully.') % { + 'document': document, 'folder': folder}) + else: + messages.warning(request, _(u'Document: %(document)s is already in folder: %(folder)s.') % { + 'document': document, 'folder': folder}) + + return HttpResponseRedirect(previous) diff --git a/settings.py b/settings.py index db701b80f9..dcae2e0082 100644 --- a/settings.py +++ b/settings.py @@ -138,6 +138,7 @@ INSTALLED_APPS = ( 'sentry.client.celery', 'filesystem_serving', 'storage', + 'folders', ) TEMPLATE_CONTEXT_PROCESSORS = ( diff --git a/urls.py b/urls.py index bedbd3e692..0fcee5753a 100644 --- a/urls.py +++ b/urls.py @@ -10,6 +10,7 @@ urlpatterns = patterns('', (r'^', include('common.urls')), (r'^', include('main.urls')), (r'^documents/', include('documents.urls')), + (r'^folders/', include('folders.urls')), (r'^filesystem_serving/', include('filesystem_serving.urls')), (r'^search/', include('dynamic_search.urls')), (r'^ocr/', include('ocr.urls')),