Merge branch 'hotfix/oberlin_references' into taggit
[philo.git] / philo / utils / __init__.py
1 from django.db import models
2 from django.contrib.contenttypes.models import ContentType
3 from django.core.paginator import Paginator, EmptyPage
4
5
6 def fattr(*args, **kwargs):
7         """
8         Returns a wrapper which takes a function as its only argument and sets the key/value pairs passed in with kwargs as attributes on that function. This can be used as a decorator.
9         
10         Example::
11         
12                 >>> from philo.utils import fattr
13                 >>> @fattr(short_description="Hello World!")
14                 ... def x():
15                 ...     pass
16                 ... 
17                 >>> x.short_description
18                 'Hello World!'
19         
20         """
21         def wrapper(function):
22                 for key in kwargs:
23                         setattr(function, key, kwargs[key])
24                 return function
25         return wrapper
26
27
28 ### ContentTypeLimiters
29
30
31 class ContentTypeLimiter(object):
32         def q_object(self):
33                 return models.Q(pk__in=[])
34         
35         def add_to_query(self, query, *args, **kwargs):
36                 query.add_q(self.q_object(), *args, **kwargs)
37
38
39 class ContentTypeRegistryLimiter(ContentTypeLimiter):
40         """Can be used to limit the choices for a :class:`ForeignKey` or :class:`ManyToManyField` to the :class:`ContentType`\ s which have been registered with this limiter."""
41         def __init__(self):
42                 self.classes = []
43         
44         def register_class(self, cls):
45                 """Registers a model class with this limiter."""
46                 self.classes.append(cls)
47         
48         def unregister_class(self, cls):
49                 """Unregisters a model class from this limiter."""
50                 self.classes.remove(cls)
51         
52         def q_object(self):
53                 contenttype_pks = []
54                 for cls in self.classes:
55                         try:
56                                 if issubclass(cls, models.Model):
57                                         if not cls._meta.abstract:
58                                                 contenttype = ContentType.objects.get_for_model(cls)
59                                                 contenttype_pks.append(contenttype.pk)
60                         except:
61                                 pass
62                 return models.Q(pk__in=contenttype_pks)
63
64
65 class ContentTypeSubclassLimiter(ContentTypeLimiter):
66         """
67         Can be used to limit the choices for a :class:`ForeignKey` or :class:`ManyToManyField` to the :class:`ContentType`\ s for all non-abstract models which subclass the class passed in on instantiation.
68         
69         :param cls: The class whose non-abstract subclasses will be valid choices.
70         :param inclusive: Whether ``cls`` should also be considered a valid choice (if it is a non-abstract subclass of :class:`models.Model`)
71         
72         """
73         def __init__(self, cls, inclusive=False):
74                 self.cls = cls
75                 self.inclusive = inclusive
76         
77         def q_object(self):
78                 contenttype_pks = []
79                 def handle_subclasses(cls):
80                         for subclass in cls.__subclasses__():
81                                 try:
82                                         if issubclass(subclass, models.Model):
83                                                 if not subclass._meta.abstract:
84                                                         if not self.inclusive and subclass is self.cls:
85                                                                 continue
86                                                         contenttype = ContentType.objects.get_for_model(subclass)
87                                                         contenttype_pks.append(contenttype.pk)
88                                         handle_subclasses(subclass)
89                                 except:
90                                         pass
91                 handle_subclasses(self.cls)
92                 return models.Q(pk__in=contenttype_pks)
93
94
95 ### Pagination
96
97
98 def paginate(objects, per_page=None, page_number=1):
99         """
100         Given a list of objects, return a (``paginator``, ``page``, ``objects``) tuple.
101         
102         :param objects: The list of objects to be paginated.
103         :param per_page: The number of objects per page.
104         :param page_number: The number of the current page.
105         :returns tuple: (``paginator``, ``page``, ``objects``) where ``paginator`` is a :class:`django.core.paginator.Paginator` instance, ``page`` is the result of calling :meth:`Paginator.page` with ``page_number``, and objects is ``page.objects``. Any of the return values which can't be calculated will be returned as ``None``.
106         
107         """
108         try:
109                 per_page = int(per_page)
110         except (TypeError, ValueError):
111                 # Then either it wasn't set or it was set to an invalid value
112                 paginator = page = None
113         else:
114                 # There also shouldn't be pagination if the list is too short. Try count()
115                 # first - good chance it's a queryset, where count is more efficient.
116                 try:
117                         if objects.count() <= per_page:
118                                 paginator = page = None
119                 except AttributeError:
120                         if len(objects) <= per_page:
121                                 paginator = page = None
122         
123         try:
124                 return paginator, page, objects
125         except NameError:
126                 pass
127         
128         paginator = Paginator(objects, per_page)
129         try:
130                 page_number = int(page_number)
131         except:
132                 page_number = 1
133         
134         try:
135                 page = paginator.page(page_number)
136         except EmptyPage:
137                 page = None
138         else:
139                 objects = page.object_list
140         
141         return paginator, page, objects