├── tests ├── __init__.py ├── logo.png ├── requirements.txt ├── settings.py └── tests_models.py ├── cmsplugin_gallery ├── settings.py ├── migrations │ ├── __init__.py │ ├── 0007_remove_image_src.py │ ├── 0004_auto_20170118_0753.py │ ├── 0003_auto_20170104_1122.py │ ├── 0002_auto_20161223_1005.py │ ├── 0006_auto_20180411_1046.py │ ├── 0005_auto_20180411_1046.py │ └── 0001_initial.py ├── __init__.py ├── locale │ ├── en │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pl │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ └── ru │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── admin.py ├── templates │ └── cmsplugin_gallery │ │ └── gallery.html ├── forms.py ├── cms_plugins.py ├── utils.py └── models.py ├── setup.cfg ├── MANIFEST.in ├── .gitignore ├── AUTHORS ├── .coveragerc ├── .travis.yml ├── tox.ini ├── .github └── workflows │ └── test.yml ├── History.md ├── LICENSE ├── Makefile ├── setup.py └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cmsplugin_gallery/settings.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cmsplugin_gallery/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cmsplugin_gallery/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '2.0.3' 2 | -------------------------------------------------------------------------------- /tests/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrkilczuk/cmsplugin_gallery/HEAD/tests/logo.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = false 3 | 4 | [options] 5 | install_requires = 6 | Django>=2.2,<4.0 7 | python_requires = >=3.7 8 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | # requirements from setup.py 2 | # other requirements 3 | django-app-helper==3.0.1 4 | tox 5 | coverage 6 | Django==3.2 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include History.md 3 | recursive-include cmsplugin_gallery/locale * 4 | recursive-include cmsplugin_gallery/templates * 5 | -------------------------------------------------------------------------------- /cmsplugin_gallery/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrkilczuk/cmsplugin_gallery/HEAD/cmsplugin_gallery/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /cmsplugin_gallery/locale/pl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrkilczuk/cmsplugin_gallery/HEAD/cmsplugin_gallery/locale/pl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /cmsplugin_gallery/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrkilczuk/cmsplugin_gallery/HEAD/cmsplugin_gallery/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | build/* 4 | .idea 5 | .project 6 | .pydevproject 7 | .settings 8 | .coverage 9 | *.egg-info 10 | dist 11 | .eggs 12 | tests/_env 13 | tests/_oldenv 14 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ales Zabala Alava 2 | Dries Desmet 3 | eserge 4 | Lalo Martins 5 | Piotr Kilczuk 6 | Vinit Kumar 7 | 8 | https://github.com/centralniak/cmsplugin_gallery/contributors 9 | 10 | Thanks! -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = cmsplugin_gallery 4 | omit = 5 | migrations/* 6 | tests/* 7 | 8 | [report] 9 | exclude_lines = 10 | pragma: no cover 11 | def __repr__ 12 | if self.debug: 13 | if settings.DEBUG 14 | raise AssertionError 15 | raise NotImplementedError 16 | if 0: 17 | if __name__ == .__main__.: 18 | ignore_errors = True 19 | -------------------------------------------------------------------------------- /cmsplugin_gallery/migrations/0007_remove_image_src.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 1.11.12 on 2018-04-11 05:21 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('cmsplugin_gallery', '0006_auto_20180411_1046'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='image', 15 | name='src', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | 8 | sudo: false 9 | 10 | env: 11 | - TOX_ENV=py27-latest 12 | - TOX_ENV=py35-latest 13 | # Django 1.8 14 | - TOX_ENV=py27-dj18-cms34 15 | - TOX_ENV=py27-dj18-cms33 16 | - TOX_ENV=py35-dj18-cms34 17 | # Django 1.9 18 | - TOX_ENV=py27-dj19-cms34 19 | - TOX_ENV=py27-dj19-cms33 20 | - TOX_ENV=py35-dj19-cms34 21 | 22 | install: 23 | - pip install tox coverage 24 | 25 | script: 26 | - tox -e $TOX_ENV 27 | -------------------------------------------------------------------------------- /cmsplugin_gallery/admin.py: -------------------------------------------------------------------------------- 1 | from inline_ordering.admin import OrderableStackedInline 2 | from . import forms 3 | from . import models 4 | 5 | 6 | class ImageInline(OrderableStackedInline): 7 | 8 | model = models.Image 9 | 10 | def formfield_for_dbfield(self, db_field, **kwargs): 11 | if db_field.name == 'image_src': 12 | kwargs.pop('request', None) 13 | kwargs['widget'] = forms.AdminImageWidget 14 | return db_field.formfield(**kwargs) 15 | return super().\ 16 | formfield_for_dbfield(db_field, **kwargs) 17 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py{35,27}-latest 4 | py{35,27}-dj18-cms{34,33} 5 | py{35,27}-dj19-cms{34,33} 6 | 7 | skip_missing_interpreters=True 8 | 9 | 10 | [testenv] 11 | deps = 12 | -r{toxinidir}/tests/requirements.txt 13 | dj18: Django>=1.8,<1.9 14 | dj19: Django>=1.9,<1.10 15 | latest: django-cms 16 | cms33: django-cms>=3.3,<3.4 17 | cms34: django-cms>=3.4,<3.5 18 | commands = 19 | {envpython} --version 20 | {env:COMMAND:coverage} erase 21 | {env:COMMAND:coverage} run setup.py test 22 | {env:COMMAND:coverage} report 23 | 24 | [flake8] 25 | max-line-length = 120 26 | exclude = */docs/*,*/migrations/* 27 | -------------------------------------------------------------------------------- /cmsplugin_gallery/templates/cmsplugin_gallery/gallery.html: -------------------------------------------------------------------------------- 1 | {% load thumbnail %} 2 | 3 | 10 | 11 | {% for image in images %} 12 |
13 | 14 |
15 | {% endfor %} 16 | 17 | 20 | -------------------------------------------------------------------------------- /cmsplugin_gallery/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin.widgets import AdminFileWidget 2 | from django.utils.translation import gettext as _ 3 | from django.utils.safestring import mark_safe 4 | 5 | 6 | class AdminImageWidget(AdminFileWidget): 7 | def render(self, name, value, attrs=None): 8 | output = [] 9 | if value and getattr(value, "url", None): 10 | image_url = value.url 11 | file_name = str(value) 12 | output.append(' %s
%s ' % \ 13 | (f'{str(image_url)}', f'{str(image_url)}', f'{str(file_name)}', _('Change:'))) 14 | output.append(super(AdminFileWidget, self).render(name, value, attrs)) 15 | return mark_safe(''.join(output)) 16 | -------------------------------------------------------------------------------- /cmsplugin_gallery/migrations/0004_auto_20170118_0753.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | 3 | 4 | class Migration(migrations.Migration): 5 | 6 | dependencies = [ 7 | ('cmsplugin_gallery', '0003_auto_20170104_1122'), 8 | ] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name='image', 13 | name='alt', 14 | field=models.CharField(max_length=255, verbose_name='Alt text', blank=True), 15 | ), 16 | migrations.AlterField( 17 | model_name='image', 18 | name='crop', 19 | field=models.CharField(default=b'0%', max_length=10, verbose_name=b'Positionering', choices=[(b'0%', b'0%'), (b'25%', b'25%'), (b'50%', b'50%'), (b'75%', b'75%'), (b'100%', b'100%')]), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | HELPER_SETTINGS = { 5 | 'INSTALLED_APPS': [ 6 | 'easy_thumbnails', 7 | 'filer', 8 | 'mptt', 9 | ], 10 | 'ALLOWED_HOSTS': ['localhost'], 11 | 'CMS_LANGUAGES': { 12 | 1: [{ 13 | 'code': 'en', 14 | 'name': 'English', 15 | }] 16 | }, 17 | 'LANGUAGE_CODE': 'en', 18 | 'DEFAULT_AUTO_FIELD': 'django.db.models.AutoField', 19 | 'SECRET_KEY': 'herozyz', 20 | 'GALLERY_PLUGIN_MODULE_NAME': 'UI', 21 | 'CMSPLUGIN_GALLERY_TEMPLATES': [ 22 | ('cmsplugin_gallery/gallery.html', 'gallery.html'), 23 | ] 24 | } 25 | 26 | def run(): 27 | from djangocms_helper import runner 28 | runner.cms('cmsplugin_gallery') 29 | 30 | if __name__ == '__main__': 31 | run() 32 | 33 | -------------------------------------------------------------------------------- /cmsplugin_gallery/locale/en/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2010-09-29 07:07-0500\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: cms_plugins.py:12 20 | msgid "Image gallery" 21 | msgstr "" 22 | 23 | #: models.py:10 24 | #, python-format 25 | msgid "%(count)d image(s) in gallery" 26 | msgstr "" 27 | -------------------------------------------------------------------------------- /cmsplugin_gallery/migrations/0003_auto_20170104_1122.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | import cmsplugin_gallery.models 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [ 8 | ('cmsplugin_gallery', '0002_auto_20161223_1005'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name='galleryplugin', 14 | name='overlay_image', 15 | field=models.ImageField(upload_to=cmsplugin_gallery.models.UploadPath(b'GalleryPlugin'), null=True, verbose_name='Overlay Image', blank=True), 16 | ), 17 | migrations.AddField( 18 | model_name='galleryplugin', 19 | name='overlay_position', 20 | field=models.IntegerField(default=3, choices=[(1, b'Top Left'), (2, b'Top right'), (3, b'Bottom Left'), (4, b'Bottom Right')]), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /cmsplugin_gallery/migrations/0002_auto_20161223_1005.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | 3 | 4 | class Migration(migrations.Migration): 5 | 6 | dependencies = [ 7 | ('cmsplugin_gallery', '0001_initial'), 8 | ] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name='image', 13 | name='crop', 14 | field=models.CharField(default=b'0%', max_length=10, choices=[(b'0%', b'0%'), (b'25%', b'25%'), (b'50%', b'50%'), (b'75%', b'75%'), (b'100%', b'100%')]), 15 | ), 16 | migrations.AlterField( 17 | model_name='galleryplugin', 18 | name='cmsplugin_ptr', 19 | field=models.OneToOneField(on_delete=models.CASCADE, parent_link=True, related_name='cmsplugin_gallery_galleryplugin', auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /cmsplugin_gallery/locale/pl/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PACKAGE VERSION\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2010-09-29 07:07-0500\n" 11 | "PO-Revision-Date: 2010-09-29 14:09+0100\n" 12 | "Last-Translator: Piotr Kilczuk \n" 13 | "Language-Team: LANGUAGE \n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" 18 | 19 | #: cms_plugins.py:12 20 | msgid "Image gallery" 21 | msgstr "Galeria zdjęć" 22 | 23 | #: models.py:10 24 | #, python-format 25 | msgid "%(count)d image(s) in gallery" 26 | msgstr "%(count)d obrazków w galerii" 27 | 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | unit-tests: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | python-version: [ 3.7, 3.8, 3.9, '3.10'] 12 | requirements-file: [ 13 | requirements.txt, 14 | ] 15 | os: [ 16 | ubuntu-20.04, 17 | ] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v3 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | cache: 'pip' 26 | - name: dev prerequisites 27 | run: sudo apt-get install python-dev libpq-dev libmagic1 gcc libxml2-dev libxslt1-dev libjpeg62 libopenjp2-7 -y 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install -r tests/${{ matrix.requirements-file }} 32 | python setup.py install 33 | 34 | - name: Run Tests 35 | run: python setup.py test -------------------------------------------------------------------------------- /tests/tests_models.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding: utf-8 -*- 3 | from django.test import TestCase 4 | from cmsplugin_gallery.models import GalleryPlugin, Image as GalleryImage 5 | from django.core.files import File 6 | import filer.fields.image 7 | 8 | class GalleryTestCase(TestCase): 9 | 10 | def setUp(self): 11 | from filer.models import Image 12 | self.gallery = GalleryPlugin.objects.create( 13 | template='cmsplugin_gallery/gallery.html') 14 | img_file = File(open(u'tests/logo.png', 'rb')) 15 | image_instance = Image.objects.get_or_create( 16 | file=img_file, 17 | defaults={ 18 | 'name': img_file.name 19 | } 20 | )[0] 21 | self.image = GalleryImage.objects.create(image_src=image_instance, gallery=self.gallery) 22 | 23 | def test_gallery_instance(self): 24 | image_instance = GalleryImage.objects.get(id=self.image.id) 25 | self.assertEqual(image_instance.gallery.id, self.gallery.id) 26 | -------------------------------------------------------------------------------- /cmsplugin_gallery/migrations/0006_auto_20180411_1046.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 1.11.12 on 2018-04-11 05:16 2 | 3 | from django.db import migrations 4 | import filer.fields.image 5 | 6 | 7 | def migrate_to_filer(apps, schema_editor): 8 | from filer.models import Image 9 | 10 | GalleryImage = apps.get_model('cmsplugin_gallery', 'Image') 11 | images = GalleryImage.objects.all() 12 | 13 | try: 14 | for image in images: 15 | if image.src: 16 | image_src = Image.objects.get_or_create( 17 | file=image.src.file, 18 | defaults={ 19 | 'name': image.src.name 20 | } 21 | )[0] 22 | 23 | images.filter(pk=image.pk).update(image_src=image_src) 24 | except Exception as e: 25 | print(e) 26 | 27 | class Migration(migrations.Migration): 28 | 29 | dependencies = [ 30 | ('cmsplugin_gallery', '0005_auto_20180411_1046'), 31 | ] 32 | 33 | operations = [ 34 | migrations.RunPython(migrate_to_filer) 35 | ] 36 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 2.0.3 / 2022-04-20 3 | ================== 4 | 5 | * fix: issue with the history.md file not included in MANIFEST.in file 6 | 7 | 2.0.0 / 2022-04-20 8 | ================== 9 | 10 | * feat: add make file for release automation and generate python3 wheels only 11 | * correct readme 12 | * fix: correct metadata 13 | * feat:add more metadata to the setup.py and add a setup.cfg too 14 | * feat: update ci setup 15 | * feat: set default auto field 16 | * feat: get package building for python 3.10 too 17 | * feat: python3+ only 18 | * fix: upgrade deps and also add more settins 19 | * feat: try updated package for djangocms-helper 20 | * fix: upgrade to a newer version of djangocms-helper 21 | * feat: add github actions 22 | * feat: make python code python3.7+ 23 | * fix: make code django 3.2+ compatible 24 | * Merge pull request #50 from centralniak/fix/failing-migration-tofiler 25 | * wrap migration in try except 26 | * bump version 27 | * fix yet another typo 28 | * make it backward compatible 29 | * add module name in settings name 30 | * bump version 31 | * fix typo 32 | * add configurable module name 33 | -------------------------------------------------------------------------------- /cmsplugin_gallery/migrations/0005_auto_20180411_1046.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 1.11.12 on 2018-04-11 05:16 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | import filer.fields.image 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.FILER_IMAGE_MODEL), 13 | ('cmsplugin_gallery', '0004_auto_20170118_0753'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='image', 19 | name='image_src', 20 | field=filer.fields.image.FilerImageField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.FILER_IMAGE_MODEL, verbose_name='Image File'), 21 | ), 22 | migrations.AlterField( 23 | model_name='galleryplugin', 24 | name='template', 25 | field=models.CharField(choices=[(b'cmsplugin_gallery/gallery-fancy.html', b'gallery-fancy.html'), (b'cmsplugin_gallery/gallery-slider.html', b'gallery-slider.html'), (b'cmsplugin_gallery/gallery.html', b'gallery.html')], default=b'cmsplugin_gallery/gallery-fancy.html', max_length=255), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /cmsplugin_gallery/cms_plugins.py: -------------------------------------------------------------------------------- 1 | from cms.plugin_base import CMSPluginBase 2 | from cms.plugin_pool import plugin_pool 3 | from django.utils.translation import gettext_lazy as _ 4 | from django.conf import settings 5 | 6 | from . import admin 7 | from . import models 8 | 9 | if hasattr(settings, 'GALLERY_PLUGIN_MODULE_NAME'): 10 | MODULE_NAME = settings.GALLERY_PLUGIN_MODULE_NAME 11 | else: 12 | MODULE_NAME = 'UI' 13 | 14 | class CMSGalleryPlugin(CMSPluginBase): 15 | 16 | model = models.GalleryPlugin 17 | inlines = [admin.ImageInline, ] 18 | name = _('Image gallery Plugin') 19 | module = MODULE_NAME 20 | render_template = 'cmsplugin_gallery/gallery.html' 21 | 22 | def render(self, context, instance, placeholder): 23 | images = instance.image_set.all() 24 | grouped_images = [images[i:i + 4] for i in range(0, len(images.values_list('image_src')), 4)] 25 | context.update({ 26 | 'images': instance.image_set.all(), 27 | 'grouped_images': grouped_images, 28 | 'gallery': instance, 29 | }) 30 | self.render_template = instance.template 31 | return context 32 | 33 | 34 | plugin_pool.register_plugin(CMSGalleryPlugin) 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2012, Piotr Kilczuk 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 5 | following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the 8 | following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the 10 | following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 13 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 14 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 15 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 16 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 17 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 18 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /cmsplugin_gallery/locale/ru/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2012-06-16 16:07+0700\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%" 20 | "10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" 21 | 22 | #: cms_plugins.py:13 23 | msgid "Image gallery" 24 | msgstr "Галерея изображений" 25 | 26 | #: forms.py:13 27 | msgid "Change:" 28 | msgstr "Изменить:" 29 | 30 | #: models.py:26 31 | #, python-format 32 | msgid "%(count)d image(s) in gallery" 33 | msgstr "%(count)d изображений в галерее" 34 | 35 | #: models.py:35 36 | msgid "Gallery" 37 | msgstr "Галерея" 38 | 39 | #: models.py:36 40 | msgid "Image file" 41 | msgstr "Файл изображения" 42 | 43 | #: models.py:39 models.py:40 44 | msgid "Image height" 45 | msgstr "Высота изображения" 46 | 47 | #: models.py:41 48 | msgid "Title" 49 | msgstr "Заголовок" 50 | 51 | #: models.py:42 52 | msgid "Alt text" 53 | msgstr "Альтеративный текст" 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | try: 8 | from urllib import pathname2url 9 | except: 10 | from urllib.request import pathname2url 11 | 12 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 13 | endef 14 | export BROWSER_PYSCRIPT 15 | 16 | define PRINT_HELP_PYSCRIPT 17 | import re, sys 18 | 19 | for line in sys.stdin: 20 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 21 | if match: 22 | target, help = match.groups() 23 | print("%-20s %s" % (target, help)) 24 | endef 25 | export PRINT_HELP_PYSCRIPT 26 | 27 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 28 | 29 | help: 30 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 31 | 32 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 33 | 34 | clean-build: ## remove build artifacts 35 | rm -fr build/ 36 | rm -fr dist/ 37 | rm -fr .eggs/ 38 | find . -name '*.egg-info' -exec rm -fr {} + 39 | find . -name '*.egg' -exec rm -rf {} + 40 | 41 | clean-pyc: ## remove Python file artifacts 42 | find . -name '*.pyc' -exec rm -f {} + 43 | find . -name '*.pyo' -exec rm -f {} + 44 | find . -name '*~' -exec rm -rf {} + 45 | find . -name '__pycache__' -exec rm -fr {} + 46 | 47 | clean-test: ## remove test and coverage artifacts 48 | rm -fr .tox/ 49 | rm -f .coverage 50 | rm -fr htmlcov/ 51 | rm -fr .pytest_cache 52 | 53 | lint: ## check style with flake8 54 | flake8 cmsplugin_gallery tests 55 | 56 | test: ## run tests quickly with the default Python 57 | python setup.py test 58 | 59 | test-all: ## run tests on every Python version with tox 60 | tox 61 | 62 | 63 | release-to-pypi: dist ## package and upload a release 64 | twine upload dist/* 65 | 66 | dist: clean ## builds source and wheel package 67 | python setup.py sdist 68 | python setup.py bdist_wheel 69 | ls -l dist 70 | 71 | install: clean ## install the package to the active Python's site-packages 72 | python setup.py install -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import find_packages, setup 4 | 5 | 6 | CLASSIFIERS = [ 7 | 'Development Status :: 5 - Production/Stable', 8 | 'Environment :: Web Environment', 9 | 'Intended Audience :: Developers', 10 | 'License :: OSI Approved :: BSD License', 11 | 'Operating System :: OS Independent', 12 | 'Programming Language :: Python', 13 | 'Programming Language :: Python :: 3', 14 | 'Programming Language :: Python :: 3.7', 15 | 'Programming Language :: Python :: 3.8', 16 | 'Programming Language :: Python :: 3.9', 17 | 'Programming Language :: Python :: 3.10', 18 | 'Framework :: Django', 19 | 'Framework :: Django :: 2.2', 20 | 'Framework :: Django :: 3.1', 21 | 'Framework :: Django :: 3.2', 22 | 'Framework :: Django CMS', 23 | 'Framework :: Django CMS :: 3.9', 24 | 'Framework :: Django CMS :: 3.10', 25 | 'Topic :: Internet :: WWW/HTTP', 26 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 27 | 'Topic :: Software Development', 28 | 'Topic :: Software Development :: Libraries', 29 | 'Topic :: Software Development :: Libraries :: Application Frameworks', 30 | ] 31 | 32 | with open("README.md") as readme_file: 33 | readme = readme_file.read() 34 | 35 | with open("History.md") as history_file: 36 | history = history_file.read() 37 | 38 | from cmsplugin_gallery import __version__ 39 | 40 | setup( 41 | name='cmsplugin_gallery', 42 | version=__version__, 43 | author='Piotr Kilczuk', 44 | author_email='piotr@tymaszweb.pl', 45 | url='https://github.com/piotrkilczuk/cmsplugin_gallery', 46 | description = 'DjangoCMS image gallery plugin with drag&drop ' 47 | 'reordering in admin, support for thumbnails and ' 48 | 'jQueryTOOLS overlay.', 49 | long_description=readme + "\n\n" + history, 50 | long_description_content_type="text/markdown", 51 | packages=find_packages(), 52 | provides=['cmsplugin_gallery', ], 53 | include_package_data=True, 54 | install_requires = ['django-cms>=3.9.0', 'django-inline-ordering==1.0.2', 55 | 'easy-thumbnails', 'django-filer',], 56 | test_suite='tests.settings.run', 57 | ) 58 | -------------------------------------------------------------------------------- /cmsplugin_gallery/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | from django.db import models, migrations 2 | import cmsplugin_gallery.models 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [ 8 | ('cms', '0014_auto_20160404_1908'), 9 | ] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name='GalleryPlugin', 14 | fields=[ 15 | ('cmsplugin_ptr', models.OneToOneField(on_delete=models.CASCADE, parent_link=True, auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin')), 16 | ('template', models.CharField(default=b'cmsplugin_gallery/gallery-fancy.html', max_length=255, choices=[(b'cmsplugin_gallery/gallery-fancy.html', b'gallery-fancy.html'), (b'cmsplugin_gallery/gallery.html', b'gallery.html')])), 17 | ], 18 | options={ 19 | 'abstract': False, 20 | }, 21 | bases=('cms.cmsplugin',), 22 | ), 23 | migrations.CreateModel( 24 | name='Image', 25 | fields=[ 26 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 27 | ('inline_ordering_position', models.IntegerField(null=True, blank=True)), 28 | ('src', models.ImageField(height_field=b'src_height', upload_to=cmsplugin_gallery.models.UploadPath(b'GalleryPlugin'), width_field=b'src_width', verbose_name='Image file')), 29 | ('src_height', models.PositiveSmallIntegerField(verbose_name='Image height', null=True, editable=False)), 30 | ('src_width', models.PositiveSmallIntegerField(verbose_name='Image height', null=True, editable=False)), 31 | ('title', models.CharField(max_length=255, verbose_name='Title', blank=True)), 32 | ('alt', models.TextField(verbose_name='Alt text', blank=True)), 33 | ('gallery', models.ForeignKey(on_delete=models.CASCADE, verbose_name='Gallery', to='cmsplugin_gallery.GalleryPlugin')), 34 | ], 35 | options={ 36 | 'ordering': ('inline_ordering_position',), 37 | 'abstract': False, 38 | }, 39 | bases=(models.Model,), 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /cmsplugin_gallery/utils.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import threading 4 | 5 | from django.conf import settings 6 | 7 | localdata = threading.local() 8 | localdata.TEMPLATES = tuple() 9 | TEMPLATES = localdata.TEMPLATES 10 | 11 | 12 | def autodiscover_templates(): 13 | ''' 14 | Autodiscovers cmsplugin_gallery templates the way 15 | 'django.template.loaders.filesystem.Loader' and 16 | 'django.template.loaders.app_directories.Loader' work. 17 | ''' 18 | def sorted_templates(templates): 19 | ''' 20 | Sorts templates 21 | ''' 22 | TEMPLATES = sorted(templates, key=lambda template: template[1]) 23 | return TEMPLATES 24 | 25 | # obviously, cache for better performance 26 | global TEMPLATES 27 | if TEMPLATES: 28 | return TEMPLATES 29 | 30 | #override templates from settings 31 | override_dir = getattr(settings, 'CMSPLUGIN_GALLERY_TEMPLATES', None) 32 | if override_dir: 33 | return sorted_templates(override_dir) 34 | 35 | templates = [] 36 | # templates = [ 37 | # ('cmsplugin_gallery/gallery.html', 'gallery.html'), 38 | # ] 39 | 40 | dirs_to_scan = [] 41 | if 'django.template.loaders.app_directories.Loader' in settings.TEMPLATE_LOADERS: 42 | for app in settings.INSTALLED_APPS: 43 | _ = __import__(app) 44 | dir = os.path.dirname(_.__file__) 45 | if not dir in dirs_to_scan: 46 | #append 'templates' for app directories 47 | dirs_to_scan.append(os.path.join(dir, 'templates')) 48 | 49 | if 'django.template.loaders.filesystem.Loader' in settings.TEMPLATE_LOADERS: 50 | for dir in settings.TEMPLATE_DIRS: 51 | if not dir in dirs_to_scan: 52 | #filesystem loader assumes our templates in 'templates' already 53 | dirs_to_scan.append(dir) 54 | 55 | for dir in dirs_to_scan: 56 | found = glob.glob(os.path.join(dir, 'cmsplugin_gallery/*.html')) 57 | for file in found: 58 | dir, file = os.path.split(file) 59 | key, value = os.path.join(dir.split('/')[-1], file), file 60 | f = False 61 | for _, template in templates: 62 | if template == file: 63 | f = True 64 | if not f: 65 | templates.append((key, value,)) 66 | #print os.path.basename(file) 67 | 68 | return sorted_templates(templates) 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cmsplugin Gallery 2 | 3 | [![Run Tests](https://github.com/piotrkilczuk/cmsplugin_gallery/actions/workflows/test.yml/badge.svg)](https://github.com/piotrkilczuk/cmsplugin_gallery/actions/workflows/test.yml) 4 | 5 | `cmsplugin_gallery` is the most versatile gallery plugin for djangoCMS. It supports 6 | Python3.7 and above. 7 | 8 | Features: 9 | 10 | - Latest version of the plugin supports filer. 11 | - Drag & Drop reordering of photos in the plugin admin 12 | - Unlimited, auto-discovered custom templates - you can change template 13 | of given gallery at anytime, use javascript galleries etc. 14 | 15 | 16 | ## Requirements 17 | 18 | Supports Django version 3.2+ and latest Django CMS version. 19 | Follow individual installation instructions before installing **cmsplugin_gallery**. 20 | Please note that cmsplugin_gallery requires: 21 | 22 | - django-inline-ordering http://pypi.python.org/pypi/django-inline-ordering/ 23 | - easy-thumbnails http://pypi.python.org/pypi/easy-thumbnails/ 24 | - django-filer https://pypi.python.org/pypi/django-filer 25 | 26 | **IMPORTANT** 27 | 28 | If you are using version later than 1.1.4, Please update all your templates to use 29 | `image_src` instead of `src`. `image_src` is the new FilerImageField instead of the old 30 | `src` which was the ImageField. Check this [diff](https://github.com/centralniak/cmsplugin_gallery/commit/364378842885ba2e2e0f2730076658b3c039534c) for the change in sample template. 31 | 32 | ## Installation 33 | 34 | - `pip install cmsplugin_gallery` 35 | - Add `'cmsplugin_gallery'` to `INSTALLED_APPS` (if necessary) 36 | - Run Migrations 37 | 38 | ## Usage 39 | 40 | The easiest approach is to use a nice feature of cmsplugin_gallery - 41 | the template autodiscovery. In order to take advantage of it, add your custom 42 | templates in the cmsplugin_gallery subdirectory of any of template dirs scanned 43 | by Django. 44 | 45 | If you don't want to use the autodiscovery, you can hardcode available templates 46 | in settings.py using following setting: 47 | 48 | ```python 49 | CMSPLUGIN_GALLERY_TEMPLATES = ( 50 | ('app/template.html', 'Template #1', ), 51 | ('app/other_template.html', 'Template #2', ), 52 | ) 53 | ``` 54 | Embed as a typical plugin. 55 | 56 | ## Bugs & Contribution 57 | 58 | Please use GitHub to report bugs, feature requests and submit your code. 59 | 60 | [Report New Issue](https://github.com/piotrkilczuk/cmsplugin_gallery/issues/new/choose) 61 | 62 | 63 | ## Contributors 64 | 65 | [Contributors](https://github.com/piotrkilczuk/cmsplugin_gallery/graphs/contributors) 66 | -------------------------------------------------------------------------------- /cmsplugin_gallery/models.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | import os 3 | import threading 4 | 5 | from cms.models import CMSPlugin 6 | 7 | try: 8 | from cms.utils import get_cms_setting 9 | except ImportError: 10 | from cms.utils.conf import get_cms_setting 11 | 12 | from django.db import models 13 | from django.utils.translation import gettext_lazy as _ 14 | from inline_ordering.models import Orderable 15 | from django.utils.deconstruct import deconstructible 16 | from django.db import connection 17 | from filer.fields.image import FilerImageField 18 | from . import utils 19 | 20 | localdata = threading.local() 21 | localdata.TEMPLATE_CHOICES = utils.autodiscover_templates() 22 | TEMPLATE_CHOICES = localdata.TEMPLATE_CHOICES 23 | 24 | 25 | @deconstructible 26 | class UploadPath: 27 | 28 | def __init__(self, sub_path): 29 | self.path = sub_path 30 | 31 | def __call__(self, instance, filename): 32 | return "gallery/%s" % (filename) 33 | 34 | get_upload_path = UploadPath('GalleryPlugin') 35 | 36 | class GalleryPlugin(CMSPlugin): 37 | 38 | TOP_LEFT = 1 39 | TOP_RIGHT = 2 40 | BOTTOM_LEFT = 3 41 | BOTTOM_RIGHT = 4 42 | OVERLAY_POSITION_CHOICES = ( 43 | (TOP_LEFT, 'Top Left'), 44 | (TOP_RIGHT, 'Top right'), 45 | (BOTTOM_LEFT, 'Bottom Left'), 46 | (BOTTOM_RIGHT, 'Bottom Right'), 47 | ) 48 | 49 | def copy_relations(self, oldinstance): 50 | for img in oldinstance.image_set.all(): 51 | new_img = Image() 52 | new_img.gallery=self 53 | new_img.image_src = img.image_src 54 | new_img.src_height = img.src_height 55 | new_img.src_width = img.src_width 56 | new_img.title = img.title 57 | new_img.alt = img.alt 58 | new_img.crop = img.crop 59 | new_img.save() 60 | 61 | overlay_image = models.ImageField(_("Overlay Image"), upload_to=get_upload_path, null=True, blank=True) 62 | overlay_position = models.IntegerField(default=BOTTOM_LEFT, 63 | choices=OVERLAY_POSITION_CHOICES) 64 | template = models.CharField(max_length=255, 65 | choices=TEMPLATE_CHOICES, 66 | default=TEMPLATE_CHOICES[0][0], 67 | editable=len(TEMPLATE_CHOICES) > 1) 68 | 69 | def __unicode__(self): 70 | return _('%(count)d image(s) in gallery') % {'count': self.image_set.count()} 71 | 72 | 73 | class Image(Orderable): 74 | ZERO_PERCENT = "0%" 75 | TWENTY_FIVE_PERCENT = "25%" 76 | FIFTY_PERCENT = "50%" 77 | SEVENTY_FIVE_PERCENT = "75%" 78 | HUNDRED_PERCENT = "100%" 79 | CROP_CHOICES = ( 80 | (ZERO_PERCENT, "0%"), 81 | (TWENTY_FIVE_PERCENT, "25%"), 82 | (FIFTY_PERCENT, "50%"), 83 | (SEVENTY_FIVE_PERCENT, "75%"), 84 | (HUNDRED_PERCENT, "100%"), 85 | ) 86 | 87 | def get_media_path(self, filename): 88 | pages = self.gallery.placeholder.page_set.all() 89 | if pages.count(): 90 | return pages[0].get_media_path(filename) 91 | else: 92 | today = date.today() 93 | return os.path.join(get_cms_setting('PAGE_MEDIA_PATH'), 94 | str(today.year), str(today.month), str(today.day), filename) 95 | 96 | gallery = models.ForeignKey(GalleryPlugin, on_delete=models.CASCADE, verbose_name=_("Gallery")) 97 | image_src = FilerImageField( 98 | verbose_name=_('Image File'), 99 | blank=True, 100 | null=True, 101 | on_delete=models.SET_NULL, 102 | related_name='+', 103 | ) 104 | src_height = models.PositiveSmallIntegerField(_("Image height"), editable=False, null=True) 105 | src_width = models.PositiveSmallIntegerField(_("Image height"), editable=False, null=True) 106 | title = models.CharField(_("Title"), max_length=255, blank=True) 107 | alt = models.CharField(_("Alt text"), blank=True, max_length=255) 108 | crop = models.CharField(default=ZERO_PERCENT, choices=CROP_CHOICES, max_length=10, verbose_name="Positionering") 109 | 110 | def __unicode__(self): 111 | return self.title or self.alt or str(self.pk) 112 | --------------------------------------------------------------------------------