Login

ModelList class

Author:
facundo_olano
Posted:
March 2, 2011
Language:
Python
Version:
1.2
Score:
0 (after 0 ratings)

This class makes easier the job of rendering lists of model instances in django templates. It's intended to mimic the behavior of the Model Forms in that it contains the code needed to render it as an HTML table and makes it easy to handle all the model lists from a single view (as it's usually done with the generic views for creating and updating model instances).

It also supports pagination and provides hooks for subclassing and customizing the rendered fields, column titles and list order.

Basic example:

class Account(Model):

name = models.CharField(max_length=MAX_LENGTH)

responsible = models.CharField(max_length=MAX_LENGTH)

email = models.EmailField()

class AccountModelList(ModelList):

class Meta:

model = Account

fields = ['name', 'responsible'] #email won't get a column

The model list would be instantiated with something like:

model_list = AccountModelList(instances=account_queryset)

Then a table header can be rendered with model_list.as_table_header(), while the table rows can be rendered calling as_table() on each model_list.items element.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
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"<td>" + field + u"</td>"
        
        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_<attr>_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'<td>' + field_names[field] + u'</td>' 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_<field>_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

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 9 months, 3 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 4 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 5 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

Please login first before commenting.