Customization
Although the built-in image model, along with the image processing views demonstrated in the demo, might have met the basic needs for some apps, advance users might require more features in terms of image storage, permission control, template inheritance, and Image model (fields) customization.
Before that, we need to address how this app is working.
Image model and views
Until now, Django didn’t supply any type of Field which can store unknown
length of images or files. However, the introduction of JsonField
(in Django 3) made it possible to store the pks
of image model instances as a Json list in a customized JsonField
.
That’s the galleryfield.fields.GalleryField
.
To facilitate the access of image model instances, we need to map the pks
to the actual image model instances. So, we introduced an optional string
param target_model
, the
app_label.model_name
of the image model, defaults to garlleryfield.BuiltInGalleryImage
.
Following that, there should be three basic views to handle image model instances: Create (the upload operation), List (the fetch operation) and Update (the crop operation).
The next problem is, when customizing, image models varies because
they can have more fields besides an ImageField
and an User
field,
e.g., a DatetimeField
to store the upload time of the image.
And, we will never being able to know how other developers will name those fields.
Inevitably, developers will have to write those 3 views for their target_models
.
Moreover, the widget should be able to know where to find those views, i.e.,
it need to know the URLs of those views.
To solve the problems, We finally introduced a class-based views for
each operation, and specified a naming rule
for the URL names of the 3 views for a target_model
.
Therefore, a model level customization (for image model) involves:
A valid target image model
Image model is where we actually save the image uploaded. To be a valid target image model, it need to meet one of the following 2 requirements:
It has a
django.db.models.ImageField
namedimage
.It has a
django.db.models.ImageField
which not namedimage
but the field can be accessed by aclassmethod
namedget_image_field
. For example, in an app namedmy_app
, we can have the following valid target image model inmy_app.models.py
:
class MyImage(models.Model):
photo = models.ImageField(
upload_to="my_images", storage=default_storage, verbose_name=_("Image"))
creator = models.ForeignKey(
settings.AUTH_USER_MODEL, null=False, blank=False,
verbose_name=_('Creator'), on_delete=models.CASCADE)
creation_time = models.DateTimeField(default=now(), blank=False)
@classmethod
def get_image_field(cls):
return cls._meta.get_field("photo")
Note
In the example above, when defining the get_image_field()
,
we can’t simply return cls.photo
because it
returns a django.db.models.fields.files.ImageFieldFile
object instead of a django.db.models.ImageField
object.
The galleryfield.models.BuiltInGalleryImage
is using the first style (
with target_model="garlleryfield.BuiltInGalleryImage"
).
However, if you don’t want to do much change to your existing models
(e.g., avoiding migrations of existing model),
the second style is more sounding.
In the following, we will use the above model in a galleryfield.fields.GalleryField
with target_model = "my_app.MyImage"
.
Three views for handling the image model objects
Three views for handling the image model objects (upload, fetch and crop). We provided 3 class-based-views for these views to enable the built-in views.
See Built-in Image handling Views for more detail. We hope users can subclass the views above without much coding work. Besides
demo_custom.image_views.CustomImageCreateView
,demo_custom.image_views.CustomImageListView
anddemo_custom.image_views.CustomImageCropView
, the 3 views handling built-in image model (i.e.,galleryfield.image_views.BuiltInImageCreateView
,galleryfield.image_views.BuiltInImageListView
andgalleryfield.image_views.BuiltInImageCropView
were also good examples of how to used them.
Note
Since version 2.0.1, image model customization requires:
Subclass of
ImageCreateView
must implement acreate_instance_from_form()
method.Subclass of
ImageCropView
must implement acreate_cropped_instance_from_form()
method.
Naming rule for URLs of image handling views
Generally, the widget need to know the URLs for image handling views (see GalleryWidget docs). We may specify the explicitly specify the URL names manually in the widget of gallery modelform fields.
Alternatively, we can also let the widget infer what URLs it should use for those views, by
following a naming rules for those views in URL_CONF
.
For a valid image model, the default URL names for the image handling views are the lower cased
app_label-model_name
, suffixed by -upload
, -fetch
and -crop
,
respectively.
For example, if you have a target_model
named my_app.MyImage
, then the default
URL names for the image handling views are my_app-myimage-upload
, my_app-myimage-fetch
and
my_app-myimage-crop
. In this way, you don’t need to specify in the GalleryWidget
the param upload_url
and fetch_url
, and no need to specify the crop_url_name
in each of the 3 class-based views.
Until now, we were talking about image model instance handling.
GalleryField rendering customization
Now we turn to the customization of gallery model.
Back to the demo, when dealing with the gallery model instance, there isn’t much magic about
demo.views.GalleryCreateView
and demo.views.GalleryUpdateView
.
Here, we need to address demo.views.GalleryDetailView
, on how it renders the
galleryfield.fields.GalleryField
.
With my_app.MyImage
in previous example as the target_model
,
we can have a gallery model named MyGallery
:
class MyGallery(models.Model):
album = GalleryField(target_model="my_app.MyImage", verbose_name=_('My photos'))
owner = models.ForeignKey(
settings.AUTH_USER_MODEL, null=False, blank=False,
verbose_name=_('Owner'), on_delete=models.CASCADE)
By subclassing django.views.generic.detail.DetailView
, we can have a gallery detail view like:
from django.views.generic.detail import DetailView
from my_app.models import MyGallery
class MyGalleryDetailView(DetailView):
model = MyGallery
Then we add a template file named mygallery_detail.html
to folder my_app/templates/my_app/
,
with the following code block:
{% extends 'base.html' %}
{% load static %}
...
{% for obj in object.album.objects.all %}
<img src="{{ obj.photo.url}}">
{% endfor %}
...
And add the URL of the view:
from my_apps import views
urlpatterns = [
...
path('album-detail/<int:pk>',
views.MyGalleryDetailView.as_view(), name='my_gallery-detail'),
]
Then we can navigate to see the images in a specific gallery.
As you might guess from the first line in the template snippet,
the GalleryField
provide a Queryset
API for the image model
instances it related to. No wonder, you can do the following:
>>> first_gallery = MyGallery.objects.first()
>>> photos_in_first_gallery = first_gallery.album.objects.all()
>>> photos_before_2021 = photos_in_first_gallery.filter(creation_time__lt=datetime(2021, 01, 01))
More over, the demo provides an example of how to render
the field using sorl.thumbnail
and Blueimp Gallery
package.
Finally, it’s your opportunity to show your skills on customizing the gallery/album frontend, which is beyond the scope of this package.
Template customization
Just like overriding URLs (see example in GalleryWidget docs), widget template can also be customized after after the form is instantiated. The following widget templates can be overridden:
template
, defaults togalleryfield/widget.html
. It is the template used to render the whole widget.upload_template
, defaults togalleryfield/upload_template.html
. It is the template used to render the table of images which were added but have not been uploaded yet.download_template
, defaults togalleryfield/download_template.html
. It is the template used to render the table of images which have been uploaded.
You can override the templates using the
Django template language .
Notice that there are mixed using of Django template language and
JavaScript tmpl in
galleryfield/upload_template.html
and galleryfield/download_template.html
. As both templating languages
used {%
and %}
templatetags, to avoid conflict, we replaced the {%
and %}
used by tmpl
to {% templatetag openblock %}
and {% templatetag closeblock %}
, respectively.
See Django templatetags for reference.
Serializer customization
By default, the rendered download_template
only show 3 fields, i.e., thumbnailUrl
, name
and size
of the images.
If you want to display more fields in the UI, you can add a serialize_extra()
method to target_model
. Notice that
correct rendering more fields also requires appropriate template customization.
See demo_custom.models.CustomImage
and template demo_custom/custom_download_template.html
for an example of how
to add an added_datetime
field in the rendered UI.
URLs customization
The download URL and crop URL can be customized by adding get_image_url()
and get_crop_url()
method to target_model
. In demo_custom
app, we customized the download URL so that
django-sendfile2
can be used to restrict user access in visiting images. See model customization demo_custom.models.CustomImage
and
the view demo_custom.image_views.image_download
.