mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-21 06:18:45 +02:00
Merge
This commit is contained in:
+27
-1
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import popen2
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from django.conf import settings
|
||||
from django.template import RequestContext
|
||||
@@ -41,6 +42,10 @@ def _render(request, tpl, user_context):
|
||||
return render_to_response(tpl, user_context,
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
def _render_error(request, user_context):
|
||||
return render_to_response("common/error.html", user_context,
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
def exec_as_vuser(cmd):
|
||||
code, output = exec_pipe("sudo -u %s %s" % (settings.VIRTUAL_UID, cmd))
|
||||
if code:
|
||||
@@ -54,7 +59,17 @@ def _ctx_ok(url):
|
||||
def _ctx_ko(tpl, ctx):
|
||||
return {"status" : "ko", "content" : render_to_string(tpl, ctx)}
|
||||
|
||||
def decode(s, encodings=('ascii', 'utf8', 'latin1')):
|
||||
def getctx(status, level=1, callback=None, **kwargs):
|
||||
if not callback:
|
||||
callername = sys._getframe(level).f_code.co_name
|
||||
else:
|
||||
callername = callback
|
||||
ctx = {"status" : status, "callback" : callername}
|
||||
for kw, v in kwargs.iteritems():
|
||||
ctx[kw] = v
|
||||
return ctx
|
||||
|
||||
def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii')):
|
||||
for encoding in encodings:
|
||||
try:
|
||||
return s.decode(encoding)
|
||||
@@ -69,3 +84,14 @@ def getoption(name, default=None):
|
||||
except AttributeError:
|
||||
res = default
|
||||
return res
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
def __init__(cls, name, bases, dict):
|
||||
super(Singleton, cls).__init__(name, bases, dict)
|
||||
cls.instance = None
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls.instance is None:
|
||||
cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls.instance
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import re
|
||||
from email.header import decode_header
|
||||
import lxml
|
||||
import lxml.html
|
||||
from lxml import etree
|
||||
from django.template.loader import render_to_string
|
||||
from django.template import Template, Context
|
||||
from django.utils.translation import ugettext as _
|
||||
from mailng.lib import _render, decode
|
||||
|
||||
attached_map = {}
|
||||
|
||||
class MBconnector(object):
|
||||
def __init__(self, address, port):
|
||||
self.address = address
|
||||
self.port = port
|
||||
|
||||
def messages_count(self, **kwargs):
|
||||
pass
|
||||
|
||||
class Page(object):
|
||||
def __init__(self, pageid, id_start, id_stop, items,
|
||||
items_per_page, has_previous, has_next,
|
||||
baseurl=None):
|
||||
self.pageid = pageid
|
||||
self.id_start = id_start
|
||||
self.id_stop = id_stop
|
||||
self.items = items
|
||||
self.items_per_page = items_per_page
|
||||
self.has_previous = has_previous
|
||||
self.has_next = has_next
|
||||
self.baseurl = baseurl
|
||||
|
||||
def previous_page(self):
|
||||
if not self.has_previous:
|
||||
return False
|
||||
return self.pageid - 1
|
||||
|
||||
def next_page(self):
|
||||
if not self.has_next:
|
||||
return False
|
||||
return self.pageid + 1
|
||||
|
||||
def last_page(self):
|
||||
lid = self.items / self.items_per_page
|
||||
if not lid:
|
||||
return 1
|
||||
if self.items % self.items_per_page:
|
||||
lid += 1
|
||||
return lid
|
||||
|
||||
class Paginator(object):
|
||||
def __init__(self, total, elems_per_page):
|
||||
self.total = total
|
||||
self.elems_per_page = elems_per_page
|
||||
self.baseurl = None
|
||||
|
||||
def _indexes(self, page):
|
||||
id_start = self.elems_per_page * page + 1
|
||||
id_stop = id_start + self.elems_per_page - 1
|
||||
return (id_start, id_stop)
|
||||
|
||||
def getpage(self, page):
|
||||
if page < 1:
|
||||
return None
|
||||
id_start, id_stop = self._indexes(page - 1)
|
||||
has_previous = has_next = False
|
||||
if id_start < self.total:
|
||||
if page > 1:
|
||||
has_previous = True
|
||||
if id_stop < self.total:
|
||||
has_next = True
|
||||
else:
|
||||
id_stop = self.total
|
||||
else:
|
||||
return None
|
||||
p = Page(page, id_start, id_stop, self.total,
|
||||
self.elems_per_page, has_previous, has_next)
|
||||
if self.baseurl:
|
||||
p.baseurl = self.baseurl
|
||||
return p
|
||||
|
||||
class EmailListing:
|
||||
def __init__(self, baseurl=None, folder=None, elems_per_page=40,
|
||||
navparams={}, **kwargs):
|
||||
self.folder = folder
|
||||
self.elems_per_page = elems_per_page
|
||||
self.navparams = navparams
|
||||
order = "order" in kwargs.keys() and kwargs["order"] or None
|
||||
self.empty = "empty" in kwargs.keys() and kwargs["empty"] or False
|
||||
if not self.empty:
|
||||
self.paginator = Paginator(self.mbc.messages_count(folder=self.folder,
|
||||
order=order),
|
||||
elems_per_page)
|
||||
if baseurl:
|
||||
self.paginator.baseurl = baseurl
|
||||
|
||||
def render_navbar(self, page):
|
||||
if page is None:
|
||||
return ""
|
||||
return render_to_string("common/navbar.html", {
|
||||
"page" : page
|
||||
})
|
||||
|
||||
def getfolders(self):
|
||||
return None
|
||||
|
||||
def fetch(self, request, id_start, id_stop):
|
||||
table = self.tbltype(self.mbc.fetch(start=id_start, stop=id_stop,
|
||||
folder=self.folder))
|
||||
tpl = Template("""
|
||||
<form method="POST" id="listingform">
|
||||
{{ table }}
|
||||
</form>""")
|
||||
return tpl.render(Context({"table" : table.render(request)}))
|
||||
|
||||
def render(self, request, pageid=1, **kwargs):
|
||||
listing = ""
|
||||
page = None
|
||||
if not self.empty:
|
||||
page = self.paginator.getpage(pageid)
|
||||
if not page:
|
||||
listing = "Empty folder"
|
||||
else:
|
||||
listing = self.fetch(request, page.id_start, page.id_stop)
|
||||
elapsed = kwargs.has_key("start") and time.time() - kwargs["start"] or 0
|
||||
return _render(request, self.tpl, {
|
||||
"listing" : listing,
|
||||
"elapsed" : elapsed,
|
||||
"navbar" : self.render_navbar(page),
|
||||
"folders" : self.getfolders(),
|
||||
"selection" : self.folder,
|
||||
"navparams" : self.navparams,
|
||||
"deflocation" : self.deflocation, "defcallback" : self.defcallback
|
||||
})
|
||||
|
||||
class Email(object):
|
||||
def __init__(self, msg, mode="plain", links="0"):
|
||||
self.attached_map = {}
|
||||
contents = {"html" : "", "plain" : ""}
|
||||
self.headers = []
|
||||
self.attachments = []
|
||||
for part in msg.walk():
|
||||
if part.get_content_maintype() == 'multipart':
|
||||
continue
|
||||
if part.has_key("Content-Disposition"):
|
||||
if re.match("^attachment", part["Content-Disposition"]):
|
||||
self.attachments += [part]
|
||||
if part.get_content_type() in ("text/html", "text/plain"):
|
||||
contents[part.get_content_subtype()] += part.get_payload(decode=True)
|
||||
if mode != "html" or links == "0":
|
||||
continue
|
||||
|
||||
if part.get_content_maintype() == "image":
|
||||
if part.has_key("Content-Location"):
|
||||
fname = part["Content-Location"]
|
||||
if re.match("^http:", fname):
|
||||
path = fname
|
||||
else:
|
||||
path = "/%s" % os.path.join("static/tmp", fname)
|
||||
fp = open(path, "wb")
|
||||
fp.write(part.get_payload(decode=True))
|
||||
fp.close()
|
||||
m = re.match("<(.+)>", part["Content-ID"])
|
||||
if m:
|
||||
self.attached_map[m.group(1)] = path
|
||||
else:
|
||||
self.attached_map[part["Content-ID"]] = path
|
||||
|
||||
if not contents.has_key(mode) or contents[mode] == "":
|
||||
if mode == "html":
|
||||
mode = "plain"
|
||||
else:
|
||||
mode = "html"
|
||||
self.pre, self.body = \
|
||||
getattr(self, "viewmail_%s" % mode)(contents[mode], links=links)
|
||||
|
||||
def map_cid(self, url):
|
||||
import re
|
||||
|
||||
map = globals()["attached_map"]
|
||||
m = re.match(".*cid:([^\"]+)", url)
|
||||
if m:
|
||||
if map.has_key(m.group(1)):
|
||||
return map[m.group(1)]
|
||||
return url
|
||||
|
||||
def render_headers(self, folder, mail_id):
|
||||
attachments = []
|
||||
for part in self.attachments:
|
||||
decoded = decode_header(part.get_filename())
|
||||
if decoded[0][1] is None:
|
||||
attachments += [decoded[0][0]]
|
||||
else:
|
||||
attachments += [unicode(decoded[0][0], decoded[0][1])]
|
||||
return render_to_string("webmail/headers.html", {
|
||||
"headers" : self.headers,
|
||||
"folder" : folder, "mail_id" : mail_id,
|
||||
"attachments" : attachments != [] and attachments or None
|
||||
})
|
||||
|
||||
def render(self, request):
|
||||
return _render(request, 'common/getmailcontent.html', {
|
||||
"body" : self.body, "pre" : self.pre
|
||||
})
|
||||
|
||||
def viewmail_plain(self, content, **kwargs):
|
||||
body = decode(content)
|
||||
return (True, body)
|
||||
|
||||
def viewmail_html(self, content, **kwargs):
|
||||
links = kwargs.has_key("links") and kwargs["links"] or "0"
|
||||
html = lxml.html.fromstring(content)
|
||||
if links == "0":
|
||||
html.rewrite_links(lambda x: None)
|
||||
else:
|
||||
html.rewrite_links(self.map_cid)
|
||||
body = html.find("body")
|
||||
if body is None:
|
||||
body = lxml.html.tostring(html)
|
||||
else:
|
||||
body = lxml.html.tostring(body)
|
||||
body = re.sub("<(/?)body", lambda m: "<%sdiv" % m.group(1), body)
|
||||
body = Template(decode(body)).render({})
|
||||
return (False, body)
|
||||
|
||||
+3
-1
@@ -13,7 +13,9 @@ events = ["CreateDomain",
|
||||
"ModifyMailbox",
|
||||
|
||||
"UserMenuDisplay",
|
||||
"AdminMenuDisplay"]
|
||||
"AdminMenuDisplay",
|
||||
|
||||
"UserLogin"]
|
||||
|
||||
callbacks = {}
|
||||
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
# -*- coding: latin-1 -*-
|
||||
|
||||
"""
|
||||
Imap folder names are encoded using a special version of utf-7 as defined in RFC
|
||||
2060 section 5.1.3.
|
||||
|
||||
5.1.3. Mailbox International Naming Convention
|
||||
|
||||
By convention, international mailbox names are specified using a
|
||||
modified version of the UTF-7 encoding described in [UTF-7]. The
|
||||
purpose of these modifications is to correct the following problems
|
||||
with UTF-7:
|
||||
|
||||
1) UTF-7 uses the "+" character for shifting; this conflicts with
|
||||
the common use of "+" in mailbox names, in particular USENET
|
||||
newsgroup names.
|
||||
|
||||
2) UTF-7's encoding is BASE64 which uses the "/" character; this
|
||||
conflicts with the use of "/" as a popular hierarchy delimiter.
|
||||
|
||||
3) UTF-7 prohibits the unencoded usage of "\"; this conflicts with
|
||||
the use of "\" as a popular hierarchy delimiter.
|
||||
|
||||
4) UTF-7 prohibits the unencoded usage of "~"; this conflicts with
|
||||
the use of "~" in some servers as a home directory indicator.
|
||||
|
||||
5) UTF-7 permits multiple alternate forms to represent the same
|
||||
string; in particular, printable US-ASCII chararacters can be
|
||||
represented in encoded form.
|
||||
|
||||
In modified UTF-7, printable US-ASCII characters except for "&"
|
||||
represent themselves; that is, characters with octet values 0x20-0x25
|
||||
and 0x27-0x7e. The character "&" (0x26) is represented by the two-
|
||||
octet sequence "&-".
|
||||
|
||||
All other characters (octet values 0x00-0x1f, 0x7f-0xff, and all
|
||||
Unicode 16-bit octets) are represented in modified BASE64, with a
|
||||
further modification from [UTF-7] that "," is used instead of "/".
|
||||
Modified BASE64 MUST NOT be used to represent any printing US-ASCII
|
||||
character which can represent itself.
|
||||
|
||||
"&" is used to shift to modified BASE64 and "-" to shift back to US-
|
||||
ASCII. All names start in US-ASCII, and MUST end in US-ASCII (that
|
||||
is, a name that ends with a Unicode 16-bit octet MUST end with a "-
|
||||
").
|
||||
|
||||
For example, here is a mailbox name which mixes English, Japanese,
|
||||
and Chinese text: ~peter/mail/&ZeVnLIqe-/&U,BTFw-
|
||||
|
||||
Found here:
|
||||
http://svn.plone.org/svn/collective/mxmImapClient/trunk/imapUTF7.py
|
||||
|
||||
"""
|
||||
import binascii
|
||||
import codecs
|
||||
|
||||
# encoding
|
||||
|
||||
def modified_base64(s):
|
||||
s = s.encode('utf-16be')
|
||||
return binascii.b2a_base64(s).rstrip('\n=').replace('/', ',')
|
||||
|
||||
def doB64(_in, r):
|
||||
if _in:
|
||||
r.append('&%s-' % modified_base64(''.join(_in)))
|
||||
del _in[:]
|
||||
|
||||
def encoder(s):
|
||||
r = []
|
||||
_in = []
|
||||
for c in s:
|
||||
ordC = ord(c)
|
||||
if 0x20 <= ordC <= 0x25 or 0x27 <= ordC <= 0x7e:
|
||||
doB64(_in, r)
|
||||
r.append(c)
|
||||
elif c == '&':
|
||||
doB64(_in, r)
|
||||
r.append('&-')
|
||||
else:
|
||||
_in.append(c)
|
||||
doB64(_in, r)
|
||||
return (str(''.join(r)), len(s))
|
||||
|
||||
|
||||
# decoding
|
||||
|
||||
def modified_unbase64(s):
|
||||
b = binascii.a2b_base64(s.replace(',', '/') + '===')
|
||||
return unicode(b, 'utf-16be')
|
||||
|
||||
|
||||
def decoder(s):
|
||||
r = []
|
||||
decode = []
|
||||
for c in s:
|
||||
if c == '&' and not decode:
|
||||
decode.append('&')
|
||||
elif c == '-' and decode:
|
||||
if len(decode) == 1:
|
||||
r.append('&')
|
||||
else:
|
||||
r.append(modified_unbase64(''.join(decode[1:])))
|
||||
decode = []
|
||||
elif decode:
|
||||
decode.append(c)
|
||||
else:
|
||||
r.append(c)
|
||||
if decode:
|
||||
r.append(modified_unbase64(''.join(decode[1:])))
|
||||
bin_str = ''.join(r)
|
||||
return (bin_str, len(s))
|
||||
|
||||
|
||||
class StreamReader(codecs.StreamReader):
|
||||
def decode(self, s, errors='strict'):
|
||||
return decoder(s)
|
||||
|
||||
|
||||
class StreamWriter(codecs.StreamWriter):
|
||||
def decode(self, s, errors='strict'):
|
||||
return encoder(s)
|
||||
|
||||
|
||||
def imap4_utf_7(name):
|
||||
if name == 'imap4-utf-7':
|
||||
return (encoder, decoder, StreamReader, StreamWriter)
|
||||
codecs.register(imap4_utf_7)
|
||||
|
||||
|
||||
## testing methods
|
||||
|
||||
def imapUTF7Encode(ust):
|
||||
"Returns imap utf-7 encoded version of string"
|
||||
return ust.encode('imap4-utf-7')
|
||||
|
||||
def imapUTF7EncodeSequence(seq):
|
||||
"Returns imap utf-7 encoded version of strings in sequence"
|
||||
return [imapUTF7Encode(itm) for itm in seq]
|
||||
|
||||
|
||||
def imapUTF7Decode(st):
|
||||
"Returns utf7 encoded version of imap utf-7 string"
|
||||
return st.decode('imap4-utf-7')
|
||||
|
||||
def imapUTF7DecodeSequence(seq):
|
||||
"Returns utf7 encoded version of imap utf-7 strings in sequence"
|
||||
return [imapUTF7Decode(itm) for itm in seq]
|
||||
|
||||
|
||||
def utf8Decode(st):
|
||||
"Returns utf7 encoded version of imap utf-7 string"
|
||||
return st.decode('utf-8')
|
||||
|
||||
|
||||
def utf7SequenceToUTF8(seq):
|
||||
"Returns utf7 encoded version of imap utf-7 strings in sequence"
|
||||
return [itm.decode('imap4-utf-7').encode('utf-8') for itm in seq]
|
||||
|
||||
|
||||
__all__ = ['imapUTF7Encode', 'imapUTF7Decode', ]
|
||||
|
||||
if __name__=='__main__':
|
||||
|
||||
## print u'bøx'.encode('imap4-utf-7')
|
||||
## print 'expected b&APg-x'
|
||||
##
|
||||
## print u'båx'.encode('imap4-utf-7')
|
||||
## print 'expected b&AOU-x'
|
||||
##
|
||||
print '#######'
|
||||
print 'bøx'
|
||||
e = imapUTF7Encode(u'bøx')
|
||||
print e
|
||||
print imapUTF7Decode(e).encode('latin-1')
|
||||
|
||||
print '#######'
|
||||
print 'båx'
|
||||
e = imapUTF7Encode(u'båx')
|
||||
print e
|
||||
print imapUTF7Decode(e).encode('latin-1')
|
||||
|
||||
print '#######'
|
||||
print '~/bågø'
|
||||
e = imapUTF7Encode(u'~/bågø')
|
||||
print e
|
||||
print imapUTF7Decode(e).encode('latin-1')
|
||||
|
||||
print '#######'
|
||||
print 'Ting & Såger'
|
||||
e = imapUTF7Encode(u'Ting & Såger')
|
||||
print e
|
||||
print imapUTF7Decode(e).encode('latin-1')
|
||||
|
||||
## e = imapUTF7Decode('b&AOU-x')
|
||||
## print e.encode('latin-1')
|
||||
##
|
||||
## e = imapUTF7Decode('b&APg-x')
|
||||
## print e.encode('latin-1')
|
||||
##
|
||||
print '#######'
|
||||
print '~/Følder/mailbåx & stuff + more'
|
||||
n = u'~/Følder/mailbåx & stuff + more'
|
||||
e = imapUTF7Encode(n)
|
||||
print e
|
||||
print imapUTF7Decode(e).encode('latin-1')
|
||||
|
||||
print '#######'
|
||||
print '~peter/mail/&ZeVnLIqe-/&U,BTFw-'
|
||||
print imapUTF7Decode('~peter/mail/&ZeVnLIqe-/&U,BTFw-').encode('utf-8')
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import string
|
||||
from mailng.admin.models import Parameter
|
||||
|
||||
"""
|
||||
This interface provides a simple way to declare and store parameters
|
||||
in MailNG's database.
|
||||
|
||||
Core components or extensions can register their own parameters, which
|
||||
will be available and modifiable directly from the web interface.
|
||||
|
||||
Only super users will be able to acces this part of the web interface.
|
||||
"""
|
||||
|
||||
_params = {}
|
||||
|
||||
class NotDefined(Exception):
|
||||
def __init__(self, app, name):
|
||||
self.app = app
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return "Application '%s' and/or parameter '%s' not defined" % (self.app,
|
||||
self.name)
|
||||
|
||||
def register(app, name, type="string", deflt=None, help=None, **kwargs):
|
||||
"""Register a new parameter.
|
||||
|
||||
app corresponds to a core component (admin, main) or an extension.
|
||||
"""
|
||||
if not app in _params.keys():
|
||||
_params[app] = {}
|
||||
_params[app][name] = {"type" : type, "default" : deflt, "help" : help}
|
||||
|
||||
def save(app, name, value):
|
||||
if not app in _params.keys() or not name in _params[app].keys():
|
||||
raise NotDefined(app, name)
|
||||
fullname = "%s.%s" % (app, name)
|
||||
try:
|
||||
p = Parameter.objects.get(name=fullname)
|
||||
except Parameter.DoesNotExist:
|
||||
p = Parameter()
|
||||
p.name = fullname
|
||||
if p.value != value:
|
||||
p.value = value
|
||||
p.save()
|
||||
return True
|
||||
|
||||
def get(app, name):
|
||||
if not app in _params.keys() or not name in _params[app].keys():
|
||||
raise NotDefined(app, name)
|
||||
try:
|
||||
p = Parameter.objects.get(name="%s.%s" % (app, name))
|
||||
except Parameter.DoesNotExist:
|
||||
return _params[app][name]["default"]
|
||||
return p.value
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import inspect
|
||||
import lxml
|
||||
from django.template import Template, Context
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
class Column:
|
||||
sortable = True
|
||||
|
||||
def __init__(self, name, **kwargs):
|
||||
self.name = name
|
||||
for k, v in kwargs.iteritems():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
label = getattr(self, "label")
|
||||
except KeyError:
|
||||
label = getattr(self, "name")
|
||||
return label
|
||||
|
||||
class SelectionColumn(Column):
|
||||
def __str__(self):
|
||||
return "<input type='checkbox' name='selectall' id='selectall' />"
|
||||
|
||||
def render(self, value):
|
||||
return "<input type='checkbox' name='%s' value='%s' />" \
|
||||
% (self.name, value)
|
||||
|
||||
class ImgColumn(Column):
|
||||
sortable = False
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
return getattr(self, "header")
|
||||
except AttributeError:
|
||||
return ""
|
||||
|
||||
def render(self, value):
|
||||
return "<img src='%s' />" % value
|
||||
|
||||
class Table(object):
|
||||
tableid = ""
|
||||
|
||||
def __init__(self, rows={}):
|
||||
self.columns = []
|
||||
for m in inspect.getmembers(self):
|
||||
if isinstance(m[1], Column):
|
||||
try:
|
||||
if getattr(m[1], "first"):
|
||||
self.columns = [m[1]] + self.columns
|
||||
except AttributeError:
|
||||
self.columns += [m[1]]
|
||||
self.rows = []
|
||||
for row in rows:
|
||||
nrow = {"id" : row[self.idkey], "cols" : []}
|
||||
for c in self.columns:
|
||||
newcol = {}
|
||||
try:
|
||||
newcol["width"] = getattr(c, "width")
|
||||
except AttributeError:
|
||||
pass
|
||||
if isinstance(c, SelectionColumn):
|
||||
newcol["value"] = c.render(row[self.idkey])
|
||||
newcol["safe"] = True
|
||||
|
||||
if isinstance(c, ImgColumn):
|
||||
key = "img_%s" % c.name
|
||||
if key in row.keys():
|
||||
newcol["value"] = c.render(row[key])
|
||||
newcol["safe"] = True
|
||||
|
||||
if row.has_key(c.name):
|
||||
try:
|
||||
cssclass = getattr(c, "cssclass")
|
||||
except AttributeError:
|
||||
cssclass = ""
|
||||
if "class" in row.keys():
|
||||
cssclass += (cssclass != "") and ", %s" % row["class"] or row["class"]
|
||||
newcol["value"] = self.parse(c.name, row[c.name])
|
||||
newcol["class"] = cssclass
|
||||
nrow["cols"] += [newcol]
|
||||
self.rows += [nrow]
|
||||
|
||||
def render(self, request):
|
||||
t = Template("""
|
||||
<table id="{{ tableid }}">
|
||||
{% include "common/table_head.html" %}
|
||||
{% include "common/table_body.html" %}
|
||||
</table>
|
||||
""")
|
||||
return t.render(Context({
|
||||
"table" : self, "tableid" : self.tableid
|
||||
}))
|
||||
|
||||
def render_head(self, request):
|
||||
t = Template("""
|
||||
<table id="{{ tableid }}">
|
||||
{% include "common/table_head.html" %}
|
||||
</table>
|
||||
""")
|
||||
return t.render(Context({
|
||||
"table" : self, "tableid" : "%s_head" % self.tableid
|
||||
}))
|
||||
|
||||
def render_body(self, request):
|
||||
t = Template("""
|
||||
<table id="{{ tableid }}">
|
||||
{% include "common/table_body.html" %}
|
||||
</table>
|
||||
""")
|
||||
return t.render(Context({
|
||||
"table" : self, "tableid" : "%s_body" % self.tableid
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user