12 |
13 | ## How to prevent this behaviour?
14 |
15 | One of the way to prevent this behaviour is create a custom `ModelForm` and raises a `ValidationError` for string values that is not present on the original value. See a example:
16 |
17 | ```python
18 | from django import forms
19 | from django.core.exceptions import ValidationError
20 |
21 |
22 | class TestWithArrayFieldForm(forms.ModelForm):
23 | old_values = []
24 |
25 | def map_is_valid_images(self, value):
26 | if not isinstance(value, str):
27 | return False
28 | return value not in self.old_values
29 |
30 | def clean(self):
31 | data = super().clean()
32 |
33 | self.old_values = []
34 | if self.instance is not None:
35 | self.old_values = self.instance.images
36 |
37 | has_changed = any(list(map(self.map_is_valid_images, data.get('images'))))
38 | if has_changed:
39 | raise ValidationError('One of the non-changed value is corrupted.')
40 |
41 | return data
42 |
43 | class Meta:
44 | model = TestWithArrayField
45 | fields = "__all__"
46 | ```
47 |
--------------------------------------------------------------------------------
/docs/array_field/03-htmx.md:
--------------------------------------------------------------------------------
1 | # Out of Box HTMX Support
2 |
3 | !!! warning "Version Information"
4 |
5 | Introduced at the 0.6.0 version.
6 |
7 | The array field, now, has out of box support for HTMX. [see docs here](../htmx/02-array_field.md).
8 |
--------------------------------------------------------------------------------
/docs/array_field/04-force-color-scheme.md:
--------------------------------------------------------------------------------
1 | # Force dark or light color scheme
2 |
3 | On the [ADR 0006](../development/architecture-decision-records/0006-why-enforce-light-theme-by-class.md) we discuted a way to force color scheme (`dark` or `light`) to ensure widget color scheme outside of `django-admin`. The two cases bellow is tested using [UI regression](../development/architecture-decision-records/0002-why-ui-regression-tests.md) tests. This is the same way that we implemented on default widget and this is [documented here](../widget/07-force-color-scheme.md).
4 |
5 |
6 | ## Light theme
7 |
8 | To enforce light widget, use `.iuw-light` class:
9 |
10 | ```html
11 |
12 | {{ form }}
13 |
14 | ```
15 |
16 | ## Dark theme
17 |
18 | To enforce dark widget, use `.iuw-dark` class:
19 |
20 | ```html
21 |
22 | {{ form }}
23 |
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/array_field/05-max-images.md:
--------------------------------------------------------------------------------
1 | # Max number of images
2 |
3 | !!! warning "Version Information"
4 |
5 | Introduced at the 1.1.0 version.
6 |
7 | !!! warning "Database Information"
8 |
9 | Supported only on PostgreSQL Database's.
10 |
11 | We introduced a `kwarg` called `max_images` on `ImageListField` field to limit the number of images that user can choose by picker. The default value is `1000` (only because this value are, previous, hardcoded value on the html template). The usage is:
12 |
13 | ```python
14 | from django.db import models
15 | from image_uploader_widget.postgres import ImageListField
16 |
17 | class TestWithArrayField(models.Model):
18 | images = ImageListField(blank=True, null=True, max_images=2, upload_to="admin_test")
19 |
20 | class Meta:
21 | verbose_name = "Test With Array Field"
22 | ```
23 |
24 | !!! warning "Validation"
25 |
26 | The limit, for now, is only on front-end widget. If you want to validate in server-side, do it by yourself for now.
27 |
--------------------------------------------------------------------------------
/docs/array_field/images/hidden-tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inventare/django-image-uploader-widget/708f5a3614fd2d7644ca2dd730b051c63b9cd724/docs/array_field/images/hidden-tree.png
--------------------------------------------------------------------------------
/docs/htmx/01-widget.md:
--------------------------------------------------------------------------------
1 | # Out of box HTMX Support for Widget
2 |
3 | !!! warning "Version Information"
4 |
5 | Introduced at the 0.6.0 version.
6 |
7 | The HTMX is, now, out of box supported, for **widget** and **array field widget**. Even though it has support out of the box, some precautions need to be taken. The media (*scripts* and *styles* of the widget) needs to be loaded in the parent document:
8 |
9 | ## Behaviour
10 |
11 | On the version `0.6.0` the **JavaScript** of the widget was rewrited using event **Event bubbling** and the render part is moved to template system to support the widget behaviour without initialization needed.
12 |
13 | ## Usage Example
14 |
15 | This usage example is taken form `tests` application that is used to run our e2e test cases. The parent view and template is created using:
16 |
17 | ```python
18 | # views.py
19 | def render_parent(request):
20 | form = TestRequiredForm()
21 | form2 = TestWithArrayFieldForm()
22 | context = {
23 | "media": form.media + form2.media,
24 | }
25 | template = "test_htmx.html"
26 | return render(request, template, context=context)
27 | ```
28 |
29 | ```html
30 |
31 |
32 |
33 |
34 |
35 |
36 | HTMX
37 | {{ media }}
38 |
39 |
40 |
41 |
51 |
52 |
53 |
54 |
55 | ```
56 |
57 | The widget view and template is created using:
58 |
59 | ```python
60 | # views.py
61 | def render_widget_required(request, pk=None):
62 | instance = TestRequired.objects.get(pk=pk) if pk else None
63 | if request.method == "POST":
64 | form = TestRequiredForm(
65 | instance=instance, data=request.POST, files=request.FILES
66 | )
67 | if form.is_valid():
68 | form.save()
69 | instance = form.instance
70 | form = TestRequiredForm(instance=instance)
71 | add_message(request, SUCCESS, "Saved")
72 | else:
73 | form = TestRequiredForm(instance=instance)
74 |
75 | context = {
76 | "form": form,
77 | "instance": instance,
78 | "post_url": "test-htmx-image-widget/required",
79 | }
80 | template = "test_htmx_widget.html"
81 | return render(request, template, context=context)
82 | ```
83 |
84 | ```html
85 |
86 |
105 | ```
106 |
--------------------------------------------------------------------------------
/docs/htmx/02-array_field.md:
--------------------------------------------------------------------------------
1 | # Out of box HTMX Support for Array Field
2 |
3 | !!! warning "Version Information"
4 |
5 | Introduced at the 0.6.0 version.
6 |
7 | ## Behaviour
8 |
9 | The behaviour for the out of box HTMX support for Array Field is a little bit different of the **widget**. The **array field** uses the base of inline admin to render the widget and the *drag and drop* support request initialization. Then to add out of box support, we decided to handle the initialization inside the `htmx:afterSwap` event:
10 |
11 | ```javascript
12 | // image-uploader-inline.js
13 | //
14 | // ...
15 | //
16 | document.addEventListener('DOMContentLoaded', function() {
17 | initialize();
18 | });
19 | document.addEventListener('htmx:afterSwap', function(ev) {
20 | initialize(ev.target);
21 | })
22 | ```
23 |
24 | ## Usage example
25 |
26 | The usage example is the same of the widget. The parent view and template is created using:
27 |
28 | ```python
29 | # views.py
30 | def render_parent(request):
31 | form = TestRequiredForm()
32 | form2 = TestWithArrayFieldForm()
33 | context = {
34 | "media": form.media + form2.media,
35 | }
36 | template = "test_htmx.html"
37 | return render(request, template, context=context)
38 | ```
39 |
40 | ```html
41 |
42 |
43 |
44 |
45 |
46 |
47 | HTMX
48 | {{ media }}
49 |
50 |
51 |
52 |
62 |
63 |
64 |
65 |
66 | ```
67 |
68 | The widget view and template is created using:
69 |
70 | ```python
71 | # views.py
72 | def render_array_field_required(request, pk=None):
73 | instance = TestWithArrayField.objects.get(pk=pk) if pk else None
74 | if request.method == "POST":
75 | form = TestWithArrayFieldForm(
76 | instance=instance, data=request.POST, files=request.FILES
77 | )
78 | if form.is_valid():
79 | form.save()
80 | instance = form.instance
81 | form = TestWithArrayFieldForm(instance=instance)
82 | add_message(request, SUCCESS, "Saved")
83 | else:
84 | form = TestWithArrayFieldForm(instance=instance)
85 |
86 | context = {
87 | "form": form,
88 | "instance": instance,
89 | "post_url": "test-htmx-image-widget/array_field",
90 | }
91 | template = "test_htmx_widget.html"
92 | return render(request, template, context=context)
93 | ```
94 |
95 | ```html
96 |
97 |
116 | ```
117 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | The **django-image-uploader-widget** is a set of **django** widget and **django-admin** inline editor to handle better image uploads with a little bit modern user interface.
4 |
5 |
10 |
11 | ## Features
12 |
13 | - Support required and optional `ImageField`;
14 | - Support for `ImageField` inside inlines of **django-admin**.
15 | - Support preview modal;
16 | - Support custom inline for **django-admin** multiple images uploader.
17 | - Support reordering inside **django-admin** custom inline for multiple uploads.
18 | - Support `ArrayField` for `PostgreSQL` database.
19 | - Support upload by dropping file.
20 | - Out of box HTMX support.
21 |
22 | ## Requirements
23 |
24 | - Python 3.8+
25 | - Django 3.2+
26 |
27 | ## Getting Started
28 |
29 | To get started, install this plugin with the pip package manager:
30 |
31 | ```sh
32 | pip install django-image-uploader-widget
33 | ```
34 |
35 | !!! warning "Version Information"
36 |
37 | On the `1.0.0` release of this package we droped the support for `Django 3.2`, `Django 4.0` and `Django 4.1`. We, currently, maintain the support for `Django 4.2` (LTS), `Django 5.0` and `Django 5.1`. Then, if you are using `Django 3.2`, `4.0` or `4.1`, installs `0.7.1` version:
38 |
39 | ```bash
40 | pip install django-image-uploader-widget==0.7.1
41 | ```
42 |
43 | then, go to the `settings.py` file and add the `image_uploader_widget` to the installed apps:
44 |
45 | ```python
46 | INSTALLED_APPS = [
47 | # ...
48 | 'image_uploader_widget',
49 | # ...
50 | ]
51 | ```
52 |
53 | ## Basic Usage
54 |
55 | ### With Admin
56 |
57 | The `ImageUploaderWidget` is a class that implements a custom widget for single image uploader and can be used inside the `formfield_overrides` attribute inside the `ModelAdmin` class.
58 |
59 | ```python
60 | # admin.py
61 | from django.contrib import admin
62 | from django.db import models
63 | from image_uploader_widget.widgets import ImageUploaderWidget
64 | from .models import YourModel
65 |
66 |
67 | @admin.register(YourModel)
68 | class YourModelAdmin(admin.ModelAdmin):
69 | formfield_overrides = {
70 | models.ImageField: {'widget': ImageUploaderWidget},
71 | }
72 | ```
73 | See the [documentation](./widget/01-resumed.md) for more complex usage's.
74 |
75 |
76 | ### With ModelForm
77 |
78 | The `ImageUploaderWidget` can be used inside the `widgets` Meta attribute of a `Form`/`ModelForm`:
79 |
80 | ```python
81 | # forms.py
82 | from django import forms
83 | from image_uploader_widget.widgets import ImageUploaderWidget
84 |
85 | class ExampleForm(forms.ModelForm):
86 | class Meta:
87 | widgets = {
88 | 'image': ImageUploaderWidget(),
89 | }
90 | fields = '__all__'
91 | ```
92 |
93 | See the [documentation](./widget/01-resumed.md) for more complex usage's.
94 |
95 | ### Custom Inline Admin
96 |
97 | The `ImageUploaderInline` is implemented with the base of the `admin.StackedInline` to create an custom django-admin to work with multiple images upload using a model only to store the images:
98 |
99 | ```python
100 | # models.py
101 |
102 | class Product(models.Model):
103 | # ...
104 |
105 | class ProductImage(models.Model):
106 | product = models.ForeignKey(
107 | Product,
108 | related_name="images",
109 | on_delete=models.CASCADE
110 | )
111 | image = models.ImageField("image")
112 | # ...
113 | ```
114 |
115 | ```python
116 | # admin.py
117 | from django.contrib import admin
118 | from image_uploader_widget.admin import ImageUploaderInline
119 | from .models import Product, ProductImage
120 |
121 | class ProductImageAdmin(ImageUploaderInline):
122 | model = ProductImage
123 |
124 | @admin.register(Product)
125 | class ProductAdmin(admin.ModelAdmin):
126 | inlines = [ProductImageAdmin]
127 | ```
128 |
129 | See the [documentation](./inline_admin/01-tutorial.md) for more complex usage's.
130 |
131 | ### Array Field
132 |
133 | The ArrayField support is made by a custom field, called `ImageListField`. Then, to use it, we need to change the field from default `ArrayField` to `ImageListField`. The reason for it is: the default `ArrayField` with `ImageField` not works and some part of the behaviour of the `ImageField` is implemented inside the `ImageListField`.
134 |
135 | ```python
136 | # models.py
137 | from django.db import models
138 | from image_uploader_widget.postgres import ImageListField
139 |
140 | class TestWithArrayField(models.Model):
141 | images = ImageListField(blank=True, null=True, upload_to="admin_test")
142 |
143 | class Meta:
144 | verbose_name = "Test With Array Field"
145 | ```
146 |
147 | See the [documentation](./array_field/01-tutorial.md) for more complex usage's.
148 |
149 |
150 | ## Preview
151 |
152 | Bellow we have some preview screenshots for the widget and inline admin editor.
153 |
154 | ### Dark Theme
155 |
156 | Preview of the widget in dark theme.
157 |
158 |
159 |
160 | 
161 |
162 |
163 |
164 |
165 |
166 | 
167 |
168 |
169 |
170 | ### Light Theme
171 |
172 | Preview of the widget in light theme.
173 |
174 |
175 |
176 | 
177 |
178 |
169 |
--------------------------------------------------------------------------------
/docs/inline_admin/02-ordered.md:
--------------------------------------------------------------------------------
1 | # Ordered Images Admin
2 |
3 | !!! warning "Version Information"
4 |
5 | Introduced at the 0.4.1 version.
6 |
7 | The first thing needed to understand the ordered version of the `ImageUploaderInline` is read the [inline tutorial](./01-tutorial.md). This page has a documentation of how to extend the `ImageUploaderInline` with order field to allow to reorder, by clicking and dragging, the images inside the inline.
8 |
9 |
10 |
11 | { loading=lazy }
12 |
13 |
14 |
15 | ## Adding Order Field to Model
16 |
17 | Add a `PositiveIntegerField` to the model to store the order of the images inside the admin.
18 |
19 | ```python
20 | # ecommerce/models.py
21 | from django.db import models
22 |
23 | class Product(models.Model):
24 | name = models.CharField(max_length=100)
25 |
26 | def __str__(self):
27 | return self.name
28 |
29 | class Meta:
30 | verbose_name = 'Product'
31 | verbose_name_plural = 'Products'
32 |
33 | class ProductImage(models.Model):
34 | product = models.ForeignKey(
35 | Product,
36 | related_name="images",
37 | on_delete=models.CASCADE
38 | )
39 | image = models.ImageField("image")
40 | order = models.PositiveIntegerField('Order', default=1)
41 |
42 | def __str__(self):
43 | return str(self.image)
44 |
45 | class Meta:
46 | verbose_name = 'Product Image'
47 | verbose_name_plural = 'Product Images'
48 | ```
49 |
50 | ## Change inline to OrderedImageUploaderInline
51 |
52 | Inside the `admin.py`, change the inline from `ImageUploaderInline` to `OrderedImageUploaderInline` and setup some configs:
53 |
54 | ```python
55 | # ecommerce/admin.py
56 | from django.contrib import admin
57 | from ecommerce.models import Product, ProductImage
58 | from image_uploader_widget.admin import ImageUploaderInline
59 |
60 | class ProductImageAdmin(OrderedImageUploaderInline):
61 | model = ProductImage
62 |
63 | @admin.register(Product)
64 | class ProductAdmin(admin.ModelAdmin):
65 | inlines = [ProductImageAdmin]
66 |
67 | ```
68 |
69 | ## Attributes
70 |
71 | | Attribute | Type | Default Value | Description |
72 | | ----------- | ----- | ------------- | --------------------------------------------------------------------------- |
73 | | order_field | `str` | `"order"` | The name of field that represents the order of images. |
74 | | template | `str` | `"admin/edit_inline/ordered_image_uploader.html"` | The template path to render the widget. |
75 |
76 | All the attributes from the `ImageUploaderInline` are present too. For example, is possible to change the name of the used `order_field` by adding it's attribute to the `OrderedImageUploaderInline`:
77 |
78 | ```python
79 | from image_uploader_widget.admin import ImageUploaderInline
80 |
81 | class MyInlineAdminAdmin(OrderedImageUploaderInline):
82 | model = MyModel
83 | order_field = "my_custom_field"
84 |
85 | ```
86 |
87 | ## Mobile Touch Support
88 |
89 | !!! warning "Version Information"
90 |
91 | Introduced at the 0.6.0 version.
92 |
93 | The original behaviour for `OrderedImageUploaderInline` is manual created and don't support mobile touch events. On the version `0.6.0` the sorting uses the [Sortable](https://sortablejs.github.io/Sortable/) and, then, the mobile touch events are supported.
94 |
--------------------------------------------------------------------------------
/docs/inline_admin/03-accept.md:
--------------------------------------------------------------------------------
1 | # Change Accept Formats
2 |
3 | Like as the Widget `accept` attribute, see [reference here](../widget/04-accept.md), we have an way to customize the accept of the `ImageUploaderInline`. To customize it, use the `accept` property inside an class that inherits from `ImageUploaderInline`, like:
4 |
5 | ```python
6 | from image_uploader_widget.admin import ImageUploaderInline
7 | from . import models
8 |
9 | class InlineEditor(ImageUploaderInline):
10 | model = models.InlineItem
11 | accept = "image/jpeg"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/inline_admin/04-custom-text-and-icons.md:
--------------------------------------------------------------------------------
1 | # Custom Text and Icons
2 |
3 | To customize the text and the icons of the inline editor is a little bit faster too. We can set some variables on the `InlineAdmin` of your model, like this:
4 |
5 | ```python
6 | class CustomInlineEditor(ImageUploaderInline):
7 | model = models.CustomInlineItem
8 | add_image_text = "add_image_text"
9 | drop_text = "drop_text"
10 | empty_text = "empty_text"
11 |
12 | def get_empty_icon(self):
13 | return render(...)
14 |
15 | def get_add_icon(self):
16 | return render(...)
17 |
18 | def get_drop_icon(self):
19 | return render(...)
20 |
21 | @admin.register(models.CustomInline)
22 | class CustomInlineAdmin(admin.ModelAdmin):
23 | inlines = [CustomInlineEditor]
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/inline_admin/05-custom-colors.md:
--------------------------------------------------------------------------------
1 | # Custom Colors
2 |
3 | To customize the image uploader inline colors you can use your own css file to override the css variables defined by the `image-uploader-inline.css`. See an example:
4 |
5 | ```scss
6 | body {
7 | --iuw-background: #FFF;
8 | --iuw-border-color: #CCC;
9 | --iuw-color: #333;
10 | --iuw-placeholder-text-color: #AAA;
11 | --iuw-placeholder-destak-color: #417690;
12 | --iuw-dropzone-background: rgba(255, 255, 255, 0.8);
13 | --iuw-image-preview-border: #BFBFBF;
14 | --iuw-image-preview-shadow: rgba(0, 0, 0, 0.3);
15 | --iuw-add-image-background: #EFEFEF;
16 | --iuw-add-image-color: #AAA;
17 | }
18 | ```
19 |
20 | **Observation**: To see better the variables name, check the css file at the GitHub repository: [here](https://github.com/inventare/django-image-uploader-widget/blob/main/image_uploader_widget/static/image_uploader_widget/css/image-uploader-inline.css).
21 |
--------------------------------------------------------------------------------
/docs/inline_admin/images/admin_demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inventare/django-image-uploader-widget/708f5a3614fd2d7644ca2dd730b051c63b9cd724/docs/inline_admin/images/admin_demo.png
--------------------------------------------------------------------------------
/docs/inline_admin/images/behaviour_reorder.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inventare/django-image-uploader-widget/708f5a3614fd2d7644ca2dd730b051c63b9cd724/docs/inline_admin/images/behaviour_reorder.gif
--------------------------------------------------------------------------------
/docs/widget/01-resumed.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | If you want to read a more complete description of how to use this widget, see the [Tutorial](./02-tutorial.md). But, if you is an advanced user, only install the package:
4 |
5 | ```bash
6 | pip install django-image-uploader-widget
7 | ```
8 |
9 | !!! warning "Version Information"
10 |
11 | On the `1.0.0` release of this package we droped the support for `Django 3.2`, `Django 4.0` and `Django 4.1`. We, currently, maintain the support for `Django 4.2` (LTS), `Django 5.0` and `Django 5.1`. Then, if you are using `Django 3.2`, `4.0` or `4.1`, installs `0.7.1` version:
12 |
13 | ```bash
14 | pip install django-image-uploader-widget==0.7.1
15 | ```
16 |
17 | and add the `image_uploader_widget` to the `INSTALLED_APPS` in the `settings.py`:
18 |
19 | ```python
20 | # ...
21 |
22 | INSTALLED_APPS = [
23 | 'django.contrib.admin',
24 | 'django.contrib.auth',
25 | 'django.contrib.contenttypes',
26 | 'django.contrib.sessions',
27 | 'django.contrib.messages',
28 | 'django.contrib.staticfiles',
29 | 'image_uploader_widget',
30 | ]
31 |
32 | # ...
33 | ```
34 |
35 | And go to use it with your forms:
36 |
37 |
38 | ```python
39 | from django.forms import ModelForm
40 | from ecommerce.models import Product
41 | from image_uploader_widget.widgets import ImageUploaderWidget
42 |
43 | class ProductForm(ModelForm):
44 | class Meta:
45 | model = Product
46 | fields = ['name', 'image']
47 | widgets = {
48 | 'image': ImageUploaderWidget()
49 | }
50 | ```
51 |
52 |
57 |
--------------------------------------------------------------------------------
/docs/widget/02-tutorial.md:
--------------------------------------------------------------------------------
1 | # Full Usage Tutorial
2 |
3 | First, we need of some context: the image uploader widget is a widget to handle image uploading with a beautiful interface with click to select file and a drop file behaviour handler. It is used with django forms.
4 |
5 | This is a more long and for newbies tutorial of how to use this widget. If you is an advanced user, see the [Resumed](./01-resumed.md) version.
6 |
7 | To write this tutorial of this documentation we go to create an empty django project, then if you don't want to see this part, skip to [using the widget section](#installing-the-widget). Another information is: we're assuming you already know the basics of **django** and already have it installed in your machine.
8 |
9 | ## Creating a django project
10 |
11 | First, create a project folder. Here we call it as `my-ecommerce`:
12 |
13 | ```bash
14 | mkdir my-ecommerce
15 | cd my-ecommerce
16 | ```
17 |
18 | And, now, create a django project in this folder:
19 |
20 | ```bash
21 | django-admin startproject core .
22 | ```
23 |
24 | And, then, we have the folder structure:
25 |
26 | ```
27 | | - my-ecommerce
28 | | - core
29 | | - asgi.py
30 | | - __init__.py
31 | | - settings.py
32 | | - urls.py
33 | | - wsgi.py
34 | | - manage.py
35 | ```
36 |
37 | Create our **django** application by running the command:
38 |
39 | ```
40 | python manage.py startapp ecommerce
41 | ```
42 |
43 | And, now, we have a new, and more complex, folder structure:
44 |
45 | ```
46 | | - my-ecommerce
47 | | - core
48 | | - asgi.py
49 | | - __init__.py
50 | | - settings.py
51 | | - urls.py
52 | | - wsgi.py
53 | | - ecommerce
54 | | - migrations
55 | | - __init__.py
56 | | - admin.py
57 | | - apps.py
58 | | - __init__.py
59 | | - models.py
60 | | - tests.py
61 | | - views.py
62 | | - manage.py
63 | ```
64 |
65 | ## Installing the widget
66 |
67 | To install the widget, is possible to use the same instructions of the [Getting started](../index.md), and the first step is to install the package with pip:
68 |
69 | ```bash
70 | pip install django-image-uploader-widget
71 | ```
72 |
73 | !!! warning "Version Information"
74 |
75 | On the `1.0.0` release of this package we droped the support for `Django 3.2`, `Django 4.0` and `Django 4.1`. We, currently, maintain the support for `Django 4.2` (LTS), `Django 5.0` and `Django 5.1`. Then, if you are using `Django 3.2`, `4.0` or `4.1`, installs `0.7.1` version:
76 |
77 | ```bash
78 | pip install django-image-uploader-widget==0.7.1
79 | ```
80 |
81 | then, add it to the `INSTALLED_APPS` on the `settings.py`, in the case of this example: `core/settings.py` file. To understand better the Applications, see the django documentation: [Applications](https://docs.djangoproject.com/en/3.2/ref/applications/).
82 |
83 | ```python
84 | # core/settings.py
85 | # ...
86 |
87 | INSTALLED_APPS = [
88 | 'django.contrib.admin',
89 | 'django.contrib.auth',
90 | 'django.contrib.contenttypes',
91 | 'django.contrib.sessions',
92 | 'django.contrib.messages',
93 | 'django.contrib.staticfiles',
94 | 'image_uploader_widget',
95 | ]
96 |
97 | # ...
98 | ```
99 |
100 | !!! info "Observation"
101 |
102 | Note that the application name to be added on the `INSTALLED_APPS` are not equals to the pip package name / install name.
103 |
104 | ## Using the widget
105 |
106 | We have two basic modes to use this widget:
107 |
108 | 1. creating a ORM `Model` and using an `ModelForm` to it setting the widget.
109 |
110 | 2. creating an custom `Form` with any other behaviour.
111 |
112 | ### With ModelForm
113 |
114 | First, go to our ecommerce app models `ecommerce/models.py` and create a basic django model with an `ImageField`:
115 |
116 | ```python
117 | # ecommerce/models.py
118 | from django.db import models
119 |
120 | class Product(models.Model):
121 | name = models.CharField(max_length=100)
122 | image = models.ImageField()
123 |
124 | def __str__(self):
125 | return self.name
126 |
127 | class Meta:
128 | verbose_name = 'Product'
129 | verbose_name_plural = 'Products'
130 | ```
131 |
132 | Now, we go to create our `ModelForm`. Create a empty file on `ecommerce/forms.py` to store our django forms. And create our own `ProductForm`:
133 |
134 | ```python
135 | # ecommerce/forms.py
136 | from django.forms import ModelForm
137 | from ecommerce.models import Product
138 |
139 | class ProductForm(ModelForm):
140 | class Meta:
141 | model = Product
142 | fields = ['name', 'image']
143 | ```
144 |
145 | And, here, we can declare the widget that our `image` field uses:
146 |
147 | ```python
148 | # ecommerce/forms.py
149 | from django.forms import ModelForm
150 | from ecommerce.models import Product
151 | from image_uploader_widget.widgets import ImageUploaderWidget
152 |
153 | class ProductForm(ModelForm):
154 | class Meta:
155 | model = Product
156 | fields = ['name', 'image']
157 | widgets = {
158 | 'image': ImageUploaderWidget()
159 | }
160 | ```
161 |
162 | #### Creating and applying migrations
163 |
164 | Our Model, declared in the above section, needs to be inserted on our database using the [migrations](https://docs.djangoproject.com/en/3.2/topics/migrations/). To create our migrations, we need to add our `ecommerce` app to `INSTALLED_APPS` on the `settings.py`:
165 |
166 | ```python
167 | # core/settings.py
168 | # ...
169 |
170 | INSTALLED_APPS = [
171 | 'django.contrib.admin',
172 | 'django.contrib.auth',
173 | 'django.contrib.contenttypes',
174 | 'django.contrib.sessions',
175 | 'django.contrib.messages',
176 | 'django.contrib.staticfiles',
177 | 'image_uploader_widget',
178 | 'ecommerce',
179 | ]
180 |
181 | # ...
182 | ```
183 |
184 | Now, we go to create the migrations using the command:
185 |
186 | ```bash
187 | python manage.py makemigrations
188 | ```
189 |
190 | If you found an `ecommerce.Product.image: (fields.E210) Cannot use ImageField because Pillow is not installed.` error, just run an:
191 |
192 | ```bash
193 | pip install Pillow
194 | ```
195 |
196 | and re-run the makemigrations command. Now, we go to apply the migrations with:
197 |
198 | ```bash
199 | python manage.py migrate
200 | ```
201 |
202 | And, now, we can run the development server to see our next steps coding:
203 |
204 | ```bash
205 | python manage.py runserver
206 | ```
207 |
208 | #### See it in the action
209 |
210 | To see the widget in action, just go to the ecommerce app and create, in the `views.py`, an view that renders an form:
211 |
212 | ```python
213 | # ecommerce/views.py
214 | from django.shortcuts import render
215 | from ecommerce.forms import ProductForm
216 |
217 | def test_widget(request):
218 | context = { 'form': ProductForm() }
219 | return render(request, 'test_widget.html', context)
220 | ```
221 |
222 | Now, we can create an `templates` folder in the `ecommerce` application and inside it we need to create a `test_widget.html`:
223 |
224 | ```html
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 | Document
233 | {{ form.media }}
234 |
235 |
236 |
237 | {{ form.as_p }}
238 |
239 |
240 |
241 | ```
242 |
243 | And register this view in the `core/urls.py`:
244 |
245 | ```python
246 | # core/urls.py
247 | from django.contrib import admin
248 | from django.urls import path
249 | from ecommerce.views import test_widget
250 |
251 | urlpatterns = [
252 | path('admin/', admin.site.urls),
253 | path('test_widget/', test_widget),
254 | ]
255 | ```
256 |
257 | And go to the browser in `http://localhost:8000/test_widget/` and see the result:
258 |
259 |
264 |
265 |
266 | ### With Form and custom behaviour
267 |
268 | It's very like the above item and we need only to change some things in the `forms.py`:
269 |
270 | ```python
271 | from django import forms
272 | from ecommerce.models import Product
273 | from image_uploader_widget.widgets import ImageUploaderWidget
274 |
275 | class ProductForm(forms.Form):
276 | image = forms.ImageField(widget=ImageUploaderWidget())
277 |
278 | class Meta:
279 | fields = ['image']
280 | ```
281 |
282 | And we not need to change nothing more. It works.
283 |
284 | ### Comments about using with django-admin
285 |
286 | The use with **django-admin** is very like it: we only needs to create `ModelForm` for our models and in the `ModelAdmin` ([django documentation](https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin)) we set our form ([here is an example](https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.form)).
287 |
--------------------------------------------------------------------------------
/docs/widget/04-accept.md:
--------------------------------------------------------------------------------
1 | # Change Accept Formats
2 |
3 | When working with **HTML** `` element, we have an `accept=""` attribute that works defining the visible file types into the file picker dialog. An better, and complete, description of this attribute can be found at [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept).
4 |
5 | To define this attribute, with the `ImageUploaderWidget`, we can set the `attrs` property when instantiate the `ImageUploaderWidget`, like:
6 |
7 | ```python
8 | from django import forms
9 | from image_uploader_widget.widgets import ImageUploaderWidget
10 | from .models import CustomWidget
11 |
12 | class TestForm(forms.ModelForm):
13 | class Meta:
14 | widgets = {
15 | 'image': ImageUploaderWidget(attrs={ 'accept': 'image/png' }),
16 | }
17 | fields = '__all__'
18 | ```
19 |
--------------------------------------------------------------------------------
/docs/widget/05-htmx.md:
--------------------------------------------------------------------------------
1 | # Out of Box HTMX Support
2 |
3 | !!! warning "Version Information"
4 |
5 | Introduced at the 0.6.0 version.
6 |
7 | The widget, now, has out of box support for [HTMX](https://v1.htmx.org/). [see docs here](../htmx/01-widget.md).
8 |
--------------------------------------------------------------------------------
/docs/widget/06-custom-text-and-icons.md:
--------------------------------------------------------------------------------
1 | # Custom Text and Icons
2 |
3 | To customize the image uploader widget, you can set some variables (this feature is based on the issue [#77](https://github.com/inventare/django-image-uploader-widget/issues/77)). In this page we talk about how to, easy, change the texts and icons on that lib.
4 |
5 | For the widget, to customize the icon and the text we need to set some variables in the `ImageUploaderWidget` constructor, like it:
6 |
7 | ```python
8 | # ...
9 | class TestCustomForm(forms.ModelForm):
10 | class Meta:
11 | model = CustomWidget
12 | widgets = {
13 | 'image': ImageUploaderWidget(
14 | drop_icon="",
15 | drop_text="Custom Drop Text",
16 | empty_icon="",
17 | empty_text="Custom Empty Marker Text",
18 | ),
19 | }
20 | fields = '__all__'
21 | ```
22 |
23 | In this example, we set all four properties (`drop_icon`, `drop_text`, `empty_icon` and `empty_text`) for the widget. In the icons is possible to use the `django.shortcuts.render` ([REF](https://docs.djangoproject.com/en/4.1/topics/http/shortcuts/#render)) to renderize the icon from an HTML template.
24 |
25 | Another way for customize it is create an new widget class based on that and use it for your forms:
26 |
27 |
28 | ```python
29 | class MyCustomWidget(ImageUploaderWidget):
30 | drop_text = ""
31 | empty_text = ""
32 |
33 | def get_empty_icon(self):
34 | return render(...)
35 |
36 | def get_drop_icon(self):
37 | return render(...)
38 |
39 | class TestCustomForm(forms.ModelForm):
40 | class Meta:
41 | model = CustomWidget
42 | widgets = {
43 | 'image': MyCustomWidget()
44 | }
45 | fields = '__all__'
46 | ```
47 |
--------------------------------------------------------------------------------
/docs/widget/07-force-color-scheme.md:
--------------------------------------------------------------------------------
1 | # Force dark or light color scheme
2 |
3 | On the [ADR 0006](../development/architecture-decision-records/0006-why-enforce-light-theme-by-class.md) we discuted a way to force color scheme (`dark` or `light`) to ensure widget color scheme outside of `django-admin`. The two cases bellow is tested using [UI regression](../development/architecture-decision-records/0002-why-ui-regression-tests.md) tests.
4 |
5 |
6 | ## Light theme
7 |
8 | To enforce light widget, use `.iuw-light` class:
9 |
10 | ```html
11 |
12 | {{ form }}
13 |
14 | ```
15 |
16 | ## Dark theme
17 |
18 | To enforce dark widget, use `.iuw-dark` class:
19 |
20 | ```html
21 |
22 | {{ form }}
23 |
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/widget/08-custom-colors.md:
--------------------------------------------------------------------------------
1 | # Custom Colors
2 |
3 | To customize the image uploader widget colors you can use your own css file to override the css variables defined by the `image-uploader-widget.css`:
4 |
5 | ```scss
6 | body {
7 | --iuw-background: #FFF;
8 | --iuw-border-color: #CCC;
9 | --iuw-color: #333;
10 | --iuw-placeholder-text-color: #AAA;
11 | --iuw-placeholder-destak-color: #417690;
12 | --iuw-dropzone-background: rgba(255, 255, 255, 0.8);
13 | --iuw-image-preview-border: #BFBFBF;
14 | --iuw-image-preview-shadow: rgba(0, 0, 0, 0.3);
15 | --iuw-add-image-background: #EFEFEF;
16 | --iuw-add-image-color: #AAA;
17 | }
18 | ```
19 |
20 | **Observation**: To see better the variables name, check the css file at the GitHub repository: [here](https://github.com/inventare/django-image-uploader-widget/blob/main/image_uploader_widget/static/image_uploader_widget/css/image-uploader-widget.css).
21 |
--------------------------------------------------------------------------------
/docs/widget/images/form_demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inventare/django-image-uploader-widget/708f5a3614fd2d7644ca2dd730b051c63b9cd724/docs/widget/images/form_demo.png
--------------------------------------------------------------------------------
/docs/widget/specific-cases/01-crispy-forms.md:
--------------------------------------------------------------------------------
1 | # Usage with django-crispy-forms
2 |
3 | The `django-crispy-forms` not support, out of box, custom widgets and this is a problem of `django-crispy-forms`. Howere, inspired by [#203](https://github.com/inventare/django-image-uploader-widget/issues/203) issue, we decided to learn about the `crispy` package and document how to made custom widget work with it.
4 |
5 | ## FormHelper
6 |
7 | The easy way to make a custom widget to work with `django-crispy-forms` is to define a `FormHelper` inside the `Form` instance:
8 |
9 | ```python
10 | from crispy_forms.helper import FormHelper
11 | from crispy_forms.layout import Layout, Div
12 | from django import forms
13 |
14 | class TestCrispyRequiredForm(forms.ModelForm):
15 | def __init__(self, *args, **kwargs):
16 | super().__init__(*args, **kwargs)
17 | self.helper = FormHelper(self)
18 | self.helper.layout = Layout(
19 | Row(
20 | Column(
21 | Div('image', template='image_uploader_widget/widget/image_uploader_widget.html'),
22 | css_class='form-group col-md-6 mb-0',
23 | ),
24 | css_class='form-row'
25 | ),
26 | )
27 |
28 | class Meta:
29 | model = TestRequired
30 | widgets = {
31 | "image": ImageUploaderWidget(),
32 | }
33 | fields = "__all__"
34 | ```
35 |
36 | The definition of the `ImageUploaderWidget()` inside the `widgets` meta property is important to render the correct media styles:
37 |
38 | ```html
39 | {% load crispy_forms_tags %}
40 |
41 |
42 |
43 |
44 |
45 | Document
46 | {{ form.media }}
47 |
48 |
49 |
50 | {% crispy form %}
51 |
52 |
53 |
54 | ```
55 |
56 | And, for the end of this article, the generated HTML should appear like this:
57 |
58 | ```html
59 |