Commits

Anonymous committed d101517

Better documentation for new features.

Comments (0)

Files changed (9)

docs/img/admin-initial.png

Added
New image

docs/img/admin-nogears.png

Added
New image

docs/img/admin-previews.png

Added
New image

docs/img/admin-uploading.png

Added
New image

docs/img/admin-with-formset.png

Added
New image
    sphinx-quickstart on Fri Sep 18 02:13:39 2009.
    You can adapt this file completely to your liking, but it should at least
    contain the root `toctree` directive.
-   
+
 .. toctree::
    :maxdepth: 2
-   
-=====================================   
+
+=====================================
 django-generic-images's documentation
 =====================================
 
 django-generic-images is a generic images pluggable django app.
 
-This app provides image model (with useful managers, methods and fields) 
+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 <http://bitbucket.org/kmike/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.
+`django-composition <http://bitbucket.org/daevaorn/django-composition/>`_ is
+required if you want to use
+:class:`~generic_images.fields.ImageCountField` or
+:class:`~generic_images.fields.UserImageCountField`.
+
+There is an image gallery app
+(`django-photo-albums <http://bitbucket.org/kmike/django-photo-albums/>`_)
+based on django-generic-images.
 
 ************
 Installation
 ************
 ::
 
-	$ pip install django-generic-images
+    $ 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
+    $ 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
+    $ manage.py syncdb
 
-If you want ``ImageCountField`` and ``UserImageCountField`` then follow  
+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
 *****
     :show-inheritance:
     :members:
 
+Admin
+-----
+
+.. autoclass:: generic_images.admin.AttachedImagesInline()
+    :show-inheritance:
+
+
+.. autoclass:: generic_images.admin.AttachedImageAdminForm()
+    :show-inheritance:
+
+
 Managers
 --------
 
 .. automodule:: generic_images.managers
-    
+
 .. autoclass:: generic_images.managers.AttachedImageManager
-    :show-inheritance:    
+    :show-inheritance:
     :exclude-members: get_for_model
     :members:
 
 .. automodule:: generic_images.forms
 
 .. autoclass:: generic_images.forms.AttachedImageForm()
-    :show-inheritance:    
+    :show-inheritance:
 
-	
+
 Fields for denormalisation
 --------------------------
 
 .. automodule:: generic_images.fields
-	:members:
-	
+    :members:
+
 
 Context processors
 ------------------
 
 .. automodule:: generic_images.context_processors
-	:members:
-	
-	
+    :members:
+
+
 Generic Utils
-=============	
+=============
 
 
 Pluggable app utils
--------------------	
+-------------------
 .. automodule:: generic_utils.app_utils
-	:members:
-	:undoc-members:
+    :members:
+    :undoc-members:
 
 
 Models
 
 .. autoclass:: generic_utils.models.GenericModelBase
     :members:
-    
+
 .. autoclass:: generic_utils.models.TrueGenericModelBase
 
-	
+
 Generic relation helpers
 ------------------------
 
 .. automodule:: generic_utils.managers
-	:members:
-	:undoc-members:
-		
-	
+    :members:
+    :undoc-members:
+
+
 
 Template tag helpers
 --------------------
-	
+
 .. automodule:: generic_utils.templatetags
-	:members:
-	:undoc-members:
-		
+    :members:
+    :undoc-members:
+
 
 Test helpers
 ------------
 
 .. automodule:: generic_utils.test_helpers
-	:members:
-			
+    :members:

generic_images/admin.py

 
 
 class AttachedImagesInline(GenericTabularInline):
-    ''' InlineAdmin for attached images.
+    ''' InlineModelAdmin for attached images.
         Adds multi-image uploader with progress bar, before-upload image
         previews and client-side resizing. Uploader is based
-        on GearsUploader project.
+        on GearsUploader (http://bitbucket.org/kmike/gearsuploader/) project.
 
-        By default images are not resized. In order to enable resizing
-        subclass AttachedImageInline and set ``max_width`` parameter::
+        To make this work copy ``generic_images`` folder from
+        ``generic_images/media/`` to your ``MEDIA_ROOT``. Then use
+        AttachedImagesInline class for you inlines::
 
-            class MyImageInline(AttachedImagesInline):
+            #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:
+
+        .. image:: img/admin-with-formset.png
+
+        Gears plugin is here
+
+        .. image:: img/admin-nogears.png
+
+        Message is displayed if Gears plugin is not available
+
+        .. image:: img/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.
+
+        .. image:: img/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 subclass AttachedImagesInline and set ``max_width`` parameter::
+
+            class MyImagesInline(AttachedImagesInline):
                 max_width=600
 
+            class MyModelAdmin(admin.ModelAdmin):
+                inlines = [MyImagesInline]
+
+            admin.site.register(MyModel, MyModelAdmin)
+
     '''
     model = AttachedImage
     form = AttachedImageAdminForm

generic_utils/app_utils.py

 from django.http import Http404
 from django.core.urlresolvers import reverse
 from django.template import RequestContext
-from django.db import models 
+from django.db import models
 from django.db.models.query import QuerySet
+from django.utils.functional import wraps
 
 def get_site_decorator(site_param='site', obj_param='obj', context_param='context'):
-    ''' Returns decorator useful for PluggableSite views. '''
+    ''' 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.
+    '''
     def site_method(**extra_params):
         def decorator(fn):
+            @wraps(fn)
             def wrapper(request, **kwargs):
                 try:
                     site = kwargs.pop(site_param)
                 except KeyError:
                     raise ValueError("'%s' parameter must be passed to "
                                      "decorated view (%s)" % (site_param, fn))
-                          
+
                 # Pop parameters to be passed to actual view function.
-                params={} 
+                params={}
                 for key in extra_params:
                     value = kwargs.pop(key, extra_params[key])
                     params.update({key:value})
-                    
-                # Now there are only site.object_getter lookup parameters in 
+
+                # Now there are only site.object_getter lookup parameters in
                 # kwargs. Get the object and compute common request context.
                 try:
                     obj = site.object_getter(**kwargs)
                 except models.ObjectDoesNotExist:
                     raise Http404("Base object does not exist.")
-                
+
                 context = site.get_common_context(obj)
-                context_instance = RequestContext(request, context, 
+                context_instance = RequestContext(request, context,
                                        processors=site.context_processors)
-                
+
                 # pass site name, the object and common request to decorated view
                 params.update({
-                                site_param:site, 
+                                site_param:site,
                                 obj_param: obj,
                                 context_param: context_instance
                              })
-                
-                # TODO: write correct decorator that keeps docstrings, etc.
-                return fn(request, **params)  
+                return fn(request, **params)
             return wrapper
         return decorator
     return site_method
 
 
 def simple_getter(queryset, object_regex=None, lookup_field=None):
-    ''' Returns simple object_getter function for use with PluggableSite. 
+    ''' 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.
     '''
     object_regex = object_regex or r'\d+'
     lookup_field = lookup_field or 'pk'
-    
+
     if isinstance(queryset, models.Model):
         qs = queryset._default_manager.all()
     elif isinstance(queryset, QuerySet) or isinstance(queryset, models.Manager):
         qs = queryset
-        
+
     def object_getter(object_id):
-        return qs.get(**{lookup_field: object_id}) 
+        return qs.get(**{lookup_field: object_id})
     object_getter.regex = "(?P<object_id>%s)" % object_regex
-    
+
     return object_getter
-        
-        
+
+
 class PluggableSite(object):
-    ''' Base class for reusable apps. 
+    ''' Base class for reusable apps.
         The approach is similar to django AdminSite.
         For usage case please check photo_albums app.
-   '''        
+   '''
 
-    def __init__(self, 
-                 instance_name, 
+    def __init__(self,
+                 instance_name,
                  app_name,
                  queryset = None,
                  object_regex = None,
                  lookup_field = None,
-                 extra_context=None, 
+                 extra_context=None,
                  template_object_name = 'object',
                  has_edit_permission = lambda request, obj: True,
-                 context_processors = None, 
+                 context_processors = None,
                  object_getter = None):
-                        
+
         self.instance_name = instance_name
         self.extra_context = extra_context or {}
         self.app_name = app_name
             self.object_getter = object_getter
         else:
             raise ValueError('Please provide object_getter or queryset.')
-                
-        
+
+
     def reverse(self, url, args=None, kwargs=None):
         ''' Reverse an url taking self.app_name in account '''
-        return reverse("%s:%s" % (self.instance_name, url,), 
+        return reverse("%s:%s" % (self.instance_name, url,),
                         args=args,
-                        kwargs=kwargs, 
+                        kwargs=kwargs,
                         current_app = self.app_name)
-                
-        
+
+
     def check_permissions(self, request, object):
         if not self.has_edit_permission(request, object):
             raise Http404('Not allowed')
-    
+
 
     def get_common_context(self, obj):
         context = {self.template_object_name: obj, 'current_app': self.app_name}
         if (self.extra_context):
             context.update(self.extra_context)
         return context
-        
-                    
-    def make_regex(self, url):  
-        ''' 
+
+
+    def make_regex(self, 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.
-        '''          
+        '''
         return r"^%s/%s%s$" % (self.object_getter.regex, self.app_name, url)
-    
-    
+
+
     def patterns(self):
-        ''' This method should return url patterns (like urlpatterns variable in 
+        ''' This method should return url patterns (like urlpatterns variable in
             :file:`urls.py`). It is helpful to construct regex with
             :meth:`~generic_utils.app_utils.PluggableSite.make_regex` method.
             Example::
-            
-                return patterns('photo_albums.views',                                
+
+                return patterns('photo_albums.views',
                                     url(
                                         self.make_regex('/'),
                                         'show_album',
                                     ),
                                )
         '''
-        
+
         raise NotImplementedError
 
 
     @property
     def urls(self):
-        '''  
+        '''
             Use it in :file:`urls.py`.
             Example::
-            
+
                 urlpatterns += patterns('', url(r'^my_site/', include(my_pluggable_site.urls)),)
         '''
         return self.patterns(), self.app_name, self.instance_name

generic_utils/test_helpers.py

     '''
     TestCase for view testing
     '''
-                        
+
     def setUp(self):
-        """This method is automatically called by the Django test framework."""
         self.client = Client()
 
     def check_url(self, url_name, status=200, kwargs=None, current_app=None):
         response = self.client.get(url)
         self.failUnlessEqual(response.status_code, status)
         return response
-    
+
     def check_login_required(self, url_name, kwargs=None, current_app=None):
         """ Check if response is a redirect to login page (ignoring GET variables) """
         url = reverse(url_name, kwargs=kwargs, current_app=current_app)
         response = self.client.get(url)
-        
+
         #remove GET variables, for example '?next=..'
         scheme, netloc, path, query, fragment = urlsplit(response['Location'])
         response['Location'] = urlunsplit(('http', 'testserver', path, None, None))
-                
+
         self.assertRedirects(response, getattr(settings, 'LOGIN_URL', '/accounts/login/'))
         return response
-