Add favorite documents per user. Adds new setting option DOCUMENTS_FAVORITE_COUNT.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2018-08-23 01:50:35 -04:00
parent ec44e81864
commit 26b31da443
10 changed files with 319 additions and 66 deletions

View File

@@ -95,7 +95,8 @@
- Add support to the ObjectActionMixin to report on instance action
failures. Add also an error_message class property and the new
ActionError exception.
- Add favorite documents per user. Adds new setting option
DOCUMENTS_FAVORITE_COUNT.
3.0.3 (2018-08-17)
==================

View File

@@ -53,14 +53,17 @@ from .handlers import (
from .links import (
link_clear_image_cache, link_document_clear_transformations,
link_document_clone_transformations, link_document_delete,
link_document_document_type_edit, link_document_duplicates_list,
link_document_multiple_document_type_edit, link_document_download,
link_document_edit, link_document_list, link_document_list_deleted,
link_document_list_recent_access, link_document_list_recent_added,
link_document_multiple_delete, link_document_multiple_trash,
link_document_document_type_edit, link_document_download,
link_document_duplicates_list, link_document_edit,
link_document_favorites_add, link_document_favorites_remove,
link_document_list, link_document_list_deleted,
link_document_list_favorites, link_document_list_recent_access,
link_document_list_recent_added,
link_document_multiple_clear_transformations,
link_document_multiple_download, link_document_multiple_restore,
link_document_multiple_update_page_count,
link_document_multiple_delete, link_document_multiple_document_type_edit,
link_document_multiple_download, link_document_multiple_favorites_add,
link_document_multiple_favorites_remove, link_document_multiple_restore,
link_document_multiple_trash, link_document_multiple_update_page_count,
link_document_page_navigation_first, link_document_page_navigation_last,
link_document_page_navigation_next, link_document_page_navigation_previous,
link_document_page_return, link_document_page_rotate_left,
@@ -395,8 +398,9 @@ class DocumentsApp(MayanAppConfig):
menu_documents.bind_links(
links=(
link_document_list_recent_access,
link_document_list_recent_added, link_document_list,
link_document_list_deleted, link_duplicated_document_list
link_document_list_recent_added, link_document_list_favorites,
link_document_list, link_document_list_deleted,
link_duplicated_document_list,
)
)
@@ -446,6 +450,7 @@ class DocumentsApp(MayanAppConfig):
# Document object links
menu_object.bind_links(
links=(
link_document_favorites_add, link_document_favorites_remove,
link_document_edit, link_document_document_type_edit,
link_document_print, link_document_trash,
link_document_quick_download, link_document_download,
@@ -488,10 +493,12 @@ class DocumentsApp(MayanAppConfig):
)
menu_multi_item.bind_links(
links=(
link_document_multiple_favorites_add,
link_document_multiple_favorites_remove,
link_document_multiple_clear_transformations,
link_document_multiple_trash, link_document_multiple_download,
link_document_multiple_update_page_count,
link_document_multiple_document_type_edit
link_document_multiple_document_type_edit,
), sources=(Document,)
)
menu_multi_item.bind_links(

View File

@@ -23,6 +23,7 @@ icon_document_duplicates_list = Icon(
)
icon_document_list = Icon(driver_name='fontawesome', symbol='file')
icon_document_list_deleted = Icon(driver_name='fontawesome', symbol='trash')
icon_document_list_favorites = Icon(driver_name='fontawesome', symbol='star')
icon_document_list_recent_access = Icon(
driver_name='fontawesome', symbol='clock'
)

View File

@@ -9,16 +9,17 @@ from navigation import Link
from .icons import (
icon_clear_image_cache, icon_document_duplicates_list, icon_document_list,
icon_document_list_deleted, icon_document_list_recent_access,
icon_document_list_recent_added, icon_document_page_navigation_first,
icon_document_page_navigation_last, icon_document_page_navigation_next,
icon_document_page_navigation_previous, icon_document_page_return,
icon_document_page_rotate_left, icon_document_page_rotate_right,
icon_document_page_zoom_in, icon_document_page_zoom_out,
icon_document_pages, icon_document_preview, icon_document_properties,
icon_document_type_setup, icon_document_version_list,
icon_document_version_return_document, icon_document_version_return_list,
icon_duplicated_document_list, icon_duplicated_document_scan
icon_document_list_deleted, icon_document_list_favorites,
icon_document_list_recent_access, icon_document_list_recent_added,
icon_document_page_navigation_first, icon_document_page_navigation_last,
icon_document_page_navigation_next, icon_document_page_navigation_previous,
icon_document_page_return, icon_document_page_rotate_left,
icon_document_page_rotate_right, icon_document_page_zoom_in,
icon_document_page_zoom_out, icon_document_pages, icon_document_preview,
icon_document_properties, icon_document_type_setup,
icon_document_version_list, icon_document_version_return_document,
icon_document_version_return_list, icon_duplicated_document_list,
icon_duplicated_document_scan
)
from .permissions import (
permission_document_delete, permission_document_download,
@@ -98,6 +99,16 @@ link_document_delete = Link(
args='resolved_object.id', permissions=(permission_document_delete,),
tags='dangerous', text=_('Delete'), view='documents:document_delete',
)
link_document_favorites_add = Link(
args='resolved_object.id',
permissions=(permission_document_view,), text=_('Add to favorites'),
view='documents:document_add_to_favorites',
)
link_document_favorites_remove = Link(
args='resolved_object.id',
permissions=(permission_document_view,), text=_('Remove from favorites'),
view='documents:document_remove_from_favorites',
)
link_document_trash = Link(
args='resolved_object.id', permissions=(permission_document_trash,),
tags='dangerous', text=_('Move to trash'),
@@ -147,6 +158,14 @@ link_document_multiple_delete = Link(
tags='dangerous', text=_('Delete'),
view='documents:document_multiple_delete'
)
link_document_multiple_favorites_add = Link(
text=_('Add to favorites'),
view='documents:document_multiple_add_to_favorites',
)
link_document_multiple_favorites_remove = Link(
text=_('Remove from favorites'),
view='documents:document_multiple_remove_from_favorites',
)
link_document_multiple_document_type_edit = Link(
text=_('Change type'),
view='documents:document_multiple_document_type_edit'
@@ -189,6 +208,10 @@ link_document_list = Link(
icon_class=icon_document_list, text=_('All documents'),
view='documents:document_list'
)
link_document_list_favorites = Link(
icon_class=icon_document_list_favorites, text=_('Favorites'),
view='documents:document_list_favorites'
)
link_document_list_recent_access = Link(
icon_class=icon_document_list_recent_access, text=_('Recently accessed'),
view='documents:document_list_recent_access'

View File

@@ -11,7 +11,7 @@ from django.utils.encoding import force_text
from django.utils.timezone import now
from .literals import STUB_EXPIRATION_INTERVAL
from .settings import setting_recent_access_count
from .settings import setting_favorite_count, setting_recent_access_count
logger = logging.getLogger(__name__)
@@ -184,6 +184,43 @@ class DuplicatedDocumentManager(models.Manager):
self.scan_for(document=document, scan_children=False)
class FavoriteDocumentManager(models.Manager):
def add_for_user(self, user, document):
favorite_document, created = self.model.objects.get_or_create(
user=user, document=document
)
old_favorites_to_delete = self.filter(user=user).values_list('pk', flat=True)[setting_favorite_count.value:]
self.filter(pk__in=list(old_favorites_to_delete)).delete()
def get_by_natural_key(self, datetime_accessed, document_natural_key, user_natural_key):
Document = apps.get_model(
app_label='documents', model_name='Document'
)
User = get_user_model()
try:
document = Document.objects.get_by_natural_key(*document_natural_key)
except Document.DoesNotExist:
raise self.model.DoesNotExist
else:
try:
user = User.objects.get_by_natural_key(*user_natural_key)
except User.DoesNotExist:
raise self.model.DoesNotExist
return self.get(document__pk=document.pk, user__pk=user.pk)
def get_for_user(self, user):
Document = apps.get_model(
app_label='documents', model_name='Document'
)
return Document.objects.filter(favorites__user=user)
def remove_for_user(self, user, document):
self.get(user=user, document=document).delete()
class PassthroughManager(models.Manager):
pass
@@ -224,14 +261,16 @@ class RecentDocumentManager(models.Manager):
)
def get_for_user(self, user):
document_model = apps.get_model('documents', 'document')
Document = apps.get_model(
app_label='documents', model_name='Document'
)
if user.is_authenticated:
return document_model.objects.filter(
recentdocument__user=user
).order_by('-recentdocument__datetime_accessed')
return Document.objects.filter(
recent__user=user
).order_by('-recent__datetime_accessed')
else:
return document_model.objects.none()
return Document.objects.none()
class TrashCanManager(models.Manager):

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-08-23 04:52
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('documents', '0043_auto_20180429_0759'),
]
operations = [
migrations.CreateModel(
name='FavoriteDocument',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'verbose_name': 'Favorite document',
'verbose_name_plural': 'Favorite documents',
},
),
migrations.AlterModelOptions(
name='document',
options={'ordering': ('label',), 'verbose_name': 'Document', 'verbose_name_plural': 'Documents'},
),
migrations.AlterField(
model_name='recentdocument',
name='document',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='recent', to='documents.Document', verbose_name='Document'),
),
migrations.AddField(
model_name='favoritedocument',
name='document',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='favorites', to='documents.Document', verbose_name='Document'),
),
migrations.AddField(
model_name='favoritedocument',
name='user',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

View File

@@ -37,8 +37,8 @@ from .events import (
from .literals import DEFAULT_DELETE_PERIOD, DEFAULT_DELETE_TIME_UNIT
from .managers import (
DocumentManager, DocumentPageManager, DocumentVersionManager,
DocumentTypeManager, DuplicatedDocumentManager, PassthroughManager,
RecentDocumentManager, TrashCanManager
DocumentTypeManager, DuplicatedDocumentManager, FavoriteDocumentManager,
PassthroughManager, RecentDocumentManager, TrashCanManager
)
from .permissions import permission_document_view
from .settings import (
@@ -988,39 +988,6 @@ class DocumentPageResult(DocumentPage):
verbose_name_plural = _('Document pages')
@python_2_unicode_compatible
class RecentDocument(models.Model):
"""
Keeps a list of the n most recent accessed or created document for
a given user
"""
user = models.ForeignKey(
db_index=True, editable=False, on_delete=models.CASCADE,
to=settings.AUTH_USER_MODEL, verbose_name=_('User')
)
document = models.ForeignKey(
editable=False, on_delete=models.CASCADE, to=Document,
verbose_name=_('Document')
)
datetime_accessed = models.DateTimeField(
auto_now=True, db_index=True, verbose_name=_('Accessed')
)
objects = RecentDocumentManager()
class Meta:
ordering = ('-datetime_accessed',)
verbose_name = _('Recent document')
verbose_name_plural = _('Recent documents')
def __str__(self):
return force_text(self.document)
def natural_key(self):
return (self.datetime_accessed, self.document.natural_key(), self.user.natural_key())
natural_key.dependencies = ['documents.Document', settings.AUTH_USER_MODEL]
@python_2_unicode_compatible
class DuplicatedDocument(models.Model):
document = models.ForeignKey(
@@ -1042,3 +1009,64 @@ class DuplicatedDocument(models.Model):
def __str__(self):
return force_text(self.document)
@python_2_unicode_compatible
class FavoriteDocument(models.Model):
"""
Keeps a list of the favorited documents of a given user
"""
user = models.ForeignKey(
db_index=True, editable=False, on_delete=models.CASCADE,
to=settings.AUTH_USER_MODEL, verbose_name=_('User')
)
document = models.ForeignKey(
editable=False, on_delete=models.CASCADE, related_name='favorites',
to=Document, verbose_name=_('Document')
)
objects = FavoriteDocumentManager()
class Meta:
verbose_name = _('Favorite document')
verbose_name_plural = _('Favorite documents')
def __str__(self):
return force_text(self.document)
def natural_key(self):
return (self.document.natural_key(), self.user.natural_key())
natural_key.dependencies = ['documents.Document', settings.AUTH_USER_MODEL]
@python_2_unicode_compatible
class RecentDocument(models.Model):
"""
Keeps a list of the n most recent accessed or created document for
a given user
"""
user = models.ForeignKey(
db_index=True, editable=False, on_delete=models.CASCADE,
to=settings.AUTH_USER_MODEL, verbose_name=_('User')
)
document = models.ForeignKey(
editable=False, on_delete=models.CASCADE, related_name='recent',
to=Document, verbose_name=_('Document')
)
datetime_accessed = models.DateTimeField(
auto_now=True, db_index=True, verbose_name=_('Accessed')
)
objects = RecentDocumentManager()
class Meta:
ordering = ('-datetime_accessed',)
verbose_name = _('Recent document')
verbose_name_plural = _('Recent documents')
def __str__(self):
return force_text(self.document)
def natural_key(self):
return (self.datetime_accessed, self.document.natural_key(), self.user.natural_key())
natural_key.dependencies = ['documents.Document', settings.AUTH_USER_MODEL]

View File

@@ -47,6 +47,12 @@ setting_display_height = namespace.add_setting(
setting_display_width = namespace.add_setting(
global_name='DOCUMENTS_DISPLAY_WIDTH', default='3600'
)
setting_favorite_count = namespace.add_setting(
global_name='DOCUMENTS_FAVORITE_COUNT', default=40,
help_text=_(
'Maximum number of favorite documents to remember per user.'
)
)
setting_fix_orientation = namespace.add_setting(
global_name='DOCUMENTS_FIX_ORIENTATION', default=False,
help_text=_(

View File

@@ -32,6 +32,7 @@ from .views import (
DocumentVersionDownloadFormView, DocumentVersionDownloadView,
DocumentVersionListView, DocumentVersionRevertView, DocumentVersionView,
DocumentView, DuplicatedDocumentListView, EmptyTrashCanView,
FavoriteAddView, FavoriteDocumentListView, FavoriteRemoveView,
RecentAccessDocumentListView, RecentAddedDocumentListView,
ScanDuplicatedDocuments
)
@@ -56,6 +57,10 @@ urlpatterns = [
DuplicatedDocumentListView.as_view(),
name='duplicated_document_list'
),
url(
r'^list/favorites/$', FavoriteDocumentListView.as_view(),
name='document_list_favorites'
),
url(
r'^(?P<pk>\d+)/preview/$', DocumentPreviewView.as_view(),
name='document_preview'
@@ -68,6 +73,22 @@ urlpatterns = [
r'^(?P<pk>\d+)/duplicates/$', DocumentDuplicatesListView.as_view(),
name='document_duplicates_list'
),
url(
r'^(?P<pk>\d+)/add_to_favorites/$', FavoriteAddView.as_view(),
name='document_add_to_favorites'
),
url(
r'^multiple/add_to_favorites/$', FavoriteAddView.as_view(),
name='document_multiple_add_to_favorites'
),
url(
r'^(?P<pk>\d+)/remove_from_favorites/$', FavoriteRemoveView.as_view(),
name='document_remove_from_favorites'
),
url(
r'^multiple/remove_from_favorites/$', FavoriteRemoveView.as_view(),
name='document_multiple_remove_from_favorites'
),
url(
r'^(?P<pk>\d+)/restore/$', DocumentRestoreView.as_view(),
name='document_restore'

View File

@@ -12,10 +12,12 @@ from django.utils.translation import ugettext_lazy as _, ungettext
from acls.models import AccessControlList
from common.compressed_files import CompressedFile
from common.exceptions import ActionError
from common.generics import (
ConfirmView, FormView, MultipleObjectConfirmActionView,
MultipleObjectFormActionView, SingleObjectDetailView,
SingleObjectDownloadView, SingleObjectEditView, SingleObjectListView
MultipleObjectConfirmActionView, MultipleObjectFormActionView,
SingleObjectDetailView, SingleObjectDownloadView, SingleObjectEditView,
SingleObjectListView
)
from common.mixins import MultipleInstanceActionMixin
from common.utils import encapsulate
@@ -30,9 +32,11 @@ from ..forms import (
DocumentPreviewForm, DocumentPrintForm, DocumentPropertiesForm,
DocumentTypeSelectForm,
)
from ..icons import icon_document_list_favorites
from ..literals import PAGE_RANGE_RANGE, DEFAULT_ZIP_FILENAME
from ..models import (
DeletedDocument, Document, DuplicatedDocument, RecentDocument
DeletedDocument, Document, DuplicatedDocument, FavoriteDocument,
RecentDocument
)
from ..permissions import (
permission_document_delete, permission_document_download,
@@ -813,9 +817,85 @@ class DuplicatedDocumentListView(DocumentListView):
return context
class FavoriteDocumentListView(DocumentListView):
def get_document_queryset(self):
return FavoriteDocument.objects.get_for_user(user=self.request.user)
def get_extra_context(self):
context = super(FavoriteDocumentListView, self).get_extra_context()
context.update(
{
'title': _('Favorites'),
}
)
return context
class FavoriteAddView(MultipleObjectConfirmActionView):
model = Document
object_permission = permission_document_view
success_message = _(
'%(count)d document added to favorites.'
)
success_message_plural = _(
'%(count)d documents added to favorites.'
)
def get_extra_context(self):
queryset = self.get_queryset()
return {
'submit_label': _('Add'),
'submit_icon_class': icon_document_list_favorites,
'title': ungettext(
singular='Add the selected document to favorites',
plural='Add the selected documents to favorites',
number=queryset.count()
)
}
def object_action(self, form, instance):
FavoriteDocument.objects.add_for_user(
user=self.request.user, document=instance
)
class FavoriteRemoveView(MultipleObjectConfirmActionView):
error_message = _('Document "%(instance)s" is not in favorites.')
model = Document
object_permission = permission_document_view
success_message = _(
'%(count)d document removed to favorites.'
)
success_message_plural = _(
'%(count)d documents removed to favorites.'
)
def get_extra_context(self):
queryset = self.get_queryset()
return {
'submit_label': _('Remove'),
'submit_icon_class': icon_document_list_favorites,
'title': ungettext(
singular='Remove the selected document to favorites',
plural='Remove the selected documents to favorites',
number=queryset.count()
)
}
def object_action(self, form, instance):
try:
FavoriteDocument.objects.remove_for_user(
user=self.request.user, document=instance
)
except FavoriteDocument.DoesNotExist:
raise ActionError
class RecentAccessDocumentListView(DocumentListView):
def get_document_queryset(self):
return RecentDocument.objects.get_for_user(self.request.user)
return RecentDocument.objects.get_for_user(user=self.request.user)
def get_extra_context(self):
context = super(RecentAccessDocumentListView, self).get_extra_context()