from django.forms.models import fields_for_model from django.core.paginator import Paginator, EmptyPage, InvalidPage from django.utils.datastructures import SortedDict class ModelListItem(object): """ Item for a ModelList. It represent a model instance. """ def __init__(self, instance, fields = None, exclude=None): self.instance = instance self.fields = fields self.exclude = exclude def as_table_row(self): """ Returns the list item as a table row, where every field is a table cell. """ row = u"" for field in self.as_field_list(): row += u"" + field + u"" return row def as_field_list(self): """ Returns the list item as a list of pretty-printed values. """ field_list = [] for attr in fields_for_model(self.instance, self.fields, self.exclude).iterkeys(): field_list.append(self.get_value(attr)) return field_list def get_value(self, attr): """ Returns a string representation of the value of the given field name. First tries to use a function get__value, otherwise infers the value. Subclasses of this can define that function for specific display. """ function_name = "get_" + attr + "_value" if hasattr(self, function_name): return getattr(self, function_name)(attr) #for choices choice_display = "get_" + attr + "_display" if hasattr(self.instance, choice_display): return getattr(self.instance, choice_display)() value = getattr(self.instance, attr) if value is True: return u"Yes" elif value is False: return u"No" if value is None: return u"" return unicode(value) class ModelList(object): ''' An object representing a list of models to use in template rendering. It's analogous to the Django ModelForm. ''' def __init__(self, instances=None, paginate_by=None, page=1, order_by=None): self.__complete_meta__() self.__override_parameters__(instances, paginate_by, order_by) self.items = [self.__new_item__(i) for i in self.Meta.instances] if self.Meta.order_by: self.__order__() if self.Meta.paginate_by: self.__paginate__(page) def __new_item__(self, instance): """ Given an instance to be added to the model list, a new model list item is created. Useful for overriding in subclasses to construct complex list items. """ return self.Meta.item_class(instance, self.Meta.fields, self.Meta.exclude) def __complete_meta__(self): """ This method ensures that the Meta class has all the needed attributes, so only those to that change have to be overriden. """ for field in dir(ModelList.Meta): if not hasattr(self.Meta, field): setattr(self.Meta, field, getattr(ModelList.Meta, field)) def __override_parameters__(self, instances, paginate_by, order_by): if instances is not None: self.Meta.instances = instances if paginate_by is not None: self.Meta.paginate_by = paginate_by if order_by is not None: self.Meta.order_by = order_by def __paginate__(self, page): """ Filters the queryset and returns the correct page object """ paginator = Paginator(self.items, self.Meta.paginate_by) try: self.page_obj = paginator.page(page) except (EmptyPage, InvalidPage): self.page_obj = paginator.page(paginator.num_pages) self.items = self.page_obj.object_list def __order__(self): reverse = False if self.Meta.order_by.startswith('-'): reverse = True self.Meta.order_by = self.Meta.order_by[1:] try: self.items.sort(key=self.__key_function__, reverse=reverse) except AttributeError: #if the order_by is not a valid field, don't order pass def __key_function__(self, item): value = item.get_value(self.Meta.order_by) if value.startswith(u'$'): value = value[1:] if value.endswith(u'%'): value = value[:len(value) - 1] try: return float(value) except ValueError: return value.lower() def as_table_header(self): field_names = self.field_names() tds = [u'' + field_names[field] + u'' for field in field_names.keys()] return u''.join(tds) def field_names(self): model_fields = fields_for_model(self.Meta.model, self.Meta.fields, self.Meta.exclude) return SortedDict((field, self.get_name(field)) for field in model_fields) def get_name(self, field): """ Returns the name of the given field. First tries to use a function get__name, otherwise it uses the field label. """ function_name = "get_" + field + "_name" if hasattr(self, function_name): return getattr(self, function_name)(field) return field.capitalize() class Meta: model = None instances = [] fields = None exclude = None item_class = ModelListItem paginate_by = None order_by = None