add gallery support (taken from #7344)
authorFrédéric Péters <fpeters@0d.be>
Sat, 20 Feb 2016 10:56:53 +0000 (11:56 +0100)
committerFrédéric Péters <fpeters@0d.be>
Sat, 20 Feb 2016 10:56:53 +0000 (11:56 +0100)
16 files changed:
MANIFEST.in
gallery/__init__.py [new file with mode: 0644]
gallery/forms.py [new file with mode: 0644]
gallery/migrations/0001_initial.py [new file with mode: 0644]
gallery/migrations/0002_image_title.py [new file with mode: 0644]
gallery/migrations/0003_gallerycell_title.py [new file with mode: 0644]
gallery/migrations/__init__.py [new file with mode: 0644]
gallery/models.py [new file with mode: 0644]
gallery/static/js/combo.gallery.js [new file with mode: 0644]
gallery/templates/combo/gallery.html [new file with mode: 0644]
gallery/templates/combo/gallery_image_form.html [new file with mode: 0644]
gallery/templates/combo/gallery_manager.html [new file with mode: 0644]
gallery/templates/combo/manager/gallery/gallerycell.html [new file with mode: 0644]
gallery/urls.py [new file with mode: 0644]
gallery/views.py [new file with mode: 0644]
setup.py

index 7f82931..1690b3f 100644 (file)
@@ -1,3 +1,6 @@
 include AUTHORS
 include COPYING
 include README
+
+recursive-include combo/static *.js
+recursive-include combo/templates *.html
diff --git a/gallery/__init__.py b/gallery/__init__.py
new file mode 100644 (file)
index 0000000..d98cf04
--- /dev/null
@@ -0,0 +1,26 @@
+# combo - content management system
+# Copyright (C) 2015  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import django.apps
+
+class AppConfig(django.apps.AppConfig):
+    name = 'gallery'
+
+    def get_after_manager_urls(self):
+        from . import urls
+        return urls.manager_urlpatterns
+
+default_app_config = 'gallery.AppConfig'
diff --git a/gallery/forms.py b/gallery/forms.py
new file mode 100644 (file)
index 0000000..c4a1420
--- /dev/null
@@ -0,0 +1,31 @@
+# combo - content management system
+# Copyright (C) 2015  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from .models import Image
+
+class ImageAddForm(forms.ModelForm):
+    class Meta:
+        model = Image
+        fields = ('image', 'title',)
+
+
+class ImageEditForm(forms.ModelForm):
+    class Meta:
+        model = Image
+        fields = ('title',)
diff --git a/gallery/migrations/0001_initial.py b/gallery/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..d7e05d0
--- /dev/null
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('auth', '0001_initial'),
+        ('data', '0005_auto_20150226_0903'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='GalleryCell',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('placeholder', models.CharField(max_length=20)),
+                ('order', models.PositiveIntegerField()),
+                ('slug', models.SlugField(verbose_name='Slug', blank=True)),
+                ('public', models.BooleanField(default=True, verbose_name='Public')),
+                ('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
+                ('page', models.ForeignKey(to='data.Page')),
+            ],
+            options={
+                'verbose_name': 'Gallery',
+            },
+            bases=(models.Model,),
+        ),
+        migrations.CreateModel(
+            name='Image',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('image', models.ImageField(upload_to=b'uploads/gallery/%Y/%m/', verbose_name='Image')),
+                ('order', models.PositiveIntegerField()),
+                ('gallery', models.ForeignKey(verbose_name='Gallery', to='gallery.GalleryCell')),
+            ],
+            options={
+                'ordering': ['order'],
+            },
+            bases=(models.Model,),
+        ),
+    ]
diff --git a/gallery/migrations/0002_image_title.py b/gallery/migrations/0002_image_title.py
new file mode 100644 (file)
index 0000000..32bd732
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('gallery', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='image',
+            name='title',
+            field=models.CharField(max_length=50, verbose_name='Title', blank=True),
+            preserve_default=True,
+        ),
+    ]
diff --git a/gallery/migrations/0003_gallerycell_title.py b/gallery/migrations/0003_gallerycell_title.py
new file mode 100644 (file)
index 0000000..57cb09c
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('gallery', '0002_image_title'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='gallerycell',
+            name='title',
+            field=models.CharField(max_length=50, null=True, verbose_name='Title', blank=True),
+            preserve_default=True,
+        ),
+    ]
diff --git a/gallery/migrations/__init__.py b/gallery/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gallery/models.py b/gallery/models.py
new file mode 100644 (file)
index 0000000..9d4a8d3
--- /dev/null
@@ -0,0 +1,51 @@
+# combo - content management system
+# Copyright (C) 2015  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django import template
+from django.db import models
+from django.forms import models as model_forms
+from django.utils.translation import ugettext_lazy as _
+
+from combo.data.models import CellBase
+from combo.data.library import register_cell_class
+
+@register_cell_class
+class GalleryCell(CellBase):
+    title = models.CharField(_('Title'), max_length=50, blank=True, null=True)
+    manager_form_template = 'combo/gallery_manager.html'
+
+    class Meta:
+        verbose_name = _('Gallery')
+
+    def render(self, context):
+        gallery_template = template.loader.get_template('combo/gallery.html')
+        return gallery_template.render(context)
+
+    def get_additional_label(self):
+        if self.title:
+            return self.title
+        return ''
+
+
+class Image(models.Model):
+    gallery = models.ForeignKey(GalleryCell, verbose_name=_('Gallery'))
+    image = models.ImageField(_('Image'),
+            upload_to='uploads/gallery/%Y/%m/')
+    order = models.PositiveIntegerField()
+    title = models.CharField(_('Title'), max_length=50, blank=True)
+
+    class Meta:
+        ordering = ['order']
diff --git a/gallery/static/js/combo.gallery.js b/gallery/static/js/combo.gallery.js
new file mode 100644 (file)
index 0000000..a91e714
--- /dev/null
@@ -0,0 +1,29 @@
+function gallery(element) {
+  var element_id = '#' + $(element).attr('id');
+  $(element).sortable({
+    items: '> li',
+    containment: 'parent',
+    placeholder: 'empty-image',
+    update: function(event, ui) {
+      var new_order = $(element).find('> li').map(function() { return $(this).data('object-id'); }).get().join();
+      $.ajax({
+        url: $(element).data('order-url'),
+        data: {'new-order': new_order},
+        success: function(data, status) {
+          $(element).replaceWith($(data).find(element_id));
+          gallery($(element_id));
+        }
+      });
+    }
+  });
+  $('.image-delete').on('click', function() {
+    $.ajax({
+      url: $(this).attr('href'),
+      success: function(data, status) {
+        $(element).replaceWith($(data).find(element_id));
+        gallery($(element_id));
+      }
+    });
+    return false;
+  });
+};
diff --git a/gallery/templates/combo/gallery.html b/gallery/templates/combo/gallery.html
new file mode 100644 (file)
index 0000000..627ab10
--- /dev/null
@@ -0,0 +1,29 @@
+{% load thumbnail %}
+<div class="gallery" id="gallery-{{cell.id}}">
+{% for image in cell.image_set.all %}
+  {% if forloop.first %}
+{% thumbnail image.image "640x480" crop="50% 25%" as im %}
+<div class="first">
+<img src="{{ im.url }}"/>
+<span>{% if image.title %}{{ image.title }}{% endif %}</span>
+</div>
+<div>
+{% endthumbnail %}
+  {% endif %}
+{% thumbnail image.image "60x60" crop="50% 25%" as im %}
+{% thumbnail image.image "640x480" crop="50% 25%" as im_large %}
+<span data-image-large="{{ im_large.url }}"><img src="{{ im.url }}"
+        {% if image.title %} title="{{image.title}}" {% endif %}/></span>
+{% endthumbnail %}
+{% endthumbnail %}
+{% endfor %}
+</div>
+</div>
+<script type="text/javascript">
+$(function() {
+  var $gallery = $('#gallery-{{cell.id}}');
+  $gallery.find('span').on('click', function() {
+    $gallery.find('div.first img').attr('src', $(this).data('image-large'));
+  });
+});
+</script>
diff --git a/gallery/templates/combo/gallery_image_form.html b/gallery/templates/combo/gallery_image_form.html
new file mode 100644 (file)
index 0000000..e38d101
--- /dev/null
@@ -0,0 +1,26 @@
+{% extends "combo/manager_base.html" %}
+{% load i18n %}
+
+{% block appbar %}
+{% if object.id %}
+<h2>{% trans "Edit Image" %}</h2>
+{% else %}
+<h2>{% trans "New Image" %}</h2>
+{% endif %}
+{% endblock %}
+
+{% block content %}
+
+<form method="post" enctype="multipart/form-data">
+  {% csrf_token %}
+  {{ form.as_p }}
+  <div class="buttons">
+    <button>{% trans "Save" %}</button>
+    {% if object.id %}
+    <a class="cancel" href="{{ object.get_absolute_url }}">{% trans 'Cancel' %}</a>
+    {% else %}
+    <a class="cancel" href="{% url 'combo-manager-homepage' %}">{% trans 'Cancel' %}</a>
+    {% endif %}
+  </div>
+</form>
+{% endblock %}
diff --git a/gallery/templates/combo/gallery_manager.html b/gallery/templates/combo/gallery_manager.html
new file mode 100644 (file)
index 0000000..438a7e3
--- /dev/null
@@ -0,0 +1,34 @@
+{% extends 'combo/cell_form.html' %}
+{% load static thumbnail i18n %}
+
+{% block cell-form %}
+<form action="{{ url }}" method="post" data-label-url="{% url 'combo-manager-page-get-additional-label' page_pk=page.id cell_reference=cell.get_reference %}">
+{% csrf_token %}
+  {{ form.as_p }}
+
+<ul class="gallery" id="gallery-{{cell.id}}" data-order-url="{% url 'combo-gallery-image-order' gallery_pk=cell.id %}">
+{% for image in cell.image_set.all %}
+<li data-object-id="{{image.id}}">
+{% thumbnail image.image "120x120" crop="50% 25%" as im %}
+  <img height="120" src="{{ im.url }}"/>
+{% endthumbnail %}
+  <span class="image-actions">
+  <a rel="popup" class="image-edit icon-edit" href="{% url 'combo-gallery-image-edit' gallery_pk=cell.id pk=image.id %}"></a>
+  <a class="image-delete icon-remove-sign" href="{% url 'combo-gallery-image-delete' gallery_pk=cell.id pk=image.id %}"></a>
+  </span>
+</li>
+{% endfor %}
+</ul>
+
+<script src="{% static "js/combo.gallery.js" %}"></script>
+<script>gallery($('#gallery-{{cell.id}}'));</script>
+
+{% endblock %}
+
+
+{% block cell-buttons %}
+<a class="image-add" rel="popup" href="{% url 'combo-gallery-image-add' gallery_pk=cell.id %}">{% trans 'New Image' %}</a>
+|
+{{ block.super }}
+</form>
+{% endblock %}
diff --git a/gallery/templates/combo/manager/gallery/gallerycell.html b/gallery/templates/combo/manager/gallery/gallerycell.html
new file mode 100644 (file)
index 0000000..900eedd
--- /dev/null
@@ -0,0 +1 @@
+HLLOW
diff --git a/gallery/urls.py b/gallery/urls.py
new file mode 100644 (file)
index 0000000..9737a15
--- /dev/null
@@ -0,0 +1,30 @@
+# combo - content management system
+# Copyright (C) 2015  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.conf.urls import patterns, url
+
+from . import views
+
+manager_urlpatterns = patterns('',
+        url('^gallery/(?P<gallery_pk>\w+)/images/add/$', views.image_add,
+            name='combo-gallery-image-add'),
+        url('^gallery/(?P<gallery_pk>\w+)/order$', views.image_order,
+            name='combo-gallery-image-order'),
+        url('^gallery/(?P<gallery_pk>\w+)/images/(?P<pk>\w+)/edit$', views.image_edit,
+            name='combo-gallery-image-edit'),
+        url('^gallery/(?P<gallery_pk>\w+)/images/(?P<pk>\w+)/delete$', views.image_delete,
+            name='combo-gallery-image-delete'),
+)
diff --git a/gallery/views.py b/gallery/views.py
new file mode 100644 (file)
index 0000000..76ca351
--- /dev/null
@@ -0,0 +1,68 @@
+# combo - content management system
+# Copyright (C) 2015  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.core.urlresolvers import reverse, reverse_lazy
+from django.shortcuts import redirect
+from django.views.generic import (TemplateView, RedirectView, DetailView,
+        CreateView, UpdateView, ListView, DeleteView, FormView)
+
+from .models import Image, GalleryCell
+from .forms import ImageAddForm, ImageEditForm
+
+class ImageAddView(CreateView):
+    model = Image
+    template_name = 'combo/gallery_image_form.html'
+    form_class = ImageAddForm
+
+    def form_valid(self, form):
+        form.instance.gallery_id = self.kwargs.get('gallery_pk')
+        other_images = form.instance.gallery.image_set.all()
+        if other_images:
+            form.instance.order = max([x.order for x in other_images]) + 1
+        else:
+            form.instance.order = 0
+        return super(ImageAddView, self).form_valid(form)
+
+    def get_success_url(self):
+        return reverse('combo-manager-page-view', kwargs={'pk': self.object.gallery.page.id})
+
+image_add = ImageAddView.as_view()
+
+
+class ImageEditView(UpdateView):
+    model = Image
+    template_name = 'combo/gallery_image_form.html'
+    form_class = ImageEditForm
+
+    def get_success_url(self):
+        return reverse('combo-manager-page-view', kwargs={'pk': self.object.gallery.page.id})
+
+image_edit = ImageEditView.as_view()
+
+
+def image_delete(request, gallery_pk, pk):
+    gallery = GalleryCell.objects.get(id=gallery_pk)
+    Image.objects.get(id=pk).delete()
+    return redirect(reverse('combo-manager-page-view', kwargs={'pk': gallery.page.id}))
+
+
+def image_order(request, gallery_pk):
+    gallery = GalleryCell.objects.get(id=gallery_pk)
+    new_order = [int(x) for x in request.GET['new-order'].split(',')]
+    for image in gallery.image_set.all():
+        image.order = new_order.index(image.id)+1
+        image.save()
+    return redirect(reverse('combo-manager-page-view', kwargs={'pk': gallery.page.id}))
index 232076e..e94e158 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -18,6 +18,8 @@ setup(
     packages=[
         'panikombo',
         'panikombo.migrations',
+        'gallery',
+        'gallery.migrations',
     ],
     classifiers=[
         'Development Status :: 4 - Beta',