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. We think the 3 views handling built-in image model (i.e.,
galleryfield.image_views.BuiltInImageCreateView
,galleryfield.image_views.BuiltInImageListView
andgalleryfield.image_views.BuiltInImageCropView
were good examples of how to used them.
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
TODO