django-generic-images’s documentation

django-generic-images is a generic images pluggable django app.

This app provides image model (useful managers, methods and fields) that can be attached to any other Django model using generic relations. It also provides admin multi-image uploader (based on GearsUploader ) with client-side image resizing, animated progress bar and before-upload image previews.

Requirements: django 1.1 (or trunk).

django-composition is required if you want to use ImageCountField or UserImageCountField.

There is an image gallery app (django-photo-albums) based on django-generic-images.

Installation

$ pip install django-generic-images

or:

$ easy_install django-generic-images

or:

$ hg clone http://bitbucket.org/kmike/django-generic-images/
$ cd django-generic-images
$ python setup.py install

Then add ‘generic_images’ to your INSTALLED_APPS in settings.py and run

$ manage.py syncdb

If you want ImageCountField and UserImageCountField then follow installation instructions at http://bitbucket.org/daevaorn/django-composition/ to install django-composition.

For admin uploader to work generic_images folder from generic_images/media/ should be copied to project’s MEDIA_ROOT.

Usage

Generic Images

The idea is to provide an infrastructure for images that can be attached to any django model using generic relations.

Models

class generic_images.models.AttachedImage(*args, **kwargs)
Image model that can be attached to any other Django model using generic relations. It is simply non-abstract subclass of AbstractAttachedImage
class generic_images.models.AbstractAttachedImage(*args, **kwargs)

Bases: generic_images.models.ReplaceOldImageModel, generic_utils.models.GenericModelBase

Abstract Image model that can be attached to any other Django model using generic relations.

is_main
BooleanField. Whether the image is the main image for object. This field is set to False automatically for all images attached to same object if image with is_main=True is saved to ensure that there is only 1 main image for object.
order
IntegerField to support ordered image sets. On creation it is set to max(id)+1.
get_file_name(filename)
Returns file name (without path and extenstion) for uploaded image. Default is ‘max(pk)+1’. Override this in subclass or assign another functions per-instance if you want different file names (ex: random string).
get_order_in_album(reversed_ordering=True)
Returns image order number. It is calculated as (number+1) of images attached to the same content_object whose order is greater (if ‘reverse_ordering’ is True) or lesser (if ‘reverse_ordering’ is False) than image’s order.
get_upload_path(filename)

Override this in proxy subclass to customize upload path. Default upload path is /media/images/<user.id>/<filename>.<ext> or /media/images/common/<filename>.<ext> if user is not set.

<filename> is returned by get_file_name() method. By default it is probable id of new image (it is predicted as it is unknown at this stage).

next()
Returns next image for same content_object and None if image is the last.
previous()
Returns previous image for same content_object and None if image is the first.
content_object
Provides a generic relation to any object through content-type/object-id fields.
objects
Default manager of AttachedImageManager type.
user
A ForeignKey to associated user, for example user who uploaded image. Can be empty.
class generic_images.models.BaseImageModel(*args, **kwargs)

Simple abstract Model class with image field.

image
models.ImageField
get_upload_path(filename)
Override this to customize upload path
class generic_images.models.ReplaceOldImageModel(*args, **kwargs)

Bases: generic_images.models.BaseImageModel

Abstract Model class with image field. If the file for image is re-uploaded, old file is deleted.

Admin

generic_images.admin.AttachedImageAdminForm

Form for AttachedImage model to be used in inline admin

alias of _AttachedImageAdminForm

generic_images.admin.AttachedImagesInline

InlineModelAdmin for attached images. Adds multi-image uploader with progress bar, before-upload image previews and client-side resizing. Uploader is based on GearsUploader (http://bitbucket.org/kmike/gearsuploader/) project.

To make this work copy generic_images folder from generic_images/media/ to your MEDIA_ROOT. Then use AttachedImagesInline class for you inlines:

#admin.py

from django.contrib import admin
from generic_images.admin import AttachedImagesInline

class MyModelAdmin(admin.ModelAdmin):
    inlines = [AttachedImagesInline]

admin.site.register(MyModel, MyModelAdmin)

Just before standard formset the following uploader is displayed:

_images/admin-with-formset.png

Gears plugin is here

_images/admin-nogears.png

Message is displayed if Gears plugin is not available

_images/admin-previews.png

User can select several files at once using Ctrl or Shift keys (Cmd on Mac) in standard OS file selection dialog. He can also remove images from selection by clicking on thumbnails. Several files can also be selected by opening file selection dialog several times.

_images/admin-uploading.png

User presses ‘Upload’ button and upload process begins

By default the ‘Resize ..’ checkbox is unchecked and the input field is blank. If it is unchecked then images are not resized before uploading. User can check it and set his max image width.

In order to set the default value and mark the checkbox as checked by default create customized AttachedImagesInline class using attachedimages_inline_factory() function. This function can also be used to change uploader language (language auto-discovering is not implemented):

from generic_images.admin import attachedimages_inline_factory

class MyModelAdmin(admin.ModelAdmin):
    inlines = [attachedimages_inline_factory(lang='ru', max_width=1024)]

admin.site.register(MyModel, MyModelAdmin)

alias of _AttachedImagesInline

generic_images.admin.attachedimage_form_factory(lang='en')
Returns ModelForm class to be used in admin. ‘lang’ is the language for GearsUploader (can be ‘en’ and ‘ru’ at the moment).
generic_images.admin.attachedimages_inline_factory(lang='en', max_width='')
Returns InlineModelAdmin for attached images. ‘lang’ is the language for GearsUploader (can be ‘en’ and ‘ru’ at the moment). ‘max_width’ is default resize width parameter to be set in widget.

Managers

class generic_images.managers.AttachedImageManager(*args, **kwargs)

Bases: generic_utils.managers.GenericModelManager

Manager with helpful functions for attached images

get_main_for(model)
Returns main image for given model
class generic_images.managers.ImagesAndUserManager(*args, **kwargs)

Useful manager for models that have AttachedImage (or subclass) field and ‘injector=GenericIngector()’ manager.

select_with_main_images(limit=None, **kwargs)
Select all objects with filters passed as kwargs. For each object it’s main image instance is accessible as object.main_image. Results can be limited using limit parameter. Selection is performed using only 2 or 3 sql queries.

Forms

class generic_images.forms.AttachedImageForm

Bases: django.forms.models.ModelForm

Simple form for AttachedImage model with image and caption fields.

Fields for denormalisation

django-generic-images provides fields for storing information about attached images count. Value is stored in model that images are attached to. Value is updated automatically when image is saved or deleted. Access to this value is much faster than additional “count()” queries.

class generic_images.fields.ImageCountField(native=None)

Field with model’s attached images count. Value of this field is updated automatically when image is added or removed. Access to this field doesn’t produce additional ‘select count(*)’ query, data is stored in table.

Example 1:

from generic_images.fields import ImageCountField

class MyModel1(models.Model):
    #... fields definitions
    image_count = ImageCountField()

Example 2:

class MyModel2(models.Model):
    #... fields definitions
    image_count = ImageCountField(native=models.IntegerField(u'MyModel2 Images count', default=0))
class generic_images.fields.UserImageCountField(native=None, user_attr='user')

Field that should be put into user’s profile (AUTH_PROFILE_MODULE). It will contain number of images that are attached to corresponding User.

This field is useful when you want to use something like ImageCountField for User model. It is not possible to add a field to User model without duck punching (monkey patching). UserImageCountField should be put into user’s profile (same model as defined in AUTH_PROFILE_MODULE). It will contain number of images that are attached to corresponding User. FK attribute to User model is considered 'user' by default, but this can be overrided using user_attr argument to UserImageCountField constructor. As with ImageCountField, UserImageCountField constructor accepts also native argument - an underlying field.

generic_images.fields.force_recalculate(obj)

Recalculate all ImageCountField and UserImageCountField fields in object obj.

This should be used if auto-updating of these fields was disabled for some reason.

To disable auto-update when saving AttachedImage instance (for example when you need to save a lot of images and want to recalculate denormalised values only after all images are saved) use this pattern:

image = AttachedImage(...)
image.send_signal = False
image.save()

Context processors

generic_images.context_processors.thumbnail_types(request)
A context processor to add possible thumbnail sizes to the current Context. Useful for managing possible sorl.thumbnails thumbnail’s sizes

Generic Utils

Pluggable app utils

class generic_utils.app_utils.PluggableSite(instance_name, app_name, queryset=None, object_regex=None, lookup_field=None, extra_context=None, template_object_name='object', has_edit_permission=<function <lambda> at 0x1d75570>, context_processors=None, object_getter=None)

Base class for reusable apps. The approach is similar to django AdminSite. For usage case please check photo_albums app.

check_permissions(request, object)
get_common_context(obj)
make_regex(url)

Make regex string for PluggableSite urlpatterns: prepend url with parent object’s url and app name.

See also: http://code.djangoproject.com/ticket/11559.

patterns()

This method should return url patterns (like urlpatterns variable in urls.py). It is helpful to construct regex with make_regex() method. Example:

return patterns('photo_albums.views',
                    url(
                        self.make_regex('/'),
                        'show_album',
                        {'album_site': self},
                        name = 'show_album',
                    ),
               )
reverse(url, args=None, kwargs=None)
Reverse an url taking self.app_name in account
urls

Use it in urls.py. Example:

urlpatterns += patterns('', url(r'^my_site/', include(my_pluggable_site.urls)),)
generic_utils.app_utils.get_site_decorator(site_param='site', obj_param='obj', context_param='context')

It is a function that returns decorator factory useful for PluggableSite views. This decorator factory returns decorator that do some boilerplate work and make writing PluggableSite views easier. It passes PluggableSite instance to decorated view, retreives and passes object that site is attached to and passes common context. It also passes and all the decorator factory’s keyword arguments.

For example usage please check photo_albums.views.

Btw, this decorator seems frightening for me. It feels that “views as PluggableSite methods” approach can easily make this decorator obsolete. But for now it just works.

generic_utils.app_utils.simple_getter(queryset, object_regex=None, lookup_field=None)
Returns simple object_getter function for use with PluggableSite. It takes ‘queryset’ with QuerySet or Model instance, ‘object_regex’ with url regex and ‘lookup_field’ with lookup field.

Models

class generic_utils.models.GenericModelBase(*args, **kwargs)

Abstract base class for models that will be attached using generic relations.

object_id
A PositiveIntegerField containing the primary key of the object the model is attached to.
content_type
A ForeignKey to ContentType; this is the type of the object the model is attached to.
content_object
A GenericForeignKey attribute pointing to the object the comment is attached to. You can use this to get at the related object (i.e. my_model.content_object). Since this field is a GenericForeignKey, it’s actually syntactic sugar on top of two underlyin attributes, described above.
injector
GenericInjector manager.
objects
Default manager. It is of type GenericModelManager.
class generic_utils.models.TrueGenericModelBase(*args, **kwargs)
It is similar to GenericModelBase but with TextField object_id instead of PositiveIntegerField.

Generic relation helpers

class generic_utils.managers.GenericInjector(fk_field='object_id', ct_field='content_type', *args, **kwargs)

RelatedInjector but for GenericForeignKey’s. Manager for selecting all generic-related objects in one (two) SQL queries. Selection is performed for a list of objects. Resulting data is aviable as attribute of original model. Only one instance per object can be selected. Example usage: select (and make acessible as user.avatar) all avatars for a list of user when avatars are AttachedImage’s attached to User model with is_main=True attributes.

Example:

from django.contrib.auth.models import User
from generic_images.models import AttachedImage

users = User.objects.all()[:10]
AttachedImage.injector.inject_to(users, 'avatar', is_main=True)

# i=0..9: users[i].avatar is AttachedImage objects with is_main=True.
# If there is no such AttachedImage (user doesn't have an avatar),
# users[i].avatar is None
For this example 2 or 3 sql queries will be executed:
  1. one query for selecting 10 users,
  2. one query for selecting all avatars (images with is_main=True) for selected users
  3. and maybe one query for selecting content-type for User model

One can reuse GenericInjector manager for other models that are supposed to be attached via generic relationship. It can be considered as an addition to GFKmanager and GFKQuerySet from djangosnippets for different use cases.

inject_to(objects, field_name, get_inject_object=<function <lambda> at 0x1a4a4b0>, **kwargs)
objects is an iterable. Images (or other generic-related model instances)
will be attached to elements of this iterable.

field_name is the attached object attribute name

get_inject_object is a callable that takes object in objects iterable.
Image will be available as an attribute of the result of get_injector_object(object). Images attached to get_injector_object(object) will be selected.

All other kwargs will be passed as arguments to queryset filter function.

Example: you have a list of comments. Each comment has ‘user’ attribute. You want to fetch 10 comments and their authors with avatars. Avatars should be accessible as user.avatar:

comments = Comment.objects.all().select_related('user')[:10]
AttachedImage.injector.inject_to(comments, 'avatar', lambda obj: obj.user, is_main=True)
class generic_utils.managers.GenericModelManager(*args, **kwargs)

Manager with for_model method.

for_model(model, content_type=None)
Returns all objects that are attached to given model
class generic_utils.managers.RelatedInjector(fk_field='object_id', *args, **kwargs)

Manager that can emulate select_related fetching reverse relations using 1 additional SQL query.

inject_to(objects, field_name, get_inject_object=<function <lambda> at 0x1a4a3f0>, **kwargs)
objects is an iterable. Related objects
will be attached to elements of this iterable.

field_name is the attached object attribute name

get_injector_object is a callable that takes object in objects
iterable. Related objects will be available as an attribute of the result of get_inject_object(obj). It is assumed that fk_field points to get_inject_object(obj).

All other kwargs will be passed as arguments to queryset filter function.

For example, we need to prefetch user profiles when we display a list of comments:

# models.py
class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    info = models.CharField(max_length=100)
    objects = models.Manager()
    injector = RelatedInjector(fk_field='user')

# views.py
def show_comments(request, obj_id):
    ...
    comments = list(Comment.objects.for_model(obj).select_related('user'))
    UserProfile.injector.inject_to(comments, '_profile_cache',
                                   lambda comment: comment.user)

    return direct_to_template('comment_list.html', {'comments': comments})

# in comment_list.html
{% for comment in comments %}
    <h3>{{ comment.user }}</h3>
    <h4>{{ comment.user.get_profile.info }}</h4>
    {{ comment.comment|linebreaks }}
{% endfor %}

comment.user attribute will be selected using select_related and comment.user._profile_cache (exposed by get_profile method) will be selected by our injector. So there will be only 2 SQL queries for selecting all comments with users and user profiles.

Template tag helpers

exception generic_utils.templatetags.InvalidParamsError
Custom exception class to distinguish usual TemplateSyntaxErrors and validation errors for templatetags introduced by validate_params function
generic_utils.templatetags.validate_params(bits, arguments_count, keyword_positions)
Raises exception if passed params (bits) do not match signature. Signature is defined by arguments_count (acceptible number of params) and keyword_positions (dictionary with positions in keys and keywords in values, for ex. {2:’by’, 4:’of’, 5:’type’, 7:’as’}).

Test helpers

class generic_utils.test_helpers.ViewTest(methodName='runTest')

TestCase for view testing

check_login_required(url_name, kwargs=None, current_app=None)
Check if response is a redirect to login page (ignoring GET variables)
check_url(url_name, status=200, kwargs=None, current_app=None)
check_url a URL and require a specific status code before proceeding