diff --git a/apps/documents/forms.py b/apps/documents/forms.py index 60092b633a..39404dedc1 100644 --- a/apps/documents/forms.py +++ b/apps/documents/forms.py @@ -364,10 +364,21 @@ class MetaDataImageWidget(forms.widgets.Widget): output.append(u'
') for document in value['group_data']: + tags_template = [] + tag_count = document.tags.count() + if tag_count: + tags_template.append(u'
') + tags_template.append(u'%(tag_string)s (%(tag_count)s): ' % { + 'tag_string': _(u'Tags'), 'tag_count': tag_count}) + + tags_template.append(u', '.join(document.tags.values_list('name', flat=True))) + tags_template.append(u'
') + output.append( u'''
%(document_name)s
%(page_string)s: %(document_pages)d
+ %(tags_template)s ') output.append( diff --git a/apps/documents/models.py b/apps/documents/models.py index b24f698f0f..fa962683d0 100644 --- a/apps/documents/models.py +++ b/apps/documents/models.py @@ -9,6 +9,7 @@ from django.contrib.auth.models import User from python_magic import magic +from taggit.managers import TaggableManager from dynamic_search.api import register from converter.api import get_page_count from converter import TRANFORMATION_CHOICES @@ -57,6 +58,8 @@ class Document(models.Model): checksum = models.TextField(blank=True, null=True, verbose_name=_(u'checksum'), editable=False) description = models.TextField(blank=True, null=True, verbose_name=_(u'description'), db_index=True) + tags = TaggableManager() + class Meta: verbose_name = _(u'document') verbose_name_plural = _(u'documents') @@ -444,5 +447,5 @@ class RecentDocument(models.Model): verbose_name_plural = _(u'recent documents') -register(Document, _(u'document'), ['document_type__name', 'file_mimetype', 'file_filename', 'file_extension', 'documentmetadata__value', 'documentpage__content', 'description']) +register(Document, _(u'document'), ['document_type__name', 'file_mimetype', 'file_filename', 'file_extension', 'documentmetadata__value', 'documentpage__content', 'description', 'tags__name']) #register(Document, _(u'document'), ['document_type__name', 'file_mimetype', 'file_extension', 'documentmetadata__value', 'documentpage__content', 'description', {'field_name':'file_filename', 'comparison':'iexact'}]) diff --git a/apps/documents/views.py b/apps/documents/views.py index 7f811d7411..03c73ec269 100644 --- a/apps/documents/views.py +++ b/apps/documents/views.py @@ -834,14 +834,14 @@ def document_view_simple(request, document_id): ] subtemplates_dict = [ - { - 'name': 'generic_list_subtemplate.html', - 'title': _(u'metadata'), - 'object_list': document.documentmetadata_set.all(), - 'extra_columns': [{'name': _(u'value'), 'attribute': 'value'}], - 'hide_link': True, - }, - ] + { + 'name': 'generic_list_subtemplate.html', + 'title': _(u'metadata'), + 'object_list': document.documentmetadata_set.all(), + 'extra_columns': [{'name': _(u'value'), 'attribute': 'value'}], + 'hide_link': True, + }, + ] metadata_groups, errors = document.get_metadata_groups() if (request.user.is_staff or request.user.is_superuser) and errors: @@ -869,9 +869,20 @@ def document_view_simple(request, document_id): } ) + subtemplates_dict.append( + { + 'name': 'generic_list_subtemplate.html', + 'title': _(u'tags'), + 'object_list': document.tags.all(), + #'extra_columns': [{'name': _(u'value'), 'attribute': 'value'}], + 'hide_link': True, + } + ) + return render_to_response('generic_detail.html', { 'form_list': form_list, 'object': document, + 'document': document, 'subtemplates_dict': subtemplates_dict, }, context_instance=RequestContext(request)) diff --git a/apps/folders/forms.py b/apps/folders/forms.py index 0d6b98f03b..4354ba9bbe 100644 --- a/apps/folders/forms.py +++ b/apps/folders/forms.py @@ -14,7 +14,10 @@ class AddDocumentForm(forms.ModelForm): def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super(AddDocumentForm, self).__init__(*args, **kwargs) - self.fields['existing_folder'] = forms.ModelChoiceField(required=False, queryset=Folder.objects.filter(user=user)) + self.fields['existing_folder'] = forms.ModelChoiceField( + required=False, + queryset=Folder.objects.filter(user=user), + label=_(u'Existing folders')) self.fields['title'].required = False self.fields['title'].label = _(u'New folder') diff --git a/apps/tags/__init__.py b/apps/tags/__init__.py new file mode 100644 index 0000000000..0284a1b4ac --- /dev/null +++ b/apps/tags/__init__.py @@ -0,0 +1,15 @@ +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 permissions.api import register_permissions +from navigation.api import register_sidebar_template + +from taggit.models import Tag + +tag_delete = {'text': _('delete'), 'view': 'tag_remove', 'args': ['object.id', 'document.id'], 'famfam': 'tag_blue_delete'}#, 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_DELETE]}} + + +register_links(Tag, [tag_delete]) + +register_sidebar_template(['document_view', 'document_view_simple'], 'tags_sidebar_template.html') diff --git a/apps/tags/forms.py b/apps/tags/forms.py new file mode 100644 index 0000000000..0a4a01b835 --- /dev/null +++ b/apps/tags/forms.py @@ -0,0 +1,29 @@ +from django import forms +from django.utils.translation import ugettext as _ + +from taggit.models import Tag + + +#class FolderForm(forms.ModelForm): +# class Meta: +# model = Folder +# fields = ('title',) + + +class AddTagForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + #user = kwargs.pop('user', None) + super(AddTagForm, self).__init__(*args, **kwargs) + #self.fields['title'].required = False + #self.fields['title'].label = _(u'New folder') + + self.fields['existing_tags'] = forms.ModelChoiceField( + required=False, + queryset=Tag.objects.all(), #(user=user), + label=_(u'Existing tags')) + self.fields['name'].required = False + self.fields['name'].label = _(u'New tag') + + class Meta: + model = Tag + fields = ('name',) diff --git a/apps/tags/models.py b/apps/tags/models.py new file mode 100644 index 0000000000..33b9aba3d5 --- /dev/null +++ b/apps/tags/models.py @@ -0,0 +1,38 @@ +from django.db import models +from django.utils.translation import ugettext as _ + +from taggit.models import Tag + +COLOR_RED = u'red' +COLOR_BLUE = u'blu' +COLOR_MAGENTA = u'mag' +COLOR_CYAN = u'cya' +COLOR_YELLOW = u'yel' + +COLOR_CHOICES = ( + (COLOR_RED, _(u'red')), + (COLOR_BLUE, _(u'blue')), +# (COLOR_MAGENTA, _(u'magenta')), +# (COLOR_CYAN, _(u'cyan')), + (COLOR_YELLOW, _(u'yellow')) +) + +COLOR_CODES = ( + (COLOR_RED, u'FF0000'), + (COLOR_BLUE, u'0000FF'), +# (COLOR_MAGENTA, u'FF0000'), +# (COLOR_CYAN, u'FF0000'), + (COLOR_YELLOW, u'00FFFF') +) + + +class TagProperties(models.Model): + tag = models.ForeignKey(Tag, verbose_name=_(u'tag')) + color = models.CharField(max_length=3, choices=COLOR_CHOICES, verbose_name=_(u'color')) + + class Meta: + verbose_name = _(u'tag properties') + verbose_name_plural = _(u'tags properties') + + def __unicode__(self): + return self.tag diff --git a/apps/tags/templates/tags_sidebar_template.html b/apps/tags/templates/tags_sidebar_template.html new file mode 100644 index 0000000000..fd1cca10ab --- /dev/null +++ b/apps/tags/templates/tags_sidebar_template.html @@ -0,0 +1,2 @@ +{% load tags_tags %} +{% get_add_tag_to_document_form %} diff --git a/apps/tags/templatetags/__init__.py b/apps/tags/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/tags/templatetags/tags_tags.py b/apps/tags/templatetags/tags_tags.py new file mode 100644 index 0000000000..9f111d9d8e --- /dev/null +++ b/apps/tags/templatetags/tags_tags.py @@ -0,0 +1,19 @@ +from django.core.urlresolvers import reverse +from django.template import Library + +from django.utils.translation import ugettext as _ + +from tags.forms import AddTagForm + +register = Library() + + +@register.inclusion_tag('generic_form_subtemplate.html', takes_context=True) +def get_add_tag_to_document_form(context): + context.update({ + 'form': AddTagForm(),#user=context['request'].user), + 'request': context['request'], + 'form_action': reverse('tag_add', args=[context['document'].pk]), + 'title': _('Add tag to document') + }) + return context diff --git a/apps/tags/tests.py b/apps/tags/tests.py new file mode 100644 index 0000000000..2247054b35 --- /dev/null +++ b/apps/tags/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/tags/urls.py b/apps/tags/urls.py new file mode 100644 index 0000000000..d25430cce6 --- /dev/null +++ b/apps/tags/urls.py @@ -0,0 +1,17 @@ +from django.conf.urls.defaults import patterns, url + +urlpatterns = patterns('tags.views', + url(r'^(?P\d+)/remove_from_document/(?P\d+)$', 'tag_remove', (), 'tag_remove'), + url(r'^add_to_document/(?P\d+)$', 'tag_add', (), 'tag_add'), +# url(r'^ocr/queue/document/list/$', 'queue_document_list', (), 'queue_document_list'), +# url(r'^ocr/queue/document/(?P\d+)/delete/$', 'queue_document_delete', (), 'queue_document_delete'), +# url(r'^ocr/queue/document/multiple/delete/$', 'queue_document_multiple_delete', (), 'queue_document_multiple_delete'), +# url(r'^ocr/queue/document/(?P\d+)/re-queue/$', 're_queue_document', (), 're_queue_document'), +# url(r'^ocr/queue/document/multiple/re-queue/$', 're_queue_multiple_document', (), 're_queue_multiple_document'),# + +# url(r'^ocr/queue/(?P\d+)/enable/$', 'document_queue_enable', (), 'document_queue_enable'),# +# url(r'^ocr/queue/(?P\d+)/disable/$', 'document_queue_disable', (), 'document_queue_disable'), + +# url(r'^ocr/document/all/clean_up/$', 'all_document_ocr_cleanup', (), 'all_document_ocr_cleanup'), +# url(r'^ocr/node/active/list/$', 'node_active_list', (), 'node_active_list'), +) diff --git a/apps/tags/views.py b/apps/tags/views.py new file mode 100644 index 0000000000..569f5c5f4c --- /dev/null +++ b/apps/tags/views.py @@ -0,0 +1,53 @@ +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.translation import ugettext as _ + +from permissions.api import check_permissions + +from taggit.models import Tag +from documents.models import Document + +from tags.forms import AddTagForm + + +def tag_remove(request, tag_id, document_id): +# check_permissions(request.user, 'ocr', [PERMISSION_OCR_DOCUMENT]) + + tag = get_object_or_404(Tag, pk=tag_id) + document = get_object_or_404(Document, pk=document_id) + + previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', None))) + tag.delete() + messages.success(request, _(u'Tag: %s, removed successfully.') % tag) + + return HttpResponseRedirect(previous) + + +def tag_add(request, document_id): +# check_permissions(request.user, 'ocr', [PERMISSION_OCR_DOCUMENT]) + + document = get_object_or_404(Document, pk=document_id) + + previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', None))) + #document.tags.add(tag) + #messages.success(request, _(u'Tag: %s, removed successfully.') % tag) + #tag = get_object_or_404(Tag, pk=tag_id) + + #return HttpResponseRedirect(previous) + + + if request.method == 'POST': + previous = request.META.get('HTTP_REFERER', '/') + form = AddTagForm(request.POST)#, user=request.user) + if form.is_valid(): + if form.cleaned_data['existing_tags']: + tag = form.cleaned_data['existing_tags'] + elif form.cleaned_data['name']: + tag = form.cleaned_data['name'] + + document.tags.add(tag) + + return HttpResponseRedirect(previous) diff --git a/docs/CREDITS b/docs/CREDITS index bffda50dcd..b207c5c874 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -124,3 +124,7 @@ Image 1299551545_egypt200.png django-sendfile - This is a wrapper around web-server specific methods for sending files to web clients. johnsensible (John Montgomery) https://github.com/johnsensible/django-sendfile + +jQuery-Jail + +django-taggit diff --git a/requirements/development.txt b/requirements/development.txt index 7b60a1627b..0bc5b70093 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -8,3 +8,4 @@ wsgiref==0.1.2 celery==2.2.2 django-celery==2.2.2 django-sentry==1.6.0 +django-taggit==0.9.3 diff --git a/requirements/production.txt b/requirements/production.txt index 86cac17565..ef0f85a404 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -5,3 +5,4 @@ wsgiref==0.1.2 celery==2.2.2 django-celery==2.2.2 django-sentry==1.6.0 +django-taggit==0.9.3 diff --git a/settings.py b/settings.py index a9298be509..19a6659a3e 100644 --- a/settings.py +++ b/settings.py @@ -139,6 +139,8 @@ INSTALLED_APPS = ( 'filesystem_serving', 'storage', 'folders', + 'taggit', + 'tags', ) TEMPLATE_CONTEXT_PROCESSORS = ( diff --git a/urls.py b/urls.py index bc98f75e20..74613393ea 100644 --- a/urls.py +++ b/urls.py @@ -13,6 +13,7 @@ urlpatterns = patterns('', (r'^search/', include('dynamic_search.urls')), (r'^ocr/', include('ocr.urls')), (r'^permissions/', include('permissions.urls')), + (r'^tags/', include('tags.urls')), (r'^admin/doc/', include('django.contrib.admindocs.urls')), (r'^admin/', include(admin.site.urls)), (r'^grappelli/', include('grappelli.urls')),