From: Stephen Burrows Date: Wed, 11 May 2011 22:08:52 +0000 (-0400) Subject: Split utils into entities (i.e. AttributeMappers) and other utils. Added documentatio... X-Git-Tag: philo-0.9~12^2~17 X-Git-Url: http://git.ithinksw.org/philo.git/commitdiff_plain/b97aa6632f51c6c1737768bde749dcd99bc5f1e6 Split utils into entities (i.e. AttributeMappers) and other utils. Added documentation for the other utils and prepped for documenting the AttributeMappers. --- diff --git a/docs/index.rst b/docs/index.rst index 36470fb..3575ddb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,6 +17,7 @@ Contents: middleware signals validators + utilities Indices and tables ================== diff --git a/docs/utilities.rst b/docs/utilities.rst new file mode 100644 index 0000000..95df593 --- /dev/null +++ b/docs/utilities.rst @@ -0,0 +1,11 @@ +Utilities +========= + +.. automodule:: philo.utils + :members: + +AttributeMappers +++++++++++++++++ + +.. automodule:: philo.utils.entities + :members: diff --git a/philo/models/base.py b/philo/models/base.py index 7d78383..c361bdc 100644 --- a/philo/models/base.py +++ b/philo/models/base.py @@ -11,7 +11,8 @@ from mptt.models import MPTTModel, MPTTModelBase, MPTTOptions from philo.exceptions import AncestorDoesNotExist from philo.models.fields import JSONField from philo.signals import entity_class_prepared -from philo.utils import ContentTypeRegistryLimiter, ContentTypeSubclassLimiter, AttributeMapper, TreeAttributeMapper +from philo.utils import ContentTypeRegistryLimiter, ContentTypeSubclassLimiter +from philo.utils.entities import AttributeMapper, TreeAttributeMapper from philo.validators import json_validator diff --git a/philo/models/nodes.py b/philo/models/nodes.py index a225416..c29ac2a 100644 --- a/philo/models/nodes.py +++ b/philo/models/nodes.py @@ -14,7 +14,8 @@ from django.utils.encoding import smart_str from philo.exceptions import MIDDLEWARE_NOT_CONFIGURED, ViewCanNotProvideSubpath, ViewDoesNotProvideSubpaths from philo.models.base import TreeEntity, Entity, register_value_model from philo.models.fields import JSONField -from philo.utils import ContentTypeSubclassLimiter, LazyPassthroughAttributeMapper +from philo.utils import ContentTypeSubclassLimiter +from philo.utils.entities import LazyPassthroughAttributeMapper from philo.signals import view_about_to_render, view_finished_rendering diff --git a/philo/utils/__init__.py b/philo/utils/__init__.py new file mode 100644 index 0000000..83436a9 --- /dev/null +++ b/philo/utils/__init__.py @@ -0,0 +1,163 @@ +from django.db import models +from django.contrib.contenttypes.models import ContentType +from django.core.paginator import Paginator, EmptyPage +from django.template import Context +from django.template.loader_tags import ExtendsNode, ConstantIncludeNode + + +def fattr(*args, **kwargs): + """ + 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. + + Example:: + + >>> from philo.utils import fattr + >>> @fattr(short_description="Hello World!") + ... def x(): + ... pass + ... + >>> x.short_description + 'Hello World!' + + """ + def wrapper(function): + for key in kwargs: + setattr(function, key, kwargs[key]) + return function + return wrapper + + +### ContentTypeLimiters + + +class ContentTypeLimiter(object): + def q_object(self): + return models.Q(pk__in=[]) + + def add_to_query(self, query, *args, **kwargs): + query.add_q(self.q_object(), *args, **kwargs) + + +class ContentTypeRegistryLimiter(ContentTypeLimiter): + """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.""" + def __init__(self): + self.classes = [] + + def register_class(self, cls): + """Registers a model class with this limiter.""" + self.classes.append(cls) + + def unregister_class(self, cls): + """Unregisters a model class from this limiter.""" + self.classes.remove(cls) + + def q_object(self): + contenttype_pks = [] + for cls in self.classes: + try: + if issubclass(cls, models.Model): + if not cls._meta.abstract: + contenttype = ContentType.objects.get_for_model(cls) + contenttype_pks.append(contenttype.pk) + except: + pass + return models.Q(pk__in=contenttype_pks) + + +class ContentTypeSubclassLimiter(ContentTypeLimiter): + """ + 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. + + :param cls: The class whose non-abstract subclasses will be valid choices. + :param inclusive: Whether ``cls`` should also be considered a valid choice (if it is a non-abstract subclass of :class:`models.Model`) + + """ + def __init__(self, cls, inclusive=False): + self.cls = cls + self.inclusive = inclusive + + def q_object(self): + contenttype_pks = [] + def handle_subclasses(cls): + for subclass in cls.__subclasses__(): + try: + if issubclass(subclass, models.Model): + if not subclass._meta.abstract: + if not self.inclusive and subclass is self.cls: + continue + contenttype = ContentType.objects.get_for_model(subclass) + contenttype_pks.append(contenttype.pk) + handle_subclasses(subclass) + except: + pass + handle_subclasses(self.cls) + return models.Q(pk__in=contenttype_pks) + + +### Pagination + + +def paginate(objects, per_page=None, page_number=1): + """ + Given a list of objects, return a (``paginator``, ``page``, ``objects``) tuple. + + :param objects: The list of objects to be paginated. + :param per_page: The number of objects per page. + :param page_number: The number of the current page. + :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``. + + """ + try: + per_page = int(per_page) + except (TypeError, ValueError): + # Then either it wasn't set or it was set to an invalid value + paginator = page = None + else: + # There also shouldn't be pagination if the list is too short. Try count() + # first - good chance it's a queryset, where count is more efficient. + try: + if objects.count() <= per_page: + paginator = page = None + except AttributeError: + if len(objects) <= per_page: + paginator = page = None + + try: + return paginator, page, objects + except NameError: + pass + + paginator = Paginator(objects, per_page) + try: + page_number = int(page_number) + except: + page_number = 1 + + try: + page = paginator.page(page_number) + except EmptyPage: + page = None + else: + objects = page.object_list + + return paginator, page, objects + + +### Facilitating template analysis. + + +LOADED_TEMPLATE_ATTR = '_philo_loaded_template' +BLANK_CONTEXT = Context() + + +def get_extended(self): + return self.get_parent(BLANK_CONTEXT) + + +def get_included(self): + return self.template + + +# We ignore the IncludeNode because it will never work in a blank context. +setattr(ExtendsNode, LOADED_TEMPLATE_ATTR, property(get_extended)) +setattr(ConstantIncludeNode, LOADED_TEMPLATE_ATTR, property(get_included)) \ No newline at end of file diff --git a/philo/utils.py b/philo/utils/entities.py similarity index 60% rename from philo/utils.py rename to philo/utils/entities.py index f055051..9764a54 100644 --- a/philo/utils.py +++ b/philo/utils/entities.py @@ -2,137 +2,6 @@ from UserDict import DictMixin from django.db import models from django.contrib.contenttypes.models import ContentType -from django.core.paginator import Paginator, EmptyPage -from django.template import Context -from django.template.loader_tags import ExtendsNode, ConstantIncludeNode - - -def fattr(*args, **kwargs): - def wrapper(function): - for key in kwargs: - setattr(function, key, kwargs[key]) - return function - return wrapper - - -### ContentTypeLimiters - - -class ContentTypeLimiter(object): - def q_object(self): - return models.Q(pk__in=[]) - - def add_to_query(self, query, *args, **kwargs): - query.add_q(self.q_object(), *args, **kwargs) - - -class ContentTypeRegistryLimiter(ContentTypeLimiter): - def __init__(self): - self.classes = [] - - def register_class(self, cls): - self.classes.append(cls) - - def unregister_class(self, cls): - self.classes.remove(cls) - - def q_object(self): - contenttype_pks = [] - for cls in self.classes: - try: - if issubclass(cls, models.Model): - if not cls._meta.abstract: - contenttype = ContentType.objects.get_for_model(cls) - contenttype_pks.append(contenttype.pk) - except: - pass - return models.Q(pk__in=contenttype_pks) - - -class ContentTypeSubclassLimiter(ContentTypeLimiter): - def __init__(self, cls, inclusive=False): - self.cls = cls - self.inclusive = inclusive - - def q_object(self): - contenttype_pks = [] - def handle_subclasses(cls): - for subclass in cls.__subclasses__(): - try: - if issubclass(subclass, models.Model): - if not subclass._meta.abstract: - if not self.inclusive and subclass is self.cls: - continue - contenttype = ContentType.objects.get_for_model(subclass) - contenttype_pks.append(contenttype.pk) - handle_subclasses(subclass) - except: - pass - handle_subclasses(self.cls) - return models.Q(pk__in=contenttype_pks) - - -### Pagination - - -def paginate(objects, per_page=None, page_number=1): - """ - Given a list of objects, return a (paginator, page, objects) tuple. - """ - try: - per_page = int(per_page) - except (TypeError, ValueError): - # Then either it wasn't set or it was set to an invalid value - paginator = page = None - else: - # There also shouldn't be pagination if the list is too short. Try count() - # first - good chance it's a queryset, where count is more efficient. - try: - if objects.count() <= per_page: - paginator = page = None - except AttributeError: - if len(objects) <= per_page: - paginator = page = None - - try: - return paginator, page, objects - except NameError: - pass - - paginator = Paginator(objects, per_page) - try: - page_number = int(page_number) - except: - page_number = 1 - - try: - page = paginator.page(page_number) - except EmptyPage: - page = None - else: - objects = page.object_list - - return paginator, page, objects - - -### Facilitating template analysis. - - -LOADED_TEMPLATE_ATTR = '_philo_loaded_template' -BLANK_CONTEXT = Context() - - -def get_extended(self): - return self.get_parent(BLANK_CONTEXT) - - -def get_included(self): - return self.template - - -# We ignore the IncludeNode because it will never work in a blank context. -setattr(ExtendsNode, LOADED_TEMPLATE_ATTR, property(get_extended)) -setattr(ConstantIncludeNode, LOADED_TEMPLATE_ATTR, property(get_included)) ### AttributeMappers