Introduce bookmarks feature.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/forms/bookmarks.py Wed May 17 16:38:08 2017 +0200
@@ -0,0 +1,52 @@
+from django import forms
+from iconolab.models import Bookmark, BookmarkCategory
+import json, logging
+
+logger = logging.getLogger(__name__)
+
+class BookmarkForm(forms.ModelForm):
+
+ bookmark_category = forms.ChoiceField(required=False)
+ bookmark_category_new = forms.CharField(required=False)
+
+ class Meta:
+ model = Bookmark
+ fields = ('bookmark_category', 'bookmark_category_new')
+
+ def __init__(self, *args, **kwargs):
+
+ user = kwargs.pop('user', None)
+ super(BookmarkForm, self).__init__(*args, **kwargs)
+
+ if user.is_authenticated():
+
+ self.user = user
+
+ # Create the default category if not exists
+ try:
+ default_category = BookmarkCategory.objects.get(user=user, name='Favoris')
+ except BookmarkCategory.DoesNotExist:
+ default_category = BookmarkCategory(
+ user=user,
+ name='Favoris'
+ )
+ default_category.save()
+
+ bookmark_categories = BookmarkCategory.objects.filter(user=user).all()
+ choices = [(bookmark_category.id, bookmark_category.name) for bookmark_category in bookmark_categories]
+ for choice in choices:
+ self.fields['bookmark_category'].choices.append(choice)
+
+ def clean(self, *args, **kwargs):
+ cleaned_data = super(BookmarkForm, self).clean(*args, **kwargs)
+
+ if cleaned_data['bookmark_category_new']:
+ bookmark_category = BookmarkCategory(
+ user=self.user,
+ name=cleaned_data['bookmark_category_new']
+ )
+ bookmark_category.save()
+ else:
+ bookmark_category = BookmarkCategory.objects.get(id=cleaned_data['bookmark_category'])
+
+ cleaned_data['category'] = bookmark_category
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0022_auto_20170516_1538.py Wed May 17 16:38:08 2017 +0200
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2017-05-16 15:38
+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),
+ ('iconolab', '0021_folder_display_image'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Bookmark',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='BookmarkCategory',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='bookmark',
+ name='category',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.BookmarkCategory'),
+ ),
+ migrations.AddField(
+ model_name='bookmark',
+ name='image',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.Image'),
+ ),
+ ]
--- a/src/iconolab/models.py Tue May 16 12:36:38 2017 +0200
+++ b/src/iconolab/models.py Wed May 17 16:38:08 2017 +0200
@@ -1004,3 +1004,14 @@
def __str__(self):
return "profile:" + self.user.username
+
+
+class BookmarkCategory(models.Model):
+ user = models.ForeignKey(User)
+ name = models.CharField(max_length=255)
+ created = models.DateTimeField(auto_now_add=True)
+
+class Bookmark(models.Model):
+ category = models.ForeignKey(BookmarkCategory)
+ image = models.ForeignKey(Image)
+ created = models.DateTimeField(auto_now_add=True)
--- a/src/iconolab/templates/iconolab/collection_home.html Tue May 16 12:36:38 2017 +0200
+++ b/src/iconolab/templates/iconolab/collection_home.html Wed May 17 16:38:08 2017 +0200
@@ -139,4 +139,83 @@
</div>
</div>
+ <div class="modal fade" id="bookmark-modal" tabindex="-1" role="dialog" aria-labelledby="bookmark-modal-label">
+ <form method="post" class="form-horizontal" action="{% url 'image_bookmark' collection_name ':image_guid' %}">
+ {% csrf_token %}
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
+ <h4 class="modal-title" id="bookmark-modal-label">Ajouter à mes favoris</h4>
+ </div>
+ <div class="modal-body">
+ <div class="form-group">
+ <label class="col-sm-2 control-label">Objet</label>
+ <div class="col-sm-10">
+ <input ype="text" class="form-control bookmark-title" disabled="disabled" value="">
+ </div>
+ </div>
+ <div class="form-group bookmark-folder">
+ <label for="bookmark_folder" class="col-sm-2 control-label">Dossier</label>
+ <div class="col-sm-10">
+ <select class="form-control" name="{{ bookmark_form.bookmark_category.name }}">
+ {% for category_id, category_name in bookmark_form.bookmark_category.field.choices %}
+ <option value="{{ category_id }}">{{ category_name }}</option>
+ {% endfor %}
+ </select>
+ </div>
+ </div>
+ <div class="form-group bookmark-folder-new" style="display: none;">
+ <label for="bookmark_folder" class="col-sm-2 control-label">Nouveau dossier</label>
+ <div class="col-sm-10">
+ <input type="text" class="form-control" name="{{ bookmark_form.bookmark_category_new.name }}">
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button class="btn btn-sm btn-default bookmark-folder-add">Ajouter un dossier</button>
+ <button class="btn btn-sm btn-default bookmark-folder-cancel" style="display: none;">Annuler</button>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
+ <button type="submit" class="btn btn-primary">Valider</button>
+ </div>
+ </div>
+ </div>
+ </form>
+ </div>
{% endblock %}
+
+{% block footer_js %}
+<script>
+var $modal = $('#bookmark-modal');
+
+function toggleAddFolder(e) {
+ e.preventDefault();
+ $modal.find('.bookmark-folder-new').toggle();
+ $modal.find('.bookmark-folder-cancel').toggle();
+ $modal.find('.bookmark-folder-add').toggle();
+ $modal.find('.bookmark-folder').toggle();
+}
+
+$modal.find('.bookmark-folder-add').on('click', toggleAddFolder);
+$modal.find('.bookmark-folder-cancel').on('click', toggleAddFolder);
+
+$('.bookmark').on('click', function(e) {
+ e.preventDefault();
+
+ var $target = $(e.currentTarget);
+
+ $modal.find('.bookmark-title').val($target.data('item'));
+ $modal.find('.bookmark-image').val($target.data('image-id'));
+
+ var $form = $modal.find('form');
+ $form.attr('action', $form.attr('action').replace(':image_guid', $target.data('image-guid')));
+
+ $modal.modal();
+
+});
+</script>
+{% endblock %}
--- a/src/iconolab/templates/iconolab/user_base.html Tue May 16 12:36:38 2017 +0200
+++ b/src/iconolab/templates/iconolab/user_base.html Wed May 17 16:38:08 2017 +0200
@@ -17,6 +17,10 @@
class="list-group-item {% if request.resolver_match.url_name == 'user_annotations' %}active{% endif%}">
Annotations
</a>
+ <a href="{% url 'user_bookmarks' profile_user.username %}"
+ class="list-group-item {% if request.resolver_match.url_name == 'user_bookmarks' %}active{% endif%}">
+ Favoris
+ </a>
</div>
<div class="list-group">
{% if profile_user == request.user %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/user_bookmarks.html Wed May 17 16:38:08 2017 +0200
@@ -0,0 +1,61 @@
+{% extends 'iconolab/user_base.html' %}
+
+{% load thumbnail %}
+{% load humanize %}
+
+{% block user_content %}
+ <table class="table">
+ <thead>
+ <th></th>
+ <th>Collection</th>
+ <th>Catégorie</th>
+ <th></th>
+ <th></th>
+ </thead>
+ <tbody>
+ {% for bookmark in bookmarks %}
+ <tr>
+ <td width="10%">
+ {% thumbnail bookmark.image.media "100x100" crop=False as thumbnail %}
+ <a href="{% url 'image_detail' bookmark.image.item.collection.name bookmark.image.image_guid %}">
+ <img src="{{ thumbnail.url }}" width="{{ thumbnail.width }}" height="{{ thumbnail.height }}" />
+ </a>
+ {% endthumbnail %}
+ </td>
+ <td>{{ bookmark.image.item.collection.verbose_name }}</td>
+ <td>
+ {% if request.user.is_authenticated and profile_user == request.user %}
+ <form class="bookmark-edit-form" method="post" action="{% url 'bookmark_edit' bookmark.id %}">
+ {% csrf_token %}
+ <select name="category">
+ {% for bookmark_category in bookmark_categories %}
+ <option value="{{ bookmark_category.id }}" {% if bookmark_category == bookmark.category %}selected="selected"{% endif %}>{{ bookmark_category.name }}</option>
+ {% endfor %}
+ </select>
+ </form>
+ {% else %}
+ {{ bookmark.category.name }}
+ {% endif %}
+ </td>
+ <td>{{ bookmark.created|naturaltime }}</td>
+ <td class="text-right">
+ {% if request.user.is_authenticated and profile_user == request.user %}
+ <form method="post" action="{% url 'bookmark_delete' bookmark.id %}">
+ {% csrf_token %}
+ <button type="submit" class="btn btn-danger btn-xs">Supprimer</button>
+ </form>
+ {% endif %}
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+{% endblock %}
+
+{% block footer_js %}
+<script>
+$('.bookmark-edit-form').find('select[name="category"]').on('change', function() {
+ $(this).closest('form').trigger('submit');
+});
+</script>
+{% endblock %}
--- a/src/iconolab/templates/partials/item_images_preview.html Tue May 16 12:36:38 2017 +0200
+++ b/src/iconolab/templates/partials/item_images_preview.html Wed May 17 16:38:08 2017 +0200
@@ -10,6 +10,11 @@
<img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
</a>
{% endthumbnail %}
+ <a href="#" class="bookmark"
+ title="Ajouter à mes favoris"
+ data-item="{{ item.metadatas.joconde_ref }}"
+ data-image-id="{{ first_image.id }}"
+ data-image-guid="{{ first_image.image_guid }}"><i class="fa fa-lg fa-star"></i></a>
</div>
{% if item.images.count > 1 %}
--- a/src/iconolab/urls.py Tue May 16 12:36:38 2017 +0200
+++ b/src/iconolab/urls.py Wed May 17 16:38:08 2017 +0200
@@ -36,6 +36,7 @@
url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/items/?$', django_views.generic.RedirectView.as_view(pattern_name="collection_home")),
url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/?$', django_views.generic.RedirectView.as_view(pattern_name="collection_home")),
url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)$', views.objects.ShowImageView.as_view(), name='image_detail'),
+ url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/bookmark/?$', views.objects.BookmarkImageView.as_view(), name='image_bookmark'),
url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/?$', django_views.generic.RedirectView.as_view(pattern_name="image_detail")),
url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/create$', login_required(views.objects.CreateAnnotationView.as_view()), name='annotation_create'),
url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/?$', views.objects.ShowAnnotationView.as_view(), name='annotation_detail'),
@@ -53,6 +54,9 @@
url(r'^user/(?P<slug>[a-z0-9\-]+)/commented/?$', views.userpages.UserCommentedView.as_view(), name="user_commented"),
url(r'^user/(?P<slug>[a-z0-9\-]+)/contributed/?$', views.userpages.UserContributedView.as_view(), name="user_contributed"),
url(r'^user/(?P<slug>[a-z0-9\-]+)/annotations/?$', views.userpages.UserAnnotationsView.as_view(), name="user_annotations"),
+ url(r'^user/(?P<slug>[a-z0-9\-]+)/bookmarks/?$', views.userpages.UserBookmarksView.as_view(), name="user_bookmarks"),
+ url(r'^bookmarks/(?P<bookmark>[0-9]+)/delete/?$', views.userpages.BookmarkDeleteView.as_view(), name="bookmark_delete"),
+ url(r'^bookmarks/(?P<bookmark>[0-9]+)/edit/?$', views.userpages.BookmarkEditView.as_view(), name="bookmark_edit"),
url(r'^user/adminpanel/(?P<collection_name>[a-z0-9\-]+)/$', views.userpages.UserCollectionAdminView.as_view(), name="user_admin_panel"),
url(r'^user/notifications/all/?$', login_required(views.userpages.UserNotificationsView.as_view()), name="user_notifications"),
--- a/src/iconolab/views/objects.py Tue May 16 12:36:38 2017 +0200
+++ b/src/iconolab/views/objects.py Wed May 17 16:38:08 2017 +0200
@@ -13,8 +13,9 @@
from django.contrib.sites.models import Site
from django.conf import settings
from notifications.models import Notification
-from iconolab.models import Annotation, AnnotationRevision, Collection, Folder, Item, Image, IconolabComment, MetaCategory, MetaCategoryInfo
+from iconolab.models import Annotation, AnnotationRevision, Collection, Folder, Item, Image, IconolabComment, MetaCategory, MetaCategoryInfo, BookmarkCategory, Bookmark
from iconolab.forms.annotations import AnnotationRevisionForm
+from iconolab.forms.bookmarks import BookmarkForm
from iconolab.serializers import AnnotationRevisionSerializer
import logging
@@ -309,6 +310,8 @@
+"&revised_perpage="+str(revised_per_page)
)
+ context['bookmark_form'] = BookmarkForm(user=request.user)
+
return render(request, 'iconolab/collection_home.html', context)
@@ -410,6 +413,30 @@
context['form'] = AnnotationRevisionForm()
return render(request, 'iconolab/detail_image.html', context)
+class BookmarkImageView(View, ContextMixin, IconolabObjectView):
+ def post(self, request, *args, **kwargs):
+ success, result = self.check_kwargs(kwargs)
+ if success:
+ (collection, image) = result
+ else:
+ return result(request)
+
+ context = super(BookmarkImageView, self).get_context_data(**kwargs)
+
+ bookmark_form = BookmarkForm(request.POST, user=request.user)
+ if bookmark_form.is_valid():
+ bookmark = Bookmark(
+ image=image,
+ category=bookmark_form.cleaned_data['category']
+ )
+ bookmark.save()
+
+ context['bookmark_form'] = bookmark_form
+
+ redirect_url = reverse('collection_home', kwargs={'collection_name': self.kwargs.get('collection_name', '')})
+
+ return redirect(redirect_url)
+
class CreateAnnotationView(View, ContextMixin, IconolabObjectView):
"""
View that displays annotation forms and handles annotation creation
--- a/src/iconolab/views/userpages.py Tue May 16 12:36:38 2017 +0200
+++ b/src/iconolab/views/userpages.py Wed May 17 16:38:08 2017 +0200
@@ -9,7 +9,7 @@
from django.conf import settings
from django.urls import reverse
from notifications.models import Notification
-from iconolab.models import Collection, Annotation, AnnotationRevision, IconolabComment, Image, MetaCategoriesCountInfo
+from iconolab.models import Collection, Annotation, AnnotationRevision, IconolabComment, Image, MetaCategoriesCountInfo, Bookmark, BookmarkCategory
from iconolab.auth.forms import UserForm
from itertools import chain
from uuid import UUID
@@ -113,6 +113,61 @@
context['profile_user'] = profile_user
return render(request, 'iconolab/user_annotations.html', context)
+class UserBookmarksView(DetailView):
+
+ model = User
+ slug_field = 'username'
+
+ def get_context_data(self, **kwargs):
+ context = super(UserBookmarksView, self).get_context_data(**kwargs)
+ return context
+
+ def get(self, request, *args, **kwargs):
+ self.object = self.get_object()
+ profile_user = self.object
+ context = self.get_context_data()
+
+ context['profile_user'] = profile_user
+ context['bookmarks'] = Bookmark.objects.filter(category__user=profile_user).all()
+
+ if (request.user.is_authenticated()):
+ context['bookmark_categories'] = BookmarkCategory.objects.filter(user=request.user).all()
+
+ return render(request, 'iconolab/user_bookmarks.html', context)
+
+class BookmarkDeleteView(View):
+
+ def post(self, request, *args, **kwargs):
+
+ if request.user.is_authenticated():
+ bookmark_id = kwargs.get('bookmark')
+ bookmark = Bookmark.objects.get(id=bookmark_id)
+ if bookmark.category.user == request.user:
+ bookmark.delete()
+ else:
+ return redirect(reverse('home'))
+ else:
+ return redirect(reverse('home'))
+
+ return redirect(reverse('user_bookmarks', kwargs={'slug': request.user.username}))
+
+class BookmarkEditView(View):
+
+ def post(self, request, *args, **kwargs):
+
+ if request.user.is_authenticated():
+ bookmark_id = kwargs.get('bookmark')
+ bookmark = Bookmark.objects.get(id=bookmark_id)
+ if bookmark.category.user == request.user:
+ bookmark.category = BookmarkCategory.objects.get(id=request.POST.get('category'))
+ bookmark.save()
+ else:
+ return redirect(reverse('home'))
+ else:
+ return redirect(reverse('home'))
+
+ return redirect(reverse('user_bookmarks', kwargs={'slug': request.user.username}))
+
class UserCommentedView(DetailView):
"""
View that displays the full paginated list of annotations on which the considered user has commented
--- a/src_js/iconolab-bundle/src/iconolab.scss Tue May 16 12:36:38 2017 +0200
+++ b/src_js/iconolab-bundle/src/iconolab.scss Wed May 17 16:38:08 2017 +0200
@@ -40,9 +40,20 @@
}
}
.main-image {
+ position: relative;
a {
display: block;
}
+ .bookmark {
+ color: #E67E22;
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ &:hover,
+ &:active {
+ color: darken(#E67E22, 10%);
+ }
+ }
}
.item-image-stats {
display: flex;