Completed
Push — master ( da0207...5a5796 )
by Mathias
01:37
created

OneToManyLink.format()   D

Complexity

Conditions 8

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 25
rs 4
cc 8
1
# Copyright 2013 Mathias WOLFF
2
# This file is part of pyfreebilling.
3
#
4
# pyfreebilling is free software: you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# pyfreebilling is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with pyfreebilling. If not, see <http://www.gnu.org/licenses/>
16
#
17
# Initial code from Chris Spencer -
18
# https://github.com/chrisspen/django-admin-steroids
19
20
import re
21
22
from django.core import urlresolvers
23
from django.utils.safestring import SafeString, mark_safe
24
from django.conf import settings
25
26
import utils
27
28
NONE_STR = '(None)'
29
30
31
class AdminFieldFormatter(object):
32
    """
33
    Base class for controlling the display formatting of field values
34
    in Django's admin.
35
    how to use :
36
    from pyfreebilling.formatters import DollarFormat
37
38
    class ModelAdmin():
39
        fields = ('id',
40
                  'name',
41
                  DollarFormat('income', decimals=2))
42
    """
43
44
    # Only necessary for logic in admin.helpers.AdminReadonlyField.__init__.
45
    __name__ = 'AdminFieldFormatter'
46
47
    is_readonly = True
48
    object_level = False
49
    title_align = None
50
    null = False
51
52
    def __init__(self, name, title=None, **kwargs):
53
        self.name = name
54
        self.short_description = kwargs.get('short_description', title or name)
55
        kwargs.setdefault('allow_tags', True)
56
        kwargs.setdefault('admin_order_field', name)
57
        kwargs.setdefault('title_align',
58
                          kwargs.get('align', kwargs.get('title_align')))
59
        self.__dict__.update(kwargs)
60
61
        if not isinstance(self.short_description, SafeString):
62
            self.short_description = re.sub(
63
                '[^0-9a-zA-Z]+',
64
                ' ',
65
                self.short_description).capitalize()
66
67
            #TODO: Allow markup in short_description? Not practical due to
68
            #hardcoded escape() in
69
            #django.contrib.admin.helpers.AdminReadonlyField
70
#            if self.title_align:
71
#                title_template = '<span style="text-align:%s">%s</span>'
72
#                self.short_description = title_template \
73
#                    % (self.title_align, self.short_description)
74
#                self.short_description = mark_safe(self.short_description)
75
76
    def __call__(self, obj, plaintext=False):
77
        if self.object_level:
78
            v = obj
79
        else:
80
            if '__' in self.name:
81
                # Follow Django's double-underscore dereferencing notation.
82
                parts = self.name.split('__')
83
                v = obj
84
                for part in parts:
85
                    if v is not None:
86
                        v = getattr(v, part)
87
            else:
88
                v = getattr(obj, self.name)
89
        if callable(v):
90
            v = v()
91
        if v is None and self.null:
92
            return NONE_STR
93
        if plaintext:
94
            return self.plaintext(v)
95
        return self.format(v)
96
97
    def format(self, v, plaintext=False):
98
        return v
99
100
    def plaintext(self, *args, **kwargs):
101
        """
102
        Called when no HTML is desired.
103
        """
104
        kwargs['plaintext'] = True
105
        return self.format(*args, **kwargs)
106
107
108
class DollarFormat(AdminFieldFormatter):
109
    """
110
    Formats a numeric value as dollars.
111
    """
112
    decimals = 2
113
    title_align = 'right'
114
    align = 'right'
115
    commas = True
116
117
    def format(self, v, plaintext=False):
118
        if v is None:
119
            return NONE_STR
120
        template = '$%.' + str(self.decimals) + 'f'
121
        if v < 0:
122
            v *= -1
123
            template = '('+template+')'
124
        if self.commas:
125
            template = utils.FormatWithCommas(template, v)
126
        else:
127
            template = template % v
128
        style = 'display:inline-block; width:100%%; text-align:'+self.align+';'
129
        if not plaintext:
130
            template = '<span style="'+style+'">'+template+'</span>'
131
        return template
132
133
134
class PercentFormat(AdminFieldFormatter):
135
    """
136
    Formats a ratio as a percent.
137
    """
138
    template = '%.0f%%'
139
    align = 'right'
140
    rounder = round
141
142
    def format(self, v, plaintext=False):
143
        if v is None:
144
            style = 'display:inline-block; width:100%%; text-align:'+self.align+';'
145
            if not plaintext:
146
                template = '<span style="'+style+'">'+NONE_STR+'</span>'
147
            return template
148
        v *= 100
149
        v = self.rounder(v)
150
        template = self.template
151
        style = 'display:inline-block; width:100%%; text-align:'+self.align+';'
152
        if not plaintext:
153
            template = '<span style="'+style+'">'+template+'</span>'
154
        return template % v
155
156
157
class FloatFormat(AdminFieldFormatter):
158
    """
159
    Formats a number as a float.
160
    """
161
    decimals = 2
162
    template = '%.02f'
163
    align = 'left'
164
    rounder = round
165
166
    def format(self, v, plaintext=False):
167
        if v is None:
168
            style = 'display:inline-block; width:100%%; text-align:'+self.align+';'
169
            if not plaintext:
170
                template = '<span style="'+style+'">'+NONE_STR+'</span>'
171
            return template
172
        v = self.rounder(v, self.decimals)
173
        template = self.template
174
        style = 'display:inline-block; width:100%%; text-align:'+self.align+';'
175
        if not plaintext:
176
            template = '<span style="'+style+'">'+template+'</span>'
177
        return template % v
178
179
180
class CenterFormat(AdminFieldFormatter):
181
    """
182
    Formats a ratio as a percent.
183
    """
184
    title_align = 'center'
185
    align = 'center'
186
187
    def format(self, v, plaintext=False):
188
        if plaintext:
189
            return str(v)
190
        style = 'display:inline-block; width:100%%; text-align:'+self.align+';'
191
        template = '<span style="'+style+'">%s</span>'
192
        return template % v
193
194
195
class ReadonlyFormat(AdminFieldFormatter):
196
    """
197
    Formats a the field as a readonly attribute.
198
    """
199
200
#    def format(self, v):
201
#        style = 'display:inline-block; width:100%%; text-align:'+self.align+';'
202
#        template = '<span style="'+style+'">%s</span>'
203
#        return template % v
204
205
206
class NbspFormat(AdminFieldFormatter):
207
    """
208
    Replaces all spaces with a non-breaking space.
209
    """
210
211
    def format(self, v, plaintext=False):
212
        v = str(v)
213
        if plaintext:
214
            return v
215
        v = v.replace(' ', '&nbsp;')
216
        return v
217
218
219
class BooleanFormat(AdminFieldFormatter):
220
    """
221
    Converts the field value into a green checkmark image for true and red dash
222
    image false.
223
    """
224
    align = 'left'
225
    yes_path = '%sadmin/img/icon-yes.gif'
226
    no_path = '%sadmin/img/icon-no.gif'
227
228
    def format(self, v, plaintext=False):
229
        v = bool(v)
230
        if plaintext:
231
            return v
232
        style = 'display:inline-block; width:100%%; text-align:'+self.align+';'
233
        template = '<span style="'+style+'"><img src="%s" alt="%s" ' + \
234
            'title="%s" /></span>'
235
        if v:
236
            url = self.yes_path
237
        else:
238
            url = self.no_path
239
        url = url % (settings.STATIC_URL,)
240
        v = template % (url, v, v)
241
        return v
242
243
244
class ForeignKeyLink(AdminFieldFormatter):
245
    """
246
    Renders a foreign key value as a link to that object's admin change page.
247
    """
248
    target = '_blank'
249
    template_type = 'raw'  # button|raw
250
    label_template = '{name}'
251
    null = True
252
253
    def format(self, v, plaintext=False):
254
        try:
255
            assert self.template_type in ('button', 'raw'), \
256
                'Invalid template type: %s' % (self.template_type)
257
            url = utils.get_admin_change_url(v)
258
            label = self.label_template.format(name=str(v))
259
            if self.template_type == 'button':
260
                return ('<a href="%s" target="%s"><input type="button" ' + \
261
                        'value="%s" /></a>') % (url, self.target, label)
262
            else:
263
                return '<a href="%s" target="%s">%s</a>' \
264
                    % (url, self.target, label)
265
        except Exception, e:
266
            return str(e)
267
268
    def plaintext(self, v):
269
        if v is None:
270
            return ''
271
        return v.id
272
273
274
class OneToManyLink(AdminFieldFormatter):
275
    """
276
    Renders a related objects manager as a link to those object's admin change
277
    list page.
278
    """
279
    object_level = True
280
    url_param = None
281
    id_param = 'id'
282
    target = '_blank'
283
284
    def format(self, obj):
285
        try:
286
            url = None
287
            try:
288
                url = urlresolvers.reverse(self.url_param)
289
                url = '{0}?{1}={2}'.format(url, self.id_param, obj.id)
290
            except Exception:
291
                pass
292
            q = count = getattr(obj, self.name)
293
            if hasattr(q, 'count'):
294
                q = q.all()
295
                count = q.count()
296
                if count == 1:
297
                    # Link directly to the record if only one result.
298
                    link_obj = q[0]
299
                    url = utils.get_admin_change_url(link_obj)
300
                elif count > 1:
301
                    url = utils.get_admin_changelist_url(q[0])
302
                    url += '?{1}={2}'.format(url, self.id_param, obj.id)
303
            if count is None or count == 0:
304
                return count
305
            return ('<a href="%s" target="%s"><input type="button" ' + \
306
                    'value="View %d" /></a>') % (url, self.target, count)
307
        except Exception, e:
308
            return str(e)
309
310
    def plaintext(self, v):
311
        return ''  # TODO?
312