django-image-gallery 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_image_gallery-0.1.0/LICENSE +21 -0
- django_image_gallery-0.1.0/PKG-INFO +137 -0
- django_image_gallery-0.1.0/README.md +111 -0
- django_image_gallery-0.1.0/THIRD_PARTY_NOTICES.md +28 -0
- django_image_gallery-0.1.0/django_image_gallery/__init__.py +0 -0
- django_image_gallery-0.1.0/django_image_gallery/admin.py +13 -0
- django_image_gallery-0.1.0/django_image_gallery/apps.py +6 -0
- django_image_gallery-0.1.0/django_image_gallery/migrations/0001_initial.py +29 -0
- django_image_gallery-0.1.0/django_image_gallery/migrations/__init__.py +0 -0
- django_image_gallery-0.1.0/django_image_gallery/models.py +29 -0
- django_image_gallery-0.1.0/django_image_gallery/static/django_image_gallery/css/gallery.css +173 -0
- django_image_gallery-0.1.0/django_image_gallery/static/django_image_gallery/css/masonry.css +14 -0
- django_image_gallery-0.1.0/django_image_gallery/static/django_image_gallery/js/gallery.js +184 -0
- django_image_gallery-0.1.0/django_image_gallery/static/django_image_gallery/js/imagesloaded.pkgd.min.js +12 -0
- django_image_gallery-0.1.0/django_image_gallery/static/django_image_gallery/js/masonry.pkgd.min.js +9 -0
- django_image_gallery-0.1.0/django_image_gallery/templates/django_image_gallery/gallery.html +64 -0
- django_image_gallery-0.1.0/django_image_gallery/templatetags/__init__.py +0 -0
- django_image_gallery-0.1.0/django_image_gallery/templatetags/django_image_gallery.py +11 -0
- django_image_gallery-0.1.0/django_image_gallery/tests.py +106 -0
- django_image_gallery-0.1.0/django_image_gallery/views.py +3 -0
- django_image_gallery-0.1.0/django_image_gallery.egg-info/PKG-INFO +137 -0
- django_image_gallery-0.1.0/django_image_gallery.egg-info/SOURCES.txt +25 -0
- django_image_gallery-0.1.0/django_image_gallery.egg-info/dependency_links.txt +1 -0
- django_image_gallery-0.1.0/django_image_gallery.egg-info/requires.txt +2 -0
- django_image_gallery-0.1.0/django_image_gallery.egg-info/top_level.txt +1 -0
- django_image_gallery-0.1.0/pyproject.toml +50 -0
- django_image_gallery-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Towe
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-image-gallery
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Django image gallery application.
|
|
5
|
+
Author: Alexander Towe
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/alextowe/django-image-gallery
|
|
8
|
+
Project-URL: Repository, https://github.com/alextowe/django-image-gallery
|
|
9
|
+
Project-URL: Issues, https://github.com/alextowe/django-image-gallery/issues
|
|
10
|
+
Classifier: Framework :: Django
|
|
11
|
+
Classifier: Framework :: Django :: 5.2
|
|
12
|
+
Classifier: Framework :: Django :: 6.0
|
|
13
|
+
Classifier: Framework :: Django :: 6.1
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
License-File: THIRD_PARTY_NOTICES.md
|
|
23
|
+
Requires-Dist: Django<7.0,>=5.2
|
|
24
|
+
Requires-Dist: Pillow>=10.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Django Image Gallery
|
|
28
|
+
|
|
29
|
+
An image gallery application for Django projects. Provides an easy way to manage, organize, and display images through the Django admin interface and a reusable template tag.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Django model for managing gallery images
|
|
34
|
+
- Automatic resizing of uploaded images exceeding 2000 × 2000 pixels
|
|
35
|
+
- Published and unpublished image management
|
|
36
|
+
- Configurable image ordering
|
|
37
|
+
- Masonry image layout
|
|
38
|
+
- Load-more functionality
|
|
39
|
+
- Full-screen image viewer
|
|
40
|
+
- Django admin integration
|
|
41
|
+
- Reusable template tag
|
|
42
|
+
|
|
43
|
+
## Requirements
|
|
44
|
+
|
|
45
|
+
- Python 3.10+
|
|
46
|
+
- Django 5.2, 6.0, or 6.1
|
|
47
|
+
- Pillow 10.0 or later
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
Install the package using pip:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install django-image-gallery
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
Add `django_image_gallery` to your project's `INSTALLED_APPS`:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
INSTALLED_APPS = [
|
|
63
|
+
# ...
|
|
64
|
+
"django_image_gallery",
|
|
65
|
+
]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Run the database migrations:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python manage.py migrate
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
Load the template tag library and include the gallery wherever you want it displayed:
|
|
77
|
+
|
|
78
|
+
```django
|
|
79
|
+
{% load django_image_gallery %}
|
|
80
|
+
|
|
81
|
+
{% gallery %}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The template tag retrieves all published gallery images and renders them using the package's included gallery template.
|
|
85
|
+
|
|
86
|
+
## Media Configuration
|
|
87
|
+
|
|
88
|
+
Configure Django to handle uploaded media files:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
MEDIA_URL = "/media/"
|
|
92
|
+
MEDIA_ROOT = BASE_DIR / "media"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
During local development, you can serve media files through Django by adding the following to your project's root `urls.py`:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from django.conf import settings
|
|
99
|
+
from django.conf.urls.static import static
|
|
100
|
+
|
|
101
|
+
urlpatterns = [
|
|
102
|
+
# Your URL patterns
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
if settings.DEBUG:
|
|
106
|
+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
For production deployments, configure your web server or storage backend to serve uploaded media files. Uploaded images must be accessible through the configured media storage.
|
|
110
|
+
|
|
111
|
+
## Managing Images
|
|
112
|
+
|
|
113
|
+
Images can be added and managed through the Django admin interface.
|
|
114
|
+
|
|
115
|
+
After creating a superuser:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
python manage.py createsuperuser
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Log in to the admin interface and locate **Gallery Images**.
|
|
122
|
+
|
|
123
|
+
Each image supports the following fields:
|
|
124
|
+
|
|
125
|
+
| Field | Description |
|
|
126
|
+
|---|---|
|
|
127
|
+
| Title | Optional image title |
|
|
128
|
+
| Image | Uploaded image file |
|
|
129
|
+
| Caption | Optional image caption |
|
|
130
|
+
| Sort order | Controls the display order of images |
|
|
131
|
+
| Published | Determines whether the image appears in the gallery |
|
|
132
|
+
|
|
133
|
+
Only published images appear in the gallery. Images are ordered by `sort_order`, with newer entries taking precedence when sort orders match.
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
This project is licensed under the MIT License. See the `LICENSE` file for details.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Django Image Gallery
|
|
2
|
+
|
|
3
|
+
An image gallery application for Django projects. Provides an easy way to manage, organize, and display images through the Django admin interface and a reusable template tag.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Django model for managing gallery images
|
|
8
|
+
- Automatic resizing of uploaded images exceeding 2000 × 2000 pixels
|
|
9
|
+
- Published and unpublished image management
|
|
10
|
+
- Configurable image ordering
|
|
11
|
+
- Masonry image layout
|
|
12
|
+
- Load-more functionality
|
|
13
|
+
- Full-screen image viewer
|
|
14
|
+
- Django admin integration
|
|
15
|
+
- Reusable template tag
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- Python 3.10+
|
|
20
|
+
- Django 5.2, 6.0, or 6.1
|
|
21
|
+
- Pillow 10.0 or later
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Install the package using pip:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install django-image-gallery
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
Add `django_image_gallery` to your project's `INSTALLED_APPS`:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
INSTALLED_APPS = [
|
|
37
|
+
# ...
|
|
38
|
+
"django_image_gallery",
|
|
39
|
+
]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Run the database migrations:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python manage.py migrate
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
Load the template tag library and include the gallery wherever you want it displayed:
|
|
51
|
+
|
|
52
|
+
```django
|
|
53
|
+
{% load django_image_gallery %}
|
|
54
|
+
|
|
55
|
+
{% gallery %}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The template tag retrieves all published gallery images and renders them using the package's included gallery template.
|
|
59
|
+
|
|
60
|
+
## Media Configuration
|
|
61
|
+
|
|
62
|
+
Configure Django to handle uploaded media files:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
MEDIA_URL = "/media/"
|
|
66
|
+
MEDIA_ROOT = BASE_DIR / "media"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
During local development, you can serve media files through Django by adding the following to your project's root `urls.py`:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from django.conf import settings
|
|
73
|
+
from django.conf.urls.static import static
|
|
74
|
+
|
|
75
|
+
urlpatterns = [
|
|
76
|
+
# Your URL patterns
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
if settings.DEBUG:
|
|
80
|
+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For production deployments, configure your web server or storage backend to serve uploaded media files. Uploaded images must be accessible through the configured media storage.
|
|
84
|
+
|
|
85
|
+
## Managing Images
|
|
86
|
+
|
|
87
|
+
Images can be added and managed through the Django admin interface.
|
|
88
|
+
|
|
89
|
+
After creating a superuser:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
python manage.py createsuperuser
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Log in to the admin interface and locate **Gallery Images**.
|
|
96
|
+
|
|
97
|
+
Each image supports the following fields:
|
|
98
|
+
|
|
99
|
+
| Field | Description |
|
|
100
|
+
|---|---|
|
|
101
|
+
| Title | Optional image title |
|
|
102
|
+
| Image | Uploaded image file |
|
|
103
|
+
| Caption | Optional image caption |
|
|
104
|
+
| Sort order | Controls the display order of images |
|
|
105
|
+
| Published | Determines whether the image appears in the gallery |
|
|
106
|
+
|
|
107
|
+
Only published images appear in the gallery. Images are ordered by `sort_order`, with newer entries taking precedence when sort orders match.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
This project is licensed under the MIT License. See the `LICENSE` file for details.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
This package includes the following third-party JavaScript libraries. Their original license notices are retained in the distributed JavaScript files.
|
|
4
|
+
|
|
5
|
+
## ImagesLoaded
|
|
6
|
+
|
|
7
|
+
- **Version:** 5.0.0
|
|
8
|
+
- **License:** MIT License
|
|
9
|
+
- **Project:** https://imagesloaded.desandro.com/
|
|
10
|
+
- **Source:** https://github.com/desandro/imagesloaded
|
|
11
|
+
|
|
12
|
+
The packaged distribution includes EvEmitter code. Its original MIT license notice is retained in the distribution.
|
|
13
|
+
|
|
14
|
+
## Masonry
|
|
15
|
+
|
|
16
|
+
- **Version:** 4.2.2
|
|
17
|
+
- **Author:** David DeSandro
|
|
18
|
+
- **License:** MIT License
|
|
19
|
+
- **Project:** https://masonry.desandro.com/
|
|
20
|
+
- **Source:** https://github.com/desandro/masonry
|
|
21
|
+
|
|
22
|
+
The packaged distribution includes supporting libraries, including EvEmitter, get-size, desandro-matches-selector, fizzy-ui-utils, Outlayer, and jquery-bridget. Their original code is distributed as part of the Masonry packaged build.
|
|
23
|
+
|
|
24
|
+
## License information
|
|
25
|
+
|
|
26
|
+
The third-party libraries listed above are distributed under the MIT License. Their respective authors and copyright holders retain ownership of their work.
|
|
27
|
+
|
|
28
|
+
The original license notices included in the bundled JavaScript files are preserved.
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Generated by Django 6.0.5 on 2026-10-03 19:33
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
initial = True
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.CreateModel(
|
|
15
|
+
name='GalleryImage',
|
|
16
|
+
fields=[
|
|
17
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
18
|
+
('title', models.CharField(blank=True, max_length=200)),
|
|
19
|
+
('image', models.ImageField(upload_to='gallery/')),
|
|
20
|
+
('caption', models.TextField(blank=True)),
|
|
21
|
+
('sort_order', models.PositiveIntegerField(default=0)),
|
|
22
|
+
('is_published', models.BooleanField(default=False)),
|
|
23
|
+
],
|
|
24
|
+
options={
|
|
25
|
+
'verbose_name': 'Gallery Image',
|
|
26
|
+
'ordering': ['sort_order', '-id'],
|
|
27
|
+
},
|
|
28
|
+
),
|
|
29
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from PIL import Image
|
|
2
|
+
from django.db import models
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GalleryImage(models.Model):
|
|
6
|
+
title = models.CharField(max_length=200, blank=True)
|
|
7
|
+
image = models.ImageField(upload_to="gallery/")
|
|
8
|
+
caption = models.TextField(blank=True)
|
|
9
|
+
sort_order = models.PositiveIntegerField(default=0)
|
|
10
|
+
is_published = models.BooleanField(default=False)
|
|
11
|
+
|
|
12
|
+
def save(self, *args, **kwargs):
|
|
13
|
+
super().save(*args, **kwargs)
|
|
14
|
+
|
|
15
|
+
img = Image.open(self.image.path)
|
|
16
|
+
|
|
17
|
+
max_size = (2000, 2000)
|
|
18
|
+
|
|
19
|
+
if img.width > max_size[0] or img.height > max_size[1]:
|
|
20
|
+
img.thumbnail(max_size)
|
|
21
|
+
|
|
22
|
+
img.save(self.image.path, quality=85, optimize=True)
|
|
23
|
+
|
|
24
|
+
class Meta:
|
|
25
|
+
ordering = ["sort_order", "-id"]
|
|
26
|
+
verbose_name = "Gallery Image"
|
|
27
|
+
|
|
28
|
+
def __str__(self):
|
|
29
|
+
return self.title or f"Image {self.id}"
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#image-gallery-modal {
|
|
2
|
+
position: fixed;
|
|
3
|
+
inset: 0;
|
|
4
|
+
z-index: 1000;
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
background: rgba(0, 0, 0, 0.85);
|
|
9
|
+
opacity: 0;
|
|
10
|
+
visibility: hidden;
|
|
11
|
+
pointer-events: none;
|
|
12
|
+
transition: opacity 0.5s ease, visibility 0.5s ease;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#image-gallery-modal.is-open {
|
|
16
|
+
opacity: 1;
|
|
17
|
+
visibility: visible;
|
|
18
|
+
pointer-events: auto;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.image-gallery-modal-content {
|
|
22
|
+
position: relative;
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
pointer-events: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.gallery-modal-image,
|
|
32
|
+
.gallery-close,
|
|
33
|
+
.gallery-prev,
|
|
34
|
+
.gallery-next {
|
|
35
|
+
pointer-events: auto;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.gallery-modal-image {
|
|
39
|
+
position: absolute;
|
|
40
|
+
max-width: 90vw;
|
|
41
|
+
max-height: 90vh;
|
|
42
|
+
object-fit: contain;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.gallery-modal-image[hidden] {
|
|
46
|
+
display: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.gallery-slide-in-right {
|
|
50
|
+
animation: gallery-slide-in-right 0.5s ease;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.gallery-slide-in-left {
|
|
54
|
+
animation: gallery-slide-in-left 0.5s ease;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@keyframes gallery-slide-in-right {
|
|
58
|
+
from {
|
|
59
|
+
transform: translateX(100%);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
to {
|
|
63
|
+
transform: translateX(0);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@keyframes gallery-slide-in-left {
|
|
68
|
+
from {
|
|
69
|
+
transform: translateX(-100%);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
to {
|
|
73
|
+
transform: translateX(0);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.gallery-close,
|
|
78
|
+
.gallery-prev,
|
|
79
|
+
.gallery-next {
|
|
80
|
+
position: absolute;
|
|
81
|
+
border: 0;
|
|
82
|
+
background: transparent;
|
|
83
|
+
color: white;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
z-index: 10;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.gallery-close {
|
|
89
|
+
top: 20px;
|
|
90
|
+
right: 25px;
|
|
91
|
+
font-size: 2rem;
|
|
92
|
+
line-height: 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.gallery-prev,
|
|
96
|
+
.gallery-next {
|
|
97
|
+
top: 50%;
|
|
98
|
+
transform: translateY(-50%);
|
|
99
|
+
font-size: 2.5rem;
|
|
100
|
+
padding: 1rem;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.gallery-prev {
|
|
104
|
+
left: 20px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.gallery-next {
|
|
108
|
+
right: 20px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.image-gallery-controls {
|
|
112
|
+
display: flex;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
gap: 1rem;
|
|
115
|
+
margin-top: 1.5rem;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.image-gallery-controls button {
|
|
119
|
+
display: inline-block;
|
|
120
|
+
border: 1px solid #fff;
|
|
121
|
+
border-radius: 0;
|
|
122
|
+
background: #fff;
|
|
123
|
+
color: #0f172a;
|
|
124
|
+
font-family: inherit;
|
|
125
|
+
font-size: 1rem;
|
|
126
|
+
font-weight: 500;
|
|
127
|
+
letter-spacing: 0.1em;
|
|
128
|
+
line-height: 1.5;
|
|
129
|
+
padding: 0.375rem 3rem;
|
|
130
|
+
text-align: center;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
transition: background-color 0.15s ease-in-out,
|
|
133
|
+
border-color 0.15s ease-in-out,
|
|
134
|
+
box-shadow 0.15s ease-in-out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.image-gallery-controls button:hover {
|
|
138
|
+
background: #cbd5e1;
|
|
139
|
+
border-color: #cbd5e1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.image-gallery-controls button:focus-visible {
|
|
143
|
+
outline: 0;
|
|
144
|
+
box-shadow: 0 0 0 0.25rem rgb(207 207 207 / 50%);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.image-gallery-controls button:active {
|
|
148
|
+
background: #cbd5e1;
|
|
149
|
+
border-color: #cbd5e1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.image-gallery-controls button:disabled {
|
|
153
|
+
opacity: 0.65;
|
|
154
|
+
pointer-events: none;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
body.image-gallery-open {
|
|
158
|
+
overflow: hidden;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.gallery-slide {
|
|
162
|
+
position: relative;
|
|
163
|
+
width: 100%;
|
|
164
|
+
height: 100%;
|
|
165
|
+
overflow: hidden;
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
justify-content: center;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#image-gallery-modal {
|
|
172
|
+
touch-action: none;
|
|
173
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
const gallery = document.getElementById("image-gallery");
|
|
2
|
+
const galleryGrid = gallery?.querySelector(".gallery-grid");
|
|
3
|
+
|
|
4
|
+
if (galleryGrid) {
|
|
5
|
+
imagesLoaded(galleryGrid, function () {
|
|
6
|
+
galleryGrid.masonry = new Masonry(galleryGrid, {
|
|
7
|
+
itemSelector: ".gallery-item",
|
|
8
|
+
columnWidth: ".gallery-item",
|
|
9
|
+
percentPosition: true,
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const galleryModal = document.getElementById("image-gallery-modal");
|
|
15
|
+
const modalImages = galleryModal?.querySelectorAll(".gallery-modal-image");
|
|
16
|
+
const closeButton = galleryModal?.querySelector(".gallery-close");
|
|
17
|
+
const previousButton = galleryModal?.querySelector(".gallery-prev");
|
|
18
|
+
const nextButton = galleryModal?.querySelector(".gallery-next");
|
|
19
|
+
|
|
20
|
+
let currentIndex = 0;
|
|
21
|
+
let modalHideTimeout = null;
|
|
22
|
+
|
|
23
|
+
function showGalleryImage(index, direction = 1) {
|
|
24
|
+
if (!modalImages?.length) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (index < 0) {
|
|
29
|
+
index = modalImages.length - 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (index >= modalImages.length) {
|
|
33
|
+
index = 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
modalImages.forEach(function (image) {
|
|
37
|
+
image.hidden = true;
|
|
38
|
+
image.classList.remove("gallery-slide-in-left", "gallery-slide-in-right");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const image = modalImages[index];
|
|
42
|
+
|
|
43
|
+
image.hidden = false;
|
|
44
|
+
|
|
45
|
+
image.classList.add(
|
|
46
|
+
direction === 1
|
|
47
|
+
? "gallery-slide-in-right"
|
|
48
|
+
: "gallery-slide-in-left"
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
currentIndex = index;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function openGallery(index) {
|
|
55
|
+
if (!galleryModal) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (modalHideTimeout) {
|
|
60
|
+
clearTimeout(modalHideTimeout);
|
|
61
|
+
modalHideTimeout = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
showGalleryImage(index);
|
|
65
|
+
|
|
66
|
+
galleryModal.hidden = false;
|
|
67
|
+
|
|
68
|
+
requestAnimationFrame(function () {
|
|
69
|
+
galleryModal.classList.add("is-open");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
galleryModal.setAttribute("aria-hidden", "false");
|
|
73
|
+
document.body.classList.add("image-gallery-open");
|
|
74
|
+
|
|
75
|
+
galleryModal.focus();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function closeGallery() {
|
|
79
|
+
if (!galleryModal) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
galleryModal.classList.remove("is-open");
|
|
84
|
+
galleryModal.setAttribute("aria-hidden", "true");
|
|
85
|
+
document.body.classList.remove("image-gallery-open");
|
|
86
|
+
|
|
87
|
+
modalHideTimeout = setTimeout(function () {
|
|
88
|
+
galleryModal.hidden = true;
|
|
89
|
+
modalHideTimeout = null;
|
|
90
|
+
}, 500);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
gallery?.addEventListener("click", function (event) {
|
|
94
|
+
const image = event.target.closest(".gallery-image");
|
|
95
|
+
|
|
96
|
+
if (!image) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
openGallery(parseInt(image.dataset.index, 10));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
closeButton?.addEventListener("click", closeGallery);
|
|
104
|
+
|
|
105
|
+
previousButton?.addEventListener("click", function (event) {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
showGalleryImage(currentIndex - 1, -1);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
nextButton?.addEventListener("click", function (event) {
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
showGalleryImage(currentIndex + 1, 1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
galleryModal?.addEventListener("click", function (event) {
|
|
116
|
+
if (!event.target.closest(".image-gallery-modal-content")) {
|
|
117
|
+
closeGallery();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
window.addEventListener("keydown", function (event) {
|
|
122
|
+
if (!galleryModal || galleryModal.hidden) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (event.key === "Escape") {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
closeGallery();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (event.key === "ArrowLeft") {
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
showGalleryImage(currentIndex - 1, -1);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (event.key === "ArrowRight") {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
event.stopPropagation();
|
|
142
|
+
showGalleryImage(currentIndex + 1, 1);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}, true);
|
|
146
|
+
|
|
147
|
+
const loadMoreButton = document.getElementById("image-gallery-load-more");
|
|
148
|
+
const hideButton = document.getElementById("image-gallery-hide");
|
|
149
|
+
const template = document.getElementById("image-gallery-template");
|
|
150
|
+
|
|
151
|
+
let loadedItems = [];
|
|
152
|
+
|
|
153
|
+
if (loadMoreButton) {
|
|
154
|
+
loadMoreButton.addEventListener("click", function () {
|
|
155
|
+
const fragment = template.content.cloneNode(true);
|
|
156
|
+
loadedItems = Array.from(fragment.children);
|
|
157
|
+
|
|
158
|
+
galleryGrid.appendChild(fragment);
|
|
159
|
+
|
|
160
|
+
imagesLoaded(loadedItems, function () {
|
|
161
|
+
galleryGrid.masonry.appended(loadedItems);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
loadMoreButton.hidden = true;
|
|
165
|
+
hideButton.hidden = false;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (hideButton) {
|
|
170
|
+
hideButton.addEventListener("click", function () {
|
|
171
|
+
galleryGrid.masonry.remove(loadedItems);
|
|
172
|
+
galleryGrid.masonry.layout();
|
|
173
|
+
|
|
174
|
+
loadedItems = [];
|
|
175
|
+
|
|
176
|
+
loadMoreButton.hidden = false;
|
|
177
|
+
hideButton.hidden = true;
|
|
178
|
+
|
|
179
|
+
gallery.scrollIntoView({
|
|
180
|
+
behavior: "smooth",
|
|
181
|
+
block: "start",
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* imagesLoaded PACKAGED v5.0.0
|
|
3
|
+
* JavaScript is all like "You images are done yet or what?"
|
|
4
|
+
* MIT License
|
|
5
|
+
*/
|
|
6
|
+
!function(t,e){"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,(function(){function t(){}let e=t.prototype;return e.on=function(t,e){if(!t||!e)return this;let i=this._events=this._events||{},s=i[t]=i[t]||[];return s.includes(e)||s.push(e),this},e.once=function(t,e){if(!t||!e)return this;this.on(t,e);let i=this._onceEvents=this._onceEvents||{};return(i[t]=i[t]||{})[e]=!0,this},e.off=function(t,e){let i=this._events&&this._events[t];if(!i||!i.length)return this;let s=i.indexOf(e);return-1!=s&&i.splice(s,1),this},e.emitEvent=function(t,e){let i=this._events&&this._events[t];if(!i||!i.length)return this;i=i.slice(0),e=e||[];let s=this._onceEvents&&this._onceEvents[t];for(let n of i){s&&s[n]&&(this.off(t,n),delete s[n]),n.apply(this,e)}return this},e.allOff=function(){return delete this._events,delete this._onceEvents,this},t})),
|
|
7
|
+
/*!
|
|
8
|
+
* imagesLoaded v5.0.0
|
|
9
|
+
* JavaScript is all like "You images are done yet or what?"
|
|
10
|
+
* MIT License
|
|
11
|
+
*/
|
|
12
|
+
function(t,e){"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter")):t.imagesLoaded=e(t,t.EvEmitter)}("undefined"!=typeof window?window:this,(function(t,e){let i=t.jQuery,s=t.console;function n(t,e,o){if(!(this instanceof n))return new n(t,e,o);let r=t;var h;("string"==typeof t&&(r=document.querySelectorAll(t)),r)?(this.elements=(h=r,Array.isArray(h)?h:"object"==typeof h&&"number"==typeof h.length?[...h]:[h]),this.options={},"function"==typeof e?o=e:Object.assign(this.options,e),o&&this.on("always",o),this.getImages(),i&&(this.jqDeferred=new i.Deferred),setTimeout(this.check.bind(this))):s.error(`Bad element for imagesLoaded ${r||t}`)}n.prototype=Object.create(e.prototype),n.prototype.getImages=function(){this.images=[],this.elements.forEach(this.addElementImages,this)};const o=[1,9,11];n.prototype.addElementImages=function(t){"IMG"===t.nodeName&&this.addImage(t),!0===this.options.background&&this.addElementBackgroundImages(t);let{nodeType:e}=t;if(!e||!o.includes(e))return;let i=t.querySelectorAll("img");for(let t of i)this.addImage(t);if("string"==typeof this.options.background){let e=t.querySelectorAll(this.options.background);for(let t of e)this.addElementBackgroundImages(t)}};const r=/url\((['"])?(.*?)\1\)/gi;function h(t){this.img=t}function d(t,e){this.url=t,this.element=e,this.img=new Image}return n.prototype.addElementBackgroundImages=function(t){let e=getComputedStyle(t);if(!e)return;let i=r.exec(e.backgroundImage);for(;null!==i;){let s=i&&i[2];s&&this.addBackground(s,t),i=r.exec(e.backgroundImage)}},n.prototype.addImage=function(t){let e=new h(t);this.images.push(e)},n.prototype.addBackground=function(t,e){let i=new d(t,e);this.images.push(i)},n.prototype.check=function(){if(this.progressedCount=0,this.hasAnyBroken=!1,!this.images.length)return void this.complete();let t=(t,e,i)=>{setTimeout((()=>{this.progress(t,e,i)}))};this.images.forEach((function(e){e.once("progress",t),e.check()}))},n.prototype.progress=function(t,e,i){this.progressedCount++,this.hasAnyBroken=this.hasAnyBroken||!t.isLoaded,this.emitEvent("progress",[this,t,e]),this.jqDeferred&&this.jqDeferred.notify&&this.jqDeferred.notify(this,t),this.progressedCount===this.images.length&&this.complete(),this.options.debug&&s&&s.log(`progress: ${i}`,t,e)},n.prototype.complete=function(){let t=this.hasAnyBroken?"fail":"done";if(this.isComplete=!0,this.emitEvent(t,[this]),this.emitEvent("always",[this]),this.jqDeferred){let t=this.hasAnyBroken?"reject":"resolve";this.jqDeferred[t](this)}},h.prototype=Object.create(e.prototype),h.prototype.check=function(){this.getIsImageComplete()?this.confirm(0!==this.img.naturalWidth,"naturalWidth"):(this.proxyImage=new Image,this.img.crossOrigin&&(this.proxyImage.crossOrigin=this.img.crossOrigin),this.proxyImage.addEventListener("load",this),this.proxyImage.addEventListener("error",this),this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.proxyImage.src=this.img.currentSrc||this.img.src)},h.prototype.getIsImageComplete=function(){return this.img.complete&&this.img.naturalWidth},h.prototype.confirm=function(t,e){this.isLoaded=t;let{parentNode:i}=this.img,s="PICTURE"===i.nodeName?i:this.img;this.emitEvent("progress",[this,s,e])},h.prototype.handleEvent=function(t){let e="on"+t.type;this[e]&&this[e](t)},h.prototype.onload=function(){this.confirm(!0,"onload"),this.unbindEvents()},h.prototype.onerror=function(){this.confirm(!1,"onerror"),this.unbindEvents()},h.prototype.unbindEvents=function(){this.proxyImage.removeEventListener("load",this),this.proxyImage.removeEventListener("error",this),this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype=Object.create(h.prototype),d.prototype.check=function(){this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.img.src=this.url,this.getIsImageComplete()&&(this.confirm(0!==this.img.naturalWidth,"naturalWidth"),this.unbindEvents())},d.prototype.unbindEvents=function(){this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype.confirm=function(t,e){this.isLoaded=t,this.emitEvent("progress",[this,this.element,e])},n.makeJQueryPlugin=function(e){(e=e||t.jQuery)&&(i=e,i.fn.imagesLoaded=function(t,e){return new n(this,t,e).jqDeferred.promise(i(this))})},n.makeJQueryPlugin(),n}));
|
django_image_gallery-0.1.0/django_image_gallery/static/django_image_gallery/js/masonry.pkgd.min.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Masonry PACKAGED v4.2.2
|
|
3
|
+
* Cascading grid layout library
|
|
4
|
+
* https://masonry.desandro.com
|
|
5
|
+
* MIT License
|
|
6
|
+
* by David DeSandro
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
!function(t,e){"function"==typeof define&&define.amd?define("jquery-bridget/jquery-bridget",["jquery"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("jquery")):t.jQueryBridget=e(t,t.jQuery)}(window,function(t,e){"use strict";function i(i,r,a){function h(t,e,n){var o,r="$()."+i+'("'+e+'")';return t.each(function(t,h){var u=a.data(h,i);if(!u)return void s(i+" not initialized. Cannot call methods, i.e. "+r);var d=u[e];if(!d||"_"==e.charAt(0))return void s(r+" is not a valid method");var l=d.apply(u,n);o=void 0===o?l:o}),void 0!==o?o:t}function u(t,e){t.each(function(t,n){var o=a.data(n,i);o?(o.option(e),o._init()):(o=new r(n,e),a.data(n,i,o))})}a=a||e||t.jQuery,a&&(r.prototype.option||(r.prototype.option=function(t){a.isPlainObject(t)&&(this.options=a.extend(!0,this.options,t))}),a.fn[i]=function(t){if("string"==typeof t){var e=o.call(arguments,1);return h(this,t,e)}return u(this,t),this},n(a))}function n(t){!t||t&&t.bridget||(t.bridget=i)}var o=Array.prototype.slice,r=t.console,s="undefined"==typeof r?function(){}:function(t){r.error(t)};return n(e||t.jQuery),i}),function(t,e){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",e):"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var i=this._events=this._events||{},n=i[t]=i[t]||[];return-1==n.indexOf(e)&&n.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var i=this._onceEvents=this._onceEvents||{},n=i[t]=i[t]||{};return n[e]=!0,this}},e.off=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=i.indexOf(e);return-1!=n&&i.splice(n,1),this}},e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){i=i.slice(0),e=e||[];for(var n=this._onceEvents&&this._onceEvents[t],o=0;o<i.length;o++){var r=i[o],s=n&&n[r];s&&(this.off(t,r),delete n[r]),r.apply(this,e)}return this}},e.allOff=function(){delete this._events,delete this._onceEvents},t}),function(t,e){"function"==typeof define&&define.amd?define("get-size/get-size",e):"object"==typeof module&&module.exports?module.exports=e():t.getSize=e()}(window,function(){"use strict";function t(t){var e=parseFloat(t),i=-1==t.indexOf("%")&&!isNaN(e);return i&&e}function e(){}function i(){for(var t={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},e=0;u>e;e++){var i=h[e];t[i]=0}return t}function n(t){var e=getComputedStyle(t);return e||a("Style returned "+e+". Are you running this code in a hidden iframe on Firefox? See https://bit.ly/getsizebug1"),e}function o(){if(!d){d=!0;var e=document.createElement("div");e.style.width="200px",e.style.padding="1px 2px 3px 4px",e.style.borderStyle="solid",e.style.borderWidth="1px 2px 3px 4px",e.style.boxSizing="border-box";var i=document.body||document.documentElement;i.appendChild(e);var o=n(e);s=200==Math.round(t(o.width)),r.isBoxSizeOuter=s,i.removeChild(e)}}function r(e){if(o(),"string"==typeof e&&(e=document.querySelector(e)),e&&"object"==typeof e&&e.nodeType){var r=n(e);if("none"==r.display)return i();var a={};a.width=e.offsetWidth,a.height=e.offsetHeight;for(var d=a.isBorderBox="border-box"==r.boxSizing,l=0;u>l;l++){var c=h[l],f=r[c],m=parseFloat(f);a[c]=isNaN(m)?0:m}var p=a.paddingLeft+a.paddingRight,g=a.paddingTop+a.paddingBottom,y=a.marginLeft+a.marginRight,v=a.marginTop+a.marginBottom,_=a.borderLeftWidth+a.borderRightWidth,z=a.borderTopWidth+a.borderBottomWidth,E=d&&s,b=t(r.width);b!==!1&&(a.width=b+(E?0:p+_));var x=t(r.height);return x!==!1&&(a.height=x+(E?0:g+z)),a.innerWidth=a.width-(p+_),a.innerHeight=a.height-(g+z),a.outerWidth=a.width+y,a.outerHeight=a.height+v,a}}var s,a="undefined"==typeof console?e:function(t){console.error(t)},h=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],u=h.length,d=!1;return r}),function(t,e){"use strict";"function"==typeof define&&define.amd?define("desandro-matches-selector/matches-selector",e):"object"==typeof module&&module.exports?module.exports=e():t.matchesSelector=e()}(window,function(){"use strict";var t=function(){var t=window.Element.prototype;if(t.matches)return"matches";if(t.matchesSelector)return"matchesSelector";for(var e=["webkit","moz","ms","o"],i=0;i<e.length;i++){var n=e[i],o=n+"MatchesSelector";if(t[o])return o}}();return function(e,i){return e[t](i)}}),function(t,e){"function"==typeof define&&define.amd?define("fizzy-ui-utils/utils",["desandro-matches-selector/matches-selector"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("desandro-matches-selector")):t.fizzyUIUtils=e(t,t.matchesSelector)}(window,function(t,e){var i={};i.extend=function(t,e){for(var i in e)t[i]=e[i];return t},i.modulo=function(t,e){return(t%e+e)%e};var n=Array.prototype.slice;i.makeArray=function(t){if(Array.isArray(t))return t;if(null===t||void 0===t)return[];var e="object"==typeof t&&"number"==typeof t.length;return e?n.call(t):[t]},i.removeFrom=function(t,e){var i=t.indexOf(e);-1!=i&&t.splice(i,1)},i.getParent=function(t,i){for(;t.parentNode&&t!=document.body;)if(t=t.parentNode,e(t,i))return t},i.getQueryElement=function(t){return"string"==typeof t?document.querySelector(t):t},i.handleEvent=function(t){var e="on"+t.type;this[e]&&this[e](t)},i.filterFindElements=function(t,n){t=i.makeArray(t);var o=[];return t.forEach(function(t){if(t instanceof HTMLElement){if(!n)return void o.push(t);e(t,n)&&o.push(t);for(var i=t.querySelectorAll(n),r=0;r<i.length;r++)o.push(i[r])}}),o},i.debounceMethod=function(t,e,i){i=i||100;var n=t.prototype[e],o=e+"Timeout";t.prototype[e]=function(){var t=this[o];clearTimeout(t);var e=arguments,r=this;this[o]=setTimeout(function(){n.apply(r,e),delete r[o]},i)}},i.docReady=function(t){var e=document.readyState;"complete"==e||"interactive"==e?setTimeout(t):document.addEventListener("DOMContentLoaded",t)},i.toDashed=function(t){return t.replace(/(.)([A-Z])/g,function(t,e,i){return e+"-"+i}).toLowerCase()};var o=t.console;return i.htmlInit=function(e,n){i.docReady(function(){var r=i.toDashed(n),s="data-"+r,a=document.querySelectorAll("["+s+"]"),h=document.querySelectorAll(".js-"+r),u=i.makeArray(a).concat(i.makeArray(h)),d=s+"-options",l=t.jQuery;u.forEach(function(t){var i,r=t.getAttribute(s)||t.getAttribute(d);try{i=r&&JSON.parse(r)}catch(a){return void(o&&o.error("Error parsing "+s+" on "+t.className+": "+a))}var h=new e(t,i);l&&l.data(t,n,h)})})},i}),function(t,e){"function"==typeof define&&define.amd?define("outlayer/item",["ev-emitter/ev-emitter","get-size/get-size"],e):"object"==typeof module&&module.exports?module.exports=e(require("ev-emitter"),require("get-size")):(t.Outlayer={},t.Outlayer.Item=e(t.EvEmitter,t.getSize))}(window,function(t,e){"use strict";function i(t){for(var e in t)return!1;return e=null,!0}function n(t,e){t&&(this.element=t,this.layout=e,this.position={x:0,y:0},this._create())}function o(t){return t.replace(/([A-Z])/g,function(t){return"-"+t.toLowerCase()})}var r=document.documentElement.style,s="string"==typeof r.transition?"transition":"WebkitTransition",a="string"==typeof r.transform?"transform":"WebkitTransform",h={WebkitTransition:"webkitTransitionEnd",transition:"transitionend"}[s],u={transform:a,transition:s,transitionDuration:s+"Duration",transitionProperty:s+"Property",transitionDelay:s+"Delay"},d=n.prototype=Object.create(t.prototype);d.constructor=n,d._create=function(){this._transn={ingProperties:{},clean:{},onEnd:{}},this.css({position:"absolute"})},d.handleEvent=function(t){var e="on"+t.type;this[e]&&this[e](t)},d.getSize=function(){this.size=e(this.element)},d.css=function(t){var e=this.element.style;for(var i in t){var n=u[i]||i;e[n]=t[i]}},d.getPosition=function(){var t=getComputedStyle(this.element),e=this.layout._getOption("originLeft"),i=this.layout._getOption("originTop"),n=t[e?"left":"right"],o=t[i?"top":"bottom"],r=parseFloat(n),s=parseFloat(o),a=this.layout.size;-1!=n.indexOf("%")&&(r=r/100*a.width),-1!=o.indexOf("%")&&(s=s/100*a.height),r=isNaN(r)?0:r,s=isNaN(s)?0:s,r-=e?a.paddingLeft:a.paddingRight,s-=i?a.paddingTop:a.paddingBottom,this.position.x=r,this.position.y=s},d.layoutPosition=function(){var t=this.layout.size,e={},i=this.layout._getOption("originLeft"),n=this.layout._getOption("originTop"),o=i?"paddingLeft":"paddingRight",r=i?"left":"right",s=i?"right":"left",a=this.position.x+t[o];e[r]=this.getXValue(a),e[s]="";var h=n?"paddingTop":"paddingBottom",u=n?"top":"bottom",d=n?"bottom":"top",l=this.position.y+t[h];e[u]=this.getYValue(l),e[d]="",this.css(e),this.emitEvent("layout",[this])},d.getXValue=function(t){var e=this.layout._getOption("horizontal");return this.layout.options.percentPosition&&!e?t/this.layout.size.width*100+"%":t+"px"},d.getYValue=function(t){var e=this.layout._getOption("horizontal");return this.layout.options.percentPosition&&e?t/this.layout.size.height*100+"%":t+"px"},d._transitionTo=function(t,e){this.getPosition();var i=this.position.x,n=this.position.y,o=t==this.position.x&&e==this.position.y;if(this.setPosition(t,e),o&&!this.isTransitioning)return void this.layoutPosition();var r=t-i,s=e-n,a={};a.transform=this.getTranslate(r,s),this.transition({to:a,onTransitionEnd:{transform:this.layoutPosition},isCleaning:!0})},d.getTranslate=function(t,e){var i=this.layout._getOption("originLeft"),n=this.layout._getOption("originTop");return t=i?t:-t,e=n?e:-e,"translate3d("+t+"px, "+e+"px, 0)"},d.goTo=function(t,e){this.setPosition(t,e),this.layoutPosition()},d.moveTo=d._transitionTo,d.setPosition=function(t,e){this.position.x=parseFloat(t),this.position.y=parseFloat(e)},d._nonTransition=function(t){this.css(t.to),t.isCleaning&&this._removeStyles(t.to);for(var e in t.onTransitionEnd)t.onTransitionEnd[e].call(this)},d.transition=function(t){if(!parseFloat(this.layout.options.transitionDuration))return void this._nonTransition(t);var e=this._transn;for(var i in t.onTransitionEnd)e.onEnd[i]=t.onTransitionEnd[i];for(i in t.to)e.ingProperties[i]=!0,t.isCleaning&&(e.clean[i]=!0);if(t.from){this.css(t.from);var n=this.element.offsetHeight;n=null}this.enableTransition(t.to),this.css(t.to),this.isTransitioning=!0};var l="opacity,"+o(a);d.enableTransition=function(){if(!this.isTransitioning){var t=this.layout.options.transitionDuration;t="number"==typeof t?t+"ms":t,this.css({transitionProperty:l,transitionDuration:t,transitionDelay:this.staggerDelay||0}),this.element.addEventListener(h,this,!1)}},d.onwebkitTransitionEnd=function(t){this.ontransitionend(t)},d.onotransitionend=function(t){this.ontransitionend(t)};var c={"-webkit-transform":"transform"};d.ontransitionend=function(t){if(t.target===this.element){var e=this._transn,n=c[t.propertyName]||t.propertyName;if(delete e.ingProperties[n],i(e.ingProperties)&&this.disableTransition(),n in e.clean&&(this.element.style[t.propertyName]="",delete e.clean[n]),n in e.onEnd){var o=e.onEnd[n];o.call(this),delete e.onEnd[n]}this.emitEvent("transitionEnd",[this])}},d.disableTransition=function(){this.removeTransitionStyles(),this.element.removeEventListener(h,this,!1),this.isTransitioning=!1},d._removeStyles=function(t){var e={};for(var i in t)e[i]="";this.css(e)};var f={transitionProperty:"",transitionDuration:"",transitionDelay:""};return d.removeTransitionStyles=function(){this.css(f)},d.stagger=function(t){t=isNaN(t)?0:t,this.staggerDelay=t+"ms"},d.removeElem=function(){this.element.parentNode.removeChild(this.element),this.css({display:""}),this.emitEvent("remove",[this])},d.remove=function(){return s&&parseFloat(this.layout.options.transitionDuration)?(this.once("transitionEnd",function(){this.removeElem()}),void this.hide()):void this.removeElem()},d.reveal=function(){delete this.isHidden,this.css({display:""});var t=this.layout.options,e={},i=this.getHideRevealTransitionEndProperty("visibleStyle");e[i]=this.onRevealTransitionEnd,this.transition({from:t.hiddenStyle,to:t.visibleStyle,isCleaning:!0,onTransitionEnd:e})},d.onRevealTransitionEnd=function(){this.isHidden||this.emitEvent("reveal")},d.getHideRevealTransitionEndProperty=function(t){var e=this.layout.options[t];if(e.opacity)return"opacity";for(var i in e)return i},d.hide=function(){this.isHidden=!0,this.css({display:""});var t=this.layout.options,e={},i=this.getHideRevealTransitionEndProperty("hiddenStyle");e[i]=this.onHideTransitionEnd,this.transition({from:t.visibleStyle,to:t.hiddenStyle,isCleaning:!0,onTransitionEnd:e})},d.onHideTransitionEnd=function(){this.isHidden&&(this.css({display:"none"}),this.emitEvent("hide"))},d.destroy=function(){this.css({position:"",left:"",right:"",top:"",bottom:"",transition:"",transform:""})},n}),function(t,e){"use strict";"function"==typeof define&&define.amd?define("outlayer/outlayer",["ev-emitter/ev-emitter","get-size/get-size","fizzy-ui-utils/utils","./item"],function(i,n,o,r){return e(t,i,n,o,r)}):"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter"),require("get-size"),require("fizzy-ui-utils"),require("./item")):t.Outlayer=e(t,t.EvEmitter,t.getSize,t.fizzyUIUtils,t.Outlayer.Item)}(window,function(t,e,i,n,o){"use strict";function r(t,e){var i=n.getQueryElement(t);if(!i)return void(h&&h.error("Bad element for "+this.constructor.namespace+": "+(i||t)));this.element=i,u&&(this.$element=u(this.element)),this.options=n.extend({},this.constructor.defaults),this.option(e);var o=++l;this.element.outlayerGUID=o,c[o]=this,this._create();var r=this._getOption("initLayout");r&&this.layout()}function s(t){function e(){t.apply(this,arguments)}return e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e}function a(t){if("number"==typeof t)return t;var e=t.match(/(^\d*\.?\d*)(\w*)/),i=e&&e[1],n=e&&e[2];if(!i.length)return 0;i=parseFloat(i);var o=m[n]||1;return i*o}var h=t.console,u=t.jQuery,d=function(){},l=0,c={};r.namespace="outlayer",r.Item=o,r.defaults={containerStyle:{position:"relative"},initLayout:!0,originLeft:!0,originTop:!0,resize:!0,resizeContainer:!0,transitionDuration:"0.4s",hiddenStyle:{opacity:0,transform:"scale(0.001)"},visibleStyle:{opacity:1,transform:"scale(1)"}};var f=r.prototype;n.extend(f,e.prototype),f.option=function(t){n.extend(this.options,t)},f._getOption=function(t){var e=this.constructor.compatOptions[t];return e&&void 0!==this.options[e]?this.options[e]:this.options[t]},r.compatOptions={initLayout:"isInitLayout",horizontal:"isHorizontal",layoutInstant:"isLayoutInstant",originLeft:"isOriginLeft",originTop:"isOriginTop",resize:"isResizeBound",resizeContainer:"isResizingContainer"},f._create=function(){this.reloadItems(),this.stamps=[],this.stamp(this.options.stamp),n.extend(this.element.style,this.options.containerStyle);var t=this._getOption("resize");t&&this.bindResize()},f.reloadItems=function(){this.items=this._itemize(this.element.children)},f._itemize=function(t){for(var e=this._filterFindItemElements(t),i=this.constructor.Item,n=[],o=0;o<e.length;o++){var r=e[o],s=new i(r,this);n.push(s)}return n},f._filterFindItemElements=function(t){return n.filterFindElements(t,this.options.itemSelector)},f.getItemElements=function(){return this.items.map(function(t){return t.element})},f.layout=function(){this._resetLayout(),this._manageStamps();var t=this._getOption("layoutInstant"),e=void 0!==t?t:!this._isLayoutInited;this.layoutItems(this.items,e),this._isLayoutInited=!0},f._init=f.layout,f._resetLayout=function(){this.getSize()},f.getSize=function(){this.size=i(this.element)},f._getMeasurement=function(t,e){var n,o=this.options[t];o?("string"==typeof o?n=this.element.querySelector(o):o instanceof HTMLElement&&(n=o),this[t]=n?i(n)[e]:o):this[t]=0},f.layoutItems=function(t,e){t=this._getItemsForLayout(t),this._layoutItems(t,e),this._postLayout()},f._getItemsForLayout=function(t){return t.filter(function(t){return!t.isIgnored})},f._layoutItems=function(t,e){if(this._emitCompleteOnItems("layout",t),t&&t.length){var i=[];t.forEach(function(t){var n=this._getItemLayoutPosition(t);n.item=t,n.isInstant=e||t.isLayoutInstant,i.push(n)},this),this._processLayoutQueue(i)}},f._getItemLayoutPosition=function(){return{x:0,y:0}},f._processLayoutQueue=function(t){this.updateStagger(),t.forEach(function(t,e){this._positionItem(t.item,t.x,t.y,t.isInstant,e)},this)},f.updateStagger=function(){var t=this.options.stagger;return null===t||void 0===t?void(this.stagger=0):(this.stagger=a(t),this.stagger)},f._positionItem=function(t,e,i,n,o){n?t.goTo(e,i):(t.stagger(o*this.stagger),t.moveTo(e,i))},f._postLayout=function(){this.resizeContainer()},f.resizeContainer=function(){var t=this._getOption("resizeContainer");if(t){var e=this._getContainerSize();e&&(this._setContainerMeasure(e.width,!0),this._setContainerMeasure(e.height,!1))}},f._getContainerSize=d,f._setContainerMeasure=function(t,e){if(void 0!==t){var i=this.size;i.isBorderBox&&(t+=e?i.paddingLeft+i.paddingRight+i.borderLeftWidth+i.borderRightWidth:i.paddingBottom+i.paddingTop+i.borderTopWidth+i.borderBottomWidth),t=Math.max(t,0),this.element.style[e?"width":"height"]=t+"px"}},f._emitCompleteOnItems=function(t,e){function i(){o.dispatchEvent(t+"Complete",null,[e])}function n(){s++,s==r&&i()}var o=this,r=e.length;if(!e||!r)return void i();var s=0;e.forEach(function(e){e.once(t,n)})},f.dispatchEvent=function(t,e,i){var n=e?[e].concat(i):i;if(this.emitEvent(t,n),u)if(this.$element=this.$element||u(this.element),e){var o=u.Event(e);o.type=t,this.$element.trigger(o,i)}else this.$element.trigger(t,i)},f.ignore=function(t){var e=this.getItem(t);e&&(e.isIgnored=!0)},f.unignore=function(t){var e=this.getItem(t);e&&delete e.isIgnored},f.stamp=function(t){t=this._find(t),t&&(this.stamps=this.stamps.concat(t),t.forEach(this.ignore,this))},f.unstamp=function(t){t=this._find(t),t&&t.forEach(function(t){n.removeFrom(this.stamps,t),this.unignore(t)},this)},f._find=function(t){return t?("string"==typeof t&&(t=this.element.querySelectorAll(t)),t=n.makeArray(t)):void 0},f._manageStamps=function(){this.stamps&&this.stamps.length&&(this._getBoundingRect(),this.stamps.forEach(this._manageStamp,this))},f._getBoundingRect=function(){var t=this.element.getBoundingClientRect(),e=this.size;this._boundingRect={left:t.left+e.paddingLeft+e.borderLeftWidth,top:t.top+e.paddingTop+e.borderTopWidth,right:t.right-(e.paddingRight+e.borderRightWidth),bottom:t.bottom-(e.paddingBottom+e.borderBottomWidth)}},f._manageStamp=d,f._getElementOffset=function(t){var e=t.getBoundingClientRect(),n=this._boundingRect,o=i(t),r={left:e.left-n.left-o.marginLeft,top:e.top-n.top-o.marginTop,right:n.right-e.right-o.marginRight,bottom:n.bottom-e.bottom-o.marginBottom};return r},f.handleEvent=n.handleEvent,f.bindResize=function(){t.addEventListener("resize",this),this.isResizeBound=!0},f.unbindResize=function(){t.removeEventListener("resize",this),this.isResizeBound=!1},f.onresize=function(){this.resize()},n.debounceMethod(r,"onresize",100),f.resize=function(){this.isResizeBound&&this.needsResizeLayout()&&this.layout()},f.needsResizeLayout=function(){var t=i(this.element),e=this.size&&t;return e&&t.innerWidth!==this.size.innerWidth},f.addItems=function(t){var e=this._itemize(t);return e.length&&(this.items=this.items.concat(e)),e},f.appended=function(t){var e=this.addItems(t);e.length&&(this.layoutItems(e,!0),this.reveal(e))},f.prepended=function(t){var e=this._itemize(t);if(e.length){var i=this.items.slice(0);this.items=e.concat(i),this._resetLayout(),this._manageStamps(),this.layoutItems(e,!0),this.reveal(e),this.layoutItems(i)}},f.reveal=function(t){if(this._emitCompleteOnItems("reveal",t),t&&t.length){var e=this.updateStagger();t.forEach(function(t,i){t.stagger(i*e),t.reveal()})}},f.hide=function(t){if(this._emitCompleteOnItems("hide",t),t&&t.length){var e=this.updateStagger();t.forEach(function(t,i){t.stagger(i*e),t.hide()})}},f.revealItemElements=function(t){var e=this.getItems(t);this.reveal(e)},f.hideItemElements=function(t){var e=this.getItems(t);this.hide(e)},f.getItem=function(t){for(var e=0;e<this.items.length;e++){var i=this.items[e];if(i.element==t)return i}},f.getItems=function(t){t=n.makeArray(t);var e=[];return t.forEach(function(t){var i=this.getItem(t);i&&e.push(i)},this),e},f.remove=function(t){var e=this.getItems(t);this._emitCompleteOnItems("remove",e),e&&e.length&&e.forEach(function(t){t.remove(),n.removeFrom(this.items,t)},this)},f.destroy=function(){var t=this.element.style;t.height="",t.position="",t.width="",this.items.forEach(function(t){t.destroy()}),this.unbindResize();var e=this.element.outlayerGUID;delete c[e],delete this.element.outlayerGUID,u&&u.removeData(this.element,this.constructor.namespace)},r.data=function(t){t=n.getQueryElement(t);var e=t&&t.outlayerGUID;return e&&c[e]},r.create=function(t,e){var i=s(r);return i.defaults=n.extend({},r.defaults),n.extend(i.defaults,e),i.compatOptions=n.extend({},r.compatOptions),i.namespace=t,i.data=r.data,i.Item=s(o),n.htmlInit(i,t),u&&u.bridget&&u.bridget(t,i),i};var m={ms:1,s:1e3};return r.Item=o,r}),function(t,e){"function"==typeof define&&define.amd?define(["outlayer/outlayer","get-size/get-size"],e):"object"==typeof module&&module.exports?module.exports=e(require("outlayer"),require("get-size")):t.Masonry=e(t.Outlayer,t.getSize)}(window,function(t,e){var i=t.create("masonry");i.compatOptions.fitWidth="isFitWidth";var n=i.prototype;return n._resetLayout=function(){this.getSize(),this._getMeasurement("columnWidth","outerWidth"),this._getMeasurement("gutter","outerWidth"),this.measureColumns(),this.colYs=[];for(var t=0;t<this.cols;t++)this.colYs.push(0);this.maxY=0,this.horizontalColIndex=0},n.measureColumns=function(){if(this.getContainerWidth(),!this.columnWidth){var t=this.items[0],i=t&&t.element;this.columnWidth=i&&e(i).outerWidth||this.containerWidth}var n=this.columnWidth+=this.gutter,o=this.containerWidth+this.gutter,r=o/n,s=n-o%n,a=s&&1>s?"round":"floor";r=Math[a](r),this.cols=Math.max(r,1)},n.getContainerWidth=function(){var t=this._getOption("fitWidth"),i=t?this.element.parentNode:this.element,n=e(i);this.containerWidth=n&&n.innerWidth},n._getItemLayoutPosition=function(t){t.getSize();var e=t.size.outerWidth%this.columnWidth,i=e&&1>e?"round":"ceil",n=Math[i](t.size.outerWidth/this.columnWidth);n=Math.min(n,this.cols);for(var o=this.options.horizontalOrder?"_getHorizontalColPosition":"_getTopColPosition",r=this[o](n,t),s={x:this.columnWidth*r.col,y:r.y},a=r.y+t.size.outerHeight,h=n+r.col,u=r.col;h>u;u++)this.colYs[u]=a;return s},n._getTopColPosition=function(t){var e=this._getTopColGroup(t),i=Math.min.apply(Math,e);return{col:e.indexOf(i),y:i}},n._getTopColGroup=function(t){if(2>t)return this.colYs;for(var e=[],i=this.cols+1-t,n=0;i>n;n++)e[n]=this._getColGroupY(n,t);return e},n._getColGroupY=function(t,e){if(2>e)return this.colYs[t];var i=this.colYs.slice(t,t+e);return Math.max.apply(Math,i)},n._getHorizontalColPosition=function(t,e){var i=this.horizontalColIndex%this.cols,n=t>1&&i+t>this.cols;i=n?0:i;var o=e.size.outerWidth&&e.size.outerHeight;return this.horizontalColIndex=o?i+t:this.horizontalColIndex,{col:i,y:this._getColGroupY(i,t)}},n._manageStamp=function(t){var i=e(t),n=this._getElementOffset(t),o=this._getOption("originLeft"),r=o?n.left:n.right,s=r+i.outerWidth,a=Math.floor(r/this.columnWidth);a=Math.max(0,a);var h=Math.floor(s/this.columnWidth);h-=s%this.columnWidth?0:1,h=Math.min(this.cols-1,h);for(var u=this._getOption("originTop"),d=(u?n.top:n.bottom)+i.outerHeight,l=a;h>=l;l++)this.colYs[l]=Math.max(d,this.colYs[l])},n._getContainerSize=function(){this.maxY=Math.max.apply(Math,this.colYs);var t={height:this.maxY};return this._getOption("fitWidth")&&(t.width=this._getContainerFitWidth()),t},n._getContainerFitWidth=function(){for(var t=0,e=this.cols;--e&&0===this.colYs[e];)t++;return(this.cols-t)*this.columnWidth-this.gutter},n.needsResizeLayout=function(){var t=this.containerWidth;return this.getContainerWidth(),t!=this.containerWidth},i});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{% load static %}
|
|
2
|
+
|
|
3
|
+
<link rel="stylesheet" href="{% static 'django_image_gallery/css/masonry.css' %}">
|
|
4
|
+
<link rel="stylesheet" href="{% static 'django_image_gallery/css/gallery.css' %}">
|
|
5
|
+
|
|
6
|
+
<div id="image-gallery">
|
|
7
|
+
<div class="image-gallery-container">
|
|
8
|
+
|
|
9
|
+
<div class="gallery-grid">
|
|
10
|
+
{% for image in gallery_images|slice:":8" %}
|
|
11
|
+
<div class="gallery-item">
|
|
12
|
+
<img src="{{ image.image.url }}" class="gallery-image" data-index="{{ forloop.counter0 }}" loading="lazy" alt="{{ image.title }}">
|
|
13
|
+
</div>
|
|
14
|
+
{% endfor %}
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<template id="image-gallery-template">
|
|
18
|
+
{% for image in gallery_images|slice:"8:" %}
|
|
19
|
+
<div class="gallery-item">
|
|
20
|
+
<img src="{{ image.image.url }}" class="gallery-image" data-index="{{ forloop.counter0|add:8 }}" loading="lazy" alt="{{ image.title }}">
|
|
21
|
+
</div>
|
|
22
|
+
{% endfor %}
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
{% if gallery_images|length > 8 %}
|
|
26
|
+
<div class="image-gallery-controls">
|
|
27
|
+
<button id="image-gallery-load-more" type="button">
|
|
28
|
+
Load More
|
|
29
|
+
</button>
|
|
30
|
+
|
|
31
|
+
<button id="image-gallery-hide" type="button" hidden>
|
|
32
|
+
Hide Images
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
{% endif %}
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div id="image-gallery-modal" class="image-gallery-modal" hidden aria-hidden="true" tabindex="-1">
|
|
39
|
+
<div class="image-gallery-modal-content">
|
|
40
|
+
|
|
41
|
+
<button type="button" class="gallery-close" aria-label="Close gallery">
|
|
42
|
+
×
|
|
43
|
+
</button>
|
|
44
|
+
|
|
45
|
+
<button type="button" class="gallery-prev" aria-label="Previous image">
|
|
46
|
+
❮
|
|
47
|
+
</button>
|
|
48
|
+
|
|
49
|
+
<div class="gallery-slide">
|
|
50
|
+
{% for image in gallery_images %}
|
|
51
|
+
<img src="{{ image.image.url }}" class="gallery-modal-image" data-index="{{ forloop.counter0 }}" alt="{{ image.title }}">
|
|
52
|
+
{% endfor %}
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<button type="button" class="gallery-next" aria-label="Next image">
|
|
56
|
+
❯
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<script src="{% static 'django_image_gallery/js/imagesloaded.pkgd.min.js' %}"></script>
|
|
63
|
+
<script src="{% static 'django_image_gallery/js/masonry.pkgd.min.js' %}"></script>
|
|
64
|
+
<script src="{% static 'django_image_gallery/js/gallery.js' %}"></script>
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from django import template
|
|
2
|
+
|
|
3
|
+
from django_image_gallery.models import GalleryImage
|
|
4
|
+
|
|
5
|
+
register = template.Library()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@register.inclusion_tag("django_image_gallery/gallery.html")
|
|
9
|
+
def gallery():
|
|
10
|
+
gallery_images = GalleryImage.objects.filter(is_published=True)
|
|
11
|
+
return {"gallery_images": gallery_images}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
|
|
2
|
+
import tempfile
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
|
|
5
|
+
from PIL import Image
|
|
6
|
+
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
7
|
+
from django.test import TestCase, override_settings
|
|
8
|
+
from django.template import Context, Template
|
|
9
|
+
|
|
10
|
+
from django_image_gallery.models import GalleryImage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GalleryImageTests(TestCase):
|
|
14
|
+
def setUp(self):
|
|
15
|
+
self.temp_dir = tempfile.TemporaryDirectory()
|
|
16
|
+
self.addCleanup(self.temp_dir.cleanup)
|
|
17
|
+
|
|
18
|
+
media_settings = override_settings(MEDIA_ROOT=self.temp_dir.name)
|
|
19
|
+
media_settings.enable()
|
|
20
|
+
self.addCleanup(media_settings.disable)
|
|
21
|
+
|
|
22
|
+
def create_test_image(self, filename="test-image.jpg", size=(800, 600)):
|
|
23
|
+
buffer = BytesIO()
|
|
24
|
+
|
|
25
|
+
Image.new("RGB", size, color=(100, 150, 200)).save(
|
|
26
|
+
buffer,
|
|
27
|
+
format="JPEG",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
return SimpleUploadedFile(
|
|
31
|
+
filename,
|
|
32
|
+
buffer.getvalue(),
|
|
33
|
+
content_type="image/jpeg",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def test_create_gallery_image(self):
|
|
37
|
+
image = GalleryImage.objects.create(
|
|
38
|
+
title="Test Image",
|
|
39
|
+
image=self.create_test_image(),
|
|
40
|
+
caption="A test caption.",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self.assertEqual(image.title, "Test Image")
|
|
44
|
+
self.assertEqual(image.caption, "A test caption.")
|
|
45
|
+
self.assertEqual(image.sort_order, 0)
|
|
46
|
+
self.assertFalse(image.is_published)
|
|
47
|
+
self.assertEqual(str(image), "Test Image")
|
|
48
|
+
|
|
49
|
+
def test_image_resizing(self):
|
|
50
|
+
image = GalleryImage.objects.create(
|
|
51
|
+
title="Large Image",
|
|
52
|
+
image=self.create_test_image(size=(2400, 1200)),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
with Image.open(image.image.path) as stored_image:
|
|
56
|
+
self.assertEqual(stored_image.size, (2000, 1000))
|
|
57
|
+
|
|
58
|
+
def test_small_images_are_not_resized(self):
|
|
59
|
+
image = GalleryImage.objects.create(
|
|
60
|
+
title="Small Image",
|
|
61
|
+
image=self.create_test_image(size=(800, 600)),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
with Image.open(image.image.path) as stored_image:
|
|
65
|
+
self.assertEqual(stored_image.size, (800, 600))
|
|
66
|
+
|
|
67
|
+
def test_gallery_image_ordering(self):
|
|
68
|
+
first = GalleryImage.objects.create(
|
|
69
|
+
title="First",
|
|
70
|
+
image=self.create_test_image("first.jpg"),
|
|
71
|
+
sort_order=1,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
second = GalleryImage.objects.create(
|
|
75
|
+
title="Second",
|
|
76
|
+
image=self.create_test_image("second.jpg"),
|
|
77
|
+
sort_order=2,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
images = list(GalleryImage.objects.all())
|
|
81
|
+
|
|
82
|
+
self.assertEqual(images, [first, second])
|
|
83
|
+
|
|
84
|
+
def test_gallery_displays_only_published_images(self):
|
|
85
|
+
GalleryImage.objects.create(
|
|
86
|
+
title="Published Image",
|
|
87
|
+
image=self.create_test_image("published.jpg"),
|
|
88
|
+
is_published=True,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
GalleryImage.objects.create(
|
|
92
|
+
title="Unpublished Image",
|
|
93
|
+
image=self.create_test_image("unpublished.jpg"),
|
|
94
|
+
is_published=False,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
template = Template(
|
|
98
|
+
"{% load django_image_gallery %}{% gallery %}"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
html = template.render(Context({}))
|
|
102
|
+
|
|
103
|
+
self.assertIn("Published Image", html)
|
|
104
|
+
self.assertIn("published.jpg", html)
|
|
105
|
+
self.assertNotIn("Unpublished Image", html)
|
|
106
|
+
self.assertNotIn("unpublished.jpg", html)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-image-gallery
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Django image gallery application.
|
|
5
|
+
Author: Alexander Towe
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/alextowe/django-image-gallery
|
|
8
|
+
Project-URL: Repository, https://github.com/alextowe/django-image-gallery
|
|
9
|
+
Project-URL: Issues, https://github.com/alextowe/django-image-gallery/issues
|
|
10
|
+
Classifier: Framework :: Django
|
|
11
|
+
Classifier: Framework :: Django :: 5.2
|
|
12
|
+
Classifier: Framework :: Django :: 6.0
|
|
13
|
+
Classifier: Framework :: Django :: 6.1
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
License-File: THIRD_PARTY_NOTICES.md
|
|
23
|
+
Requires-Dist: Django<7.0,>=5.2
|
|
24
|
+
Requires-Dist: Pillow>=10.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Django Image Gallery
|
|
28
|
+
|
|
29
|
+
An image gallery application for Django projects. Provides an easy way to manage, organize, and display images through the Django admin interface and a reusable template tag.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Django model for managing gallery images
|
|
34
|
+
- Automatic resizing of uploaded images exceeding 2000 × 2000 pixels
|
|
35
|
+
- Published and unpublished image management
|
|
36
|
+
- Configurable image ordering
|
|
37
|
+
- Masonry image layout
|
|
38
|
+
- Load-more functionality
|
|
39
|
+
- Full-screen image viewer
|
|
40
|
+
- Django admin integration
|
|
41
|
+
- Reusable template tag
|
|
42
|
+
|
|
43
|
+
## Requirements
|
|
44
|
+
|
|
45
|
+
- Python 3.10+
|
|
46
|
+
- Django 5.2, 6.0, or 6.1
|
|
47
|
+
- Pillow 10.0 or later
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
Install the package using pip:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install django-image-gallery
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
Add `django_image_gallery` to your project's `INSTALLED_APPS`:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
INSTALLED_APPS = [
|
|
63
|
+
# ...
|
|
64
|
+
"django_image_gallery",
|
|
65
|
+
]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Run the database migrations:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python manage.py migrate
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
Load the template tag library and include the gallery wherever you want it displayed:
|
|
77
|
+
|
|
78
|
+
```django
|
|
79
|
+
{% load django_image_gallery %}
|
|
80
|
+
|
|
81
|
+
{% gallery %}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The template tag retrieves all published gallery images and renders them using the package's included gallery template.
|
|
85
|
+
|
|
86
|
+
## Media Configuration
|
|
87
|
+
|
|
88
|
+
Configure Django to handle uploaded media files:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
MEDIA_URL = "/media/"
|
|
92
|
+
MEDIA_ROOT = BASE_DIR / "media"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
During local development, you can serve media files through Django by adding the following to your project's root `urls.py`:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from django.conf import settings
|
|
99
|
+
from django.conf.urls.static import static
|
|
100
|
+
|
|
101
|
+
urlpatterns = [
|
|
102
|
+
# Your URL patterns
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
if settings.DEBUG:
|
|
106
|
+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
For production deployments, configure your web server or storage backend to serve uploaded media files. Uploaded images must be accessible through the configured media storage.
|
|
110
|
+
|
|
111
|
+
## Managing Images
|
|
112
|
+
|
|
113
|
+
Images can be added and managed through the Django admin interface.
|
|
114
|
+
|
|
115
|
+
After creating a superuser:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
python manage.py createsuperuser
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Log in to the admin interface and locate **Gallery Images**.
|
|
122
|
+
|
|
123
|
+
Each image supports the following fields:
|
|
124
|
+
|
|
125
|
+
| Field | Description |
|
|
126
|
+
|---|---|
|
|
127
|
+
| Title | Optional image title |
|
|
128
|
+
| Image | Uploaded image file |
|
|
129
|
+
| Caption | Optional image caption |
|
|
130
|
+
| Sort order | Controls the display order of images |
|
|
131
|
+
| Published | Determines whether the image appears in the gallery |
|
|
132
|
+
|
|
133
|
+
Only published images appear in the gallery. Images are ordered by `sort_order`, with newer entries taking precedence when sort orders match.
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
This project is licensed under the MIT License. See the `LICENSE` file for details.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
THIRD_PARTY_NOTICES.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
django_image_gallery/__init__.py
|
|
6
|
+
django_image_gallery/admin.py
|
|
7
|
+
django_image_gallery/apps.py
|
|
8
|
+
django_image_gallery/models.py
|
|
9
|
+
django_image_gallery/tests.py
|
|
10
|
+
django_image_gallery/views.py
|
|
11
|
+
django_image_gallery.egg-info/PKG-INFO
|
|
12
|
+
django_image_gallery.egg-info/SOURCES.txt
|
|
13
|
+
django_image_gallery.egg-info/dependency_links.txt
|
|
14
|
+
django_image_gallery.egg-info/requires.txt
|
|
15
|
+
django_image_gallery.egg-info/top_level.txt
|
|
16
|
+
django_image_gallery/migrations/0001_initial.py
|
|
17
|
+
django_image_gallery/migrations/__init__.py
|
|
18
|
+
django_image_gallery/static/django_image_gallery/css/gallery.css
|
|
19
|
+
django_image_gallery/static/django_image_gallery/css/masonry.css
|
|
20
|
+
django_image_gallery/static/django_image_gallery/js/gallery.js
|
|
21
|
+
django_image_gallery/static/django_image_gallery/js/imagesloaded.pkgd.min.js
|
|
22
|
+
django_image_gallery/static/django_image_gallery/js/masonry.pkgd.min.js
|
|
23
|
+
django_image_gallery/templates/django_image_gallery/gallery.html
|
|
24
|
+
django_image_gallery/templatetags/__init__.py
|
|
25
|
+
django_image_gallery/templatetags/django_image_gallery.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
django_image_gallery
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-image-gallery"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A Django image gallery application."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Alexander Towe" },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
dependencies = [
|
|
18
|
+
"Django>=5.2,<7.0",
|
|
19
|
+
"Pillow>=10.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Framework :: Django",
|
|
24
|
+
"Framework :: Django :: 5.2",
|
|
25
|
+
"Framework :: Django :: 6.0",
|
|
26
|
+
"Framework :: Django :: 6.1",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
31
|
+
"Programming Language :: Python :: 3.14",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/alextowe/django-image-gallery"
|
|
36
|
+
Repository = "https://github.com/alextowe/django-image-gallery"
|
|
37
|
+
Issues = "https://github.com/alextowe/django-image-gallery/issues"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools]
|
|
40
|
+
license-files = ["LICENSE", "THIRD_PARTY_NOTICES.md"]
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
include = ["django_image_gallery*"]
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.package-data]
|
|
46
|
+
django_image_gallery = [
|
|
47
|
+
"templates/**/*.html",
|
|
48
|
+
"static/**/*",
|
|
49
|
+
"migrations/*.py",
|
|
50
|
+
]
|