Commit 0c273c09 authored by Yoshinori Okuji's avatar Yoshinori Okuji

Initial import. This is the HEAD of the official repository + my patch

to address the problem that Cc: or Bcc: does not work. Since I don't see
any maintenance activity in them, I put a fork here. Once they fix
the problem officially, we can trash this fork.

FYI, here is my report:

https://secure.simplistix.co.uk/support/issue318


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@20876 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 849af0b1
# Copyright (c) 2005-2006 Simplistix Ltd
#
# This Software is released under the MIT License:
# http://www.opensource.org/licenses/mit-license.html
# See license.txt for more details.
import os
import rfc822
from AccessControl import ClassSecurityInfo
from DateTime import DateTime
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from Globals import InitializeClass, package_home
from MTMultipart import MTMultipart
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate, SUPPORTS_WEBDAV_LOCKS
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from ZPublisher import HTTPResponse
# Configured using zope.conf in Zope 2.7.8, Zope 2.8.2, and above
default_encoding = getattr(HTTPResponse,'default_encoding','iso-8859-15')
class BaseMailTemplate:
security = ClassSecurityInfo()
_properties = ()
ZScriptHTML_tryForm = None
content_type = 'text/plain'
mailhost = None
security.declarePrivate('_process')
def _process(self,kw):
# sort out what encoding we're going to use
encoding = kw.get('encoding',
self.getProperty('encoding',
default_encoding))
text = self.__class__.__bases__[1].__call__(self,**kw)
if not self.html():
text = text.encode(encoding,'replace')
# now turn the result into a MIMEText object
msg = MIMEText(
text.replace('\r',''),
self.content_type.split('/')[1],
encoding
)
# sort out what headers and addresses we're going to use
headers = {}
values = {}
# headers from the headers property
for header in getattr(self,'headers',()):
name,value = header.split(':',1)
headers[name]=value
# headers from the headers parameter
headers_param = kw.get('headers',{})
headers.update(headers_param)
# values and some specific headers
for key,header in (('mfrom','From'),
('mto','To'),
('mcc','Cc'),
('mbcc','Bcc'),
('subject','Subject')):
value = kw.get(key,
headers_param.get(header,
getattr(self,
key,
headers.get(header))))
if value is not None:
values[key]=value
# turn some sequences in coma-seperated strings
if isinstance(value,tuple) or isinstance(value,list):
value = ', '.join(value)
# make sure we have no unicode headers
if isinstance(value,unicode):
value = value.encode(encoding)
headers[header]=value
# check required values have been supplied
errors = []
for param in ('mfrom','mto','subject'):
if not values.get(param):
errors.append(param)
if errors:
raise TypeError(
'The following parameters were required by not specified: '+(
', '.join(errors)
))
# add date header
headers['Date']=DateTime().rfc822()
# turn headers into an ordered list for predictable header order
keys = headers.keys()
keys.sort()
return msg,values,[(key,headers[key]) for key in keys]
security.declarePrivate('_send')
def _send(self,mfrom,mto,msg):
mailhost = self.restrictedTraverse(self.mailhost,None)
if not getattr(mailhost,'meta_type',None) in (
'Mail Host','Maildrop Host'
):
raise RuntimeError(
'Could not traverse to MailHost %r' % self.mailhost
)
mailhost._send(mfrom,mto,msg.as_string())
security.declareProtected('View', 'send')
def send(self,**kw):
msg,values,headers = self._process(kw)
for header,value in headers:
msg[header]=value
to_addrs = ()
for key in ('mto', 'mcc', 'mbcc'):
v = values.get(key)
if v:
if isinstance(v, basestring):
v = [rfc822.dump_address_pair(addr) for addr \
in rfc822.AddressList(v)]
to_addrs += tuple(v)
self._send(values['mfrom'], to_addrs, msg)
security.declareProtected('View', '__call__')
__call__ = send
security.declareProtected('View', 'as_message')
def as_message(self,**kw):
msg,values,headers = self._process(kw)
multipart_kw = {}
subtype = kw.get('subtype')
if subtype:
multipart_kw['_subtype'] = subtype
boundary = kw.get('boundary')
if boundary:
multipart_kw['boundary'] = boundary
multipart = MTMultipart(self,
values['mfrom'],
values['mto'],
**multipart_kw)
# set the encoding for the container
multipart.set_charset(msg.get_charset())
for header,value in headers:
multipart[header]=value
multipart.attach(msg)
return multipart
InitializeClass(BaseMailTemplate)
# Copyright (c) 2005-2006 Simplistix Ltd
#
# This Software is released under the MIT License:
# http://www.opensource.org/licenses/mit-license.html
# See license.txt for more details.
from AccessControl import ClassSecurityInfo
from AccessControl import getSecurityManager
from Globals import InitializeClass
from Products.CMFCore.FSPageTemplate import FSPageTemplate,expandpath
from Products.CMFCore.DirectoryView import registerFileExtension
from Products.CMFCore.DirectoryView import registerMetaType
from BaseMailTemplate import BaseMailTemplate
from MailTemplate import MailTemplate
class FSMailTemplate(BaseMailTemplate,FSPageTemplate):
"Wrapper for Mail Template"
security = ClassSecurityInfo()
meta_type = 'Filesystem Mail Template'
def __init__(self, id, filepath, fullname=None, properties=None):
FSPageTemplate.__init__(self,id,filepath,fullname,properties)
self._properties = properties
security.declarePrivate('_createZODBClone')
def _createZODBClone(self):
"""Create a ZODB (editable) equivalent of this object."""
obj = MailTemplate(self.getId(), self._text, self.content_type)
obj.expand = 0
obj.write(self.read())
obj._setPropValue('mailhost',self.mailhost)
obj.content_type = self.content_type
if self._properties:
keys = self._properties.keys()
keys.sort()
for id in keys:
if id not in ('mailhost','content_type'):
obj.manage_addProperty(id,self._properties[id],'string')
return obj
security.declarePrivate('_readFile')
def _readFile(self, reparse):
fp = expandpath(self._filepath)
file = open(fp, 'r') # not 'rb', as this is a text file!
try:
data = file.read()
finally:
file.close()
if reparse:
self.write(data)
def _exec(self, bound_names, args, kw):
"""Call a FSPageTemplate"""
try:
response = self.REQUEST.RESPONSE
except AttributeError:
response = None
# Read file first to get a correct content_type default value.
self._updateFromFS()
if not kw.has_key('args'):
kw['args'] = args
bound_names['options'] = kw
security=getSecurityManager()
bound_names['user'] = security.getUser()
# Retrieve the value from the cache.
keyset = None
if self.ZCacheable_isCachingEnabled():
# Prepare a cache key.
keyset = {
# Why oh why?
# All this code is cut and paste
# here to make sure that we
# dont call _getContext and hence can't cache
# Annoying huh?
'here': self.aq_parent.getPhysicalPath(),
'bound_names': bound_names}
result = self.ZCacheable_get(keywords=keyset)
if result is not None:
# Got a cached value.
return result
# Execute the template in a new security context.
security.addContext(self)
try:
result = self.pt_render(extra_context=bound_names)
if keyset is not None:
# Store the result in the cache.
self.ZCacheable_set(result, keywords=keyset)
return result
finally:
security.removeContext(self)
return result
InitializeClass(FSMailTemplate)
registerFileExtension('mt', FSMailTemplate)
registerMetaType('Mail Template', FSMailTemplate)
# Copyright (c) 2005-2006 Simplistix Ltd
#
# This Software is released under the MIT License:
# http://www.opensource.org/licenses/mit-license.html
# See license.txt for more details.
from AccessControl import ClassSecurityInfo
from email import Encoders
from email.MIMEBase import MIMEBase
from email.MIMEMultipart import MIMEMultipart
from Globals import InitializeClass
from zope.app.content_types import guess_content_type
from OFS.Image import File
from ZPublisher.HTTPRequest import FileUpload
def cookId(filename):
return filename[max(filename.rfind('/'),
filename.rfind('\\'),
filename.rfind(':'),
)+1:]
class MTMultipart(MIMEMultipart):
security = ClassSecurityInfo()
security.setDefaultAccess('allow')
def __init__(self,mt,mfrom,mto,_subtype='mixed',boundary=None):
MIMEMultipart.__init__(self,_subtype,boundary)
self.mfrom = mfrom
self.mto = mto
self.mt = mt
security.declarePublic('send')
def send(self):
"send ourselves using our MailTemplate's send method"
return self.mt._send(self.mfrom,self.mto,self)
security.declarePublic('add_file')
def add_file(self,theFile=None,data=None,filename=None,content_type=None):
"add a Zope file or Image to ourselves as an attachment"
if theFile and data:
raise TypeError(
'A file-like object was passed as well as data to create a file'
)
if (data or filename) and not (data and filename):
raise TypeError(
'Both data and filename must be specified'
)
if data:
if content_type is None:
content_type, enc=guess_content_type(filename, data)
elif isinstance(theFile,File):
filename = theFile.getId()
data = str(theFile.data)
content_type = content_type or theFile.content_type
elif isinstance(theFile,file):
filename = cookId(theFile.name)
data = theFile.read()
if content_type is None:
content_type,enc = guess_content_type(filename, data)
elif isinstance(theFile,FileUpload):
filename = cookId(theFile.filename)
data=theFile.read()
headers=theFile.headers
if content_type is None:
if headers.has_key('content-type'):
content_type=headers['content-type']
else:
content_type, enc=guess_content_type(filename, data)
else:
raise TypeError('Unknown object type found: %r' % theFile)
msg = MIMEBase(*content_type.split('/'))
msg.set_payload(data)
Encoders.encode_base64(msg)
msg.add_header('Content-Disposition', 'attachment',
filename=filename)
self.attach(msg)
InitializeClass(MTMultipart)
# Copyright (c) 2005-2006 Simplistix Ltd
#
# This Software is released under the MIT License:
# http://www.opensource.org/licenses/mit-license.html
# See license.txt for more details.
import os
from AccessControl import ClassSecurityInfo
from AccessControl import getSecurityManager
from Globals import InitializeClass, package_home
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate, SUPPORTS_WEBDAV_LOCKS
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from BaseMailTemplate import BaseMailTemplate
class MailTemplate(BaseMailTemplate,ZopePageTemplate):
"A ZPT-like template for sending mails"
security = ClassSecurityInfo()
meta_type = 'Mail Template'
_properties = ()
manage_options = ZopePageTemplate.manage_options[0:1] + \
ZopePageTemplate.manage_options[2:]
_default_content_fn = os.path.join(package_home(globals()),
'www', 'default.txt')
security.declareProtected('View management screens','pt_editForm')
pt_editForm = PageTemplateFile('www/mtEdit', globals(),
__name__='pt_editForm')
manage = manage_main = pt_editForm
security.declareProtected('Change Page Templates','pt_editAction')
def pt_editAction(self, REQUEST, mailhost, text, content_type, expand):
"""Change the mailhost and document."""
if SUPPORTS_WEBDAV_LOCKS and self.wl_isLocked():
raise ResourceLockedError, "File is locked via WebDAV"
self.expand=expand
self._setPropValue('mailhost',mailhost)
self.pt_edit(text, content_type)
REQUEST.set('text', self.read()) # May not equal 'text'!
message = "Saved changes."
if getattr(self, '_v_warnings', None):
message = ("<strong>Warning:</strong> <i>%s</i>"
% '<br>'.join(self._v_warnings))
return self.pt_editForm(manage_tabs_message=message)
def om_icons(self):
"""Return a list of icon URLs to be displayed by an ObjectManager"""
icons = ({'path': 'misc_/MailTemplates/mt.gif',
'alt': self.meta_type, 'title': self.meta_type},)
if not self._v_cooked:
self._cook()
if self._v_errors:
icons = icons + ({'path': 'misc_/PageTemplates/exclamation.gif',
'alt': 'Error',
'title': 'This template has an error'},)
return icons
def _exec(self, bound_names, args, kw):
"""Call a Page Template"""
if not kw.has_key('args'):
kw['args'] = args
bound_names['options'] = kw
security=getSecurityManager()
bound_names['user'] = security.getUser()
# Retrieve the value from the cache.
keyset = None
if self.ZCacheable_isCachingEnabled():
# Prepare a cache key.
keyset = {'here': self._getContext(),
'bound_names': bound_names}
result = self.ZCacheable_get(keywords=keyset)
if result is not None:
# Got a cached value.
return result
# Execute the template in a new security context.
security.addContext(self)
try:
result = self.pt_render(extra_context=bound_names)
if keyset is not None:
# Store the result in the cache.
self.ZCacheable_set(result, keywords=keyset)
return result
finally:
security.removeContext(self)
InitializeClass(MailTemplate)
# Copyright (c) 2005-2006 Simplistix Ltd
#
# This Software is released under the MIT License:
# http://www.opensource.org/licenses/mit-license.html
# See license.txt for more details.
from AccessControl import allow_module,allow_class
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from MailTemplate import MailTemplate
from types import ClassType
from urllib import quote
try:
import Products.CMFCore
except ImportError:
pass
else:
import FSMailTemplate
import Products.CMFCore.utils
Products.CMFCore.utils.registerIcon(FSMailTemplate.FSMailTemplate,
'www/fsmt.gif', globals())
def initialize( context ):
context.registerClass(
MailTemplate,
# we use the same permission as page templates
# in order to keep things simple.
permission='Add Page Templates',
constructors=(addMailTemplateForm,
addMailTemplate),
icon='www/mt.gif',
)
addMailTemplateForm = PageTemplateFile(
'www/mtAdd',
globals(),
__name__='addMailTemplateForm'
)
def addMailTemplate(self, id, mailhost=None, text=None,
REQUEST=None, submit=None):
"Add a Mail Template with optional file content."
id = str(id)
if REQUEST is None:
self._setObject(id, MailTemplate(id, text))
ob = getattr(self, id)
if mailhost:
ob._setPropValue('mailhost',mailhost)
return ob
else:
file = REQUEST.form.get('file')
headers = getattr(file, 'headers', None)
if headers is None or not file.filename:
mt = MailTemplate(id, text)
else:
mt = MailTemplate(id, file, headers.get('content_type'))
self._setObject(id, mt)
ob = getattr(self, id)
if mailhost:
ob._setPropValue('mailhost',mailhost)
if submit == " Add and Edit ":
u = ob.absolute_url()
else:
u = ob.aq_parent.absolute_url()
REQUEST.RESPONSE.redirect(u+'/manage_main')
# allow all the email module's public bits
import email
for name in email.__all__:
path = 'email.'+name
allow_module(path)
try:
mod = __import__(path)
except ImportError:
pass
else:
mod = getattr(mod,name)
for mod_name in dir(mod):
obj = getattr(mod,mod_name)
if isinstance(obj,ClassType):
allow_class(obj)
Copyright (c) 2005-2006 Simplistix Ltd
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Mail Templates
Mail Templates provide full access to Python's email package and
allow the body of messages to be generated using Zope's Page
Template technology. They work in both a Zope environment and a CMF
environment. That includes software based on the CMF such as Plone
and CPS.
If you're impatient and want to get going, skip straight to the
examples section further on in this document.
Requirements
Zope 2.8.0 or higher is required.
Installation
Mail Templates should be installed as for any standard Zope
product. Unpack the tarball into the Products directory of your
instance home and restart your instance. More extensive
documentation regarding the installation of Zope Products can be
found on http://www.zope.org.
Overview
Mail Templates can be created either by adding a Mail Template
using the menu in Zope's management information or by adding a
file with a .mt extension in a folder pointed to by a File System
Directory View.
NB: Due a bug in certain specific versions of Zope 2.7.x, you may
receive an Unauthorized error while trying to add a
MailTemplate through the ZMI. If this happens, please update
to a newer version of Zope.
Either type of Mail Template is a callable object that, when
called, renders the body of a mail message and either returns that
as an object that may have further parts added to it or actually
uses a configured Mail Host object to send the message.
Various attributes of the generated mail, such as headers,
charcater encoding and MIME encoding are controlled by a
combination of properties on the Mail Template and parameters
passed to the Mail Template when it is called.
In addition, the MailTemplates product contains security
assertions that make all of Python's email package available for
use in "untrusted" Zope code such as Script (Python) objects.
Properties
The following properties can be added to a Mail Template using the
Properties tab, or an accompanying .metadata file for skin-based
Mail Templates:
mailhost
For ZODB-based Mail Templates, this is set using the drop-down
on the edit screen. For skin-based Mail Templates, this is
specified in the .metadata file associated with the Mail Template.
In either case, it ends up being a slash-seperated path that is
used to traverse to the MailHost object which will be used to
send email.
content_type
As well as influencing the ZPT engine, the property is also used
as the content type for the email generated by this Mail
Template.
mfrom
This should be a string property and should contain the
address which will be used to generate the 'From' header for any
mail sent from this Mail Template.
mto
This should be a string property, for a single address, or a
lines property for multiple addresses, and should contain the
address(es) to which any mail generated using this Mail Template
will be sent. It is also used to generate the 'To' header.
mcc
This should be a string property, for a single address, or a
lines property for multiple addresses, and should contain the
address(es) which will be used to generate the 'CC' header for any
mail sent from this Mail Template.
mbcc
This should be a string property, for a single address, or a
lines property for multiple addresses, and should contain the
address(es) which will be used to generate the 'BCC' header for any
mail sent from this Mail Template.
subject
This should be a string property and its value will be used to
generate the 'Subject' header for any mail sent from this Mail
Template.
headers
This should be a lines property and each line should be of the
form:
HeaderName: value-to-set
The headers specified in this property will be added to any mail
generated by this Mail Template.
encoding
This should be a string property and controls the character set
used to encode any mail generated by this Mail Template. It is
used for both the character set supplied in the MIME encoding
and for encoding the results of rending non-html emails. See the
section on Unicode and Encoded Strings below for more
information.
The default value is that specifed in zope.conf's
default_zpublisher_encoding setting, if avaiable, or iso-8859-15
otherwise.
Methods
In addition to being directly callable, Mail Templates have the
following methods. Directly calling a Mail Template is an alias
for calling the 'send' method described below. The parameters to
both methods are described in the Parameters section further down
in this document.
send
This method renders the email and sends it using the configured
Mail Host.
as_message
This method is used when you want to build multi-part emails
such as those with both text and html versions and those with
attached files.
It renders the body of the MailTemplate as an email.MIMEText
object, creates a MailTemplates.MTMultipart containing the
headers for the rendered email, attaches the body to it and
returns it.
An MTMultipart object is identical to an email.MIMEMultipart
object except that is has two additional methods:
send
This sends a rendered version of the message described by the
MTMultipart object using the Mail Host configured in the Mail
Template that created it.
add_file
This method is used to add attachments to the message.
It can take either one parameter or two keyword parameters.
In either case, an optional content_type keyword parameter
can be specified.
If one parameter is passed, the it should be a Zope File or Image
object, ZPublisher FileUpload object or a python file object.
If two keyword parameters are passed, they should be 'data'
and 'filename'. 'data' should be a string containing the body
of the attachment. 'filename' should be the file name to use
for the attachment.
In all cases, the content_type parameter can be used to
override any guesswork in setting the content type for the
attachment.
In addition to the paramters described below, the following
keyword parameters can also optionally be used to control
aspects of the multi-part message creation:
subtype
This is a string argument specifying the subtype to use for
the content type of the multi part message. The default is
calculated to be something sensible but could, for example, be
set to 'alternative' to give a 'multipart/alternative' message.
boundary
This is a tring specifying the boundary to use for multi-part
messages. In general, this should not be specified, but can be
handy when writing unit tests.
Parameters
The following parameter may optionally be passed as keyword
arguments to both the 'send' and 'as_message' methods:
mfrom
This should be a string and should contain the address which
will be used to generate the 'From' header for the message being
generated or sent.
mto
This should be a string, for a single address, or a sequence of
strings, for multiple addresses and should contain the
address(es) to which the message will be sent. It is also used
to generate the 'To' header.
mcc
This should be a string, for a single address, or a sequence of
strings, for multiple addresses and should contain the
address(es) which will be used to generate the 'CC' header for
the mail currently being generated or sent.
mbcc
This should be a string, for a single address, or a sequence of
strings, for multiple addresses and should contain the
address(es) which will be used to generate the 'BCC' header for
the mail currently being generated or sent.
subject
This should be a string and will be used to generate the
'Subject' header for the mail currently being generated or
sent.
headers
This should be a dictionary mapping header names to header
values. Both the names and values should be strings.
The headers specified in this dictionary will be added to the
mail currently being generated or sent.
encoding
This parameter controls the character set used to encode the mail
currently being generated or sent. It is used for both the
character set supplied in the MIME encoding and for encoding the
results of rending non-html emails. See the section on Unicode
and Encoded Strings below for more information.
The default value is that specifed in zope.conf's
default_zpublisher_encoding setting, if avaiable, or iso-8859-15
otherwise.
Precedence During Generation
When generating an email, headers and the charset, mto and mfrom
parameters can come from several places; the parameters passed to
the send or as_message method, the properties of the Mail Template
itself, or the headers parameter or property. The order lookup is
as follows:
1. The parameters passed to the send or as_message method
2. For headers only, the content of the headers parameter passed
to the send or as_message method.
3. The properties of the Mail Template object
4. For headers only, the content of the headers property of the
Mail Template object.
Required Parameters
In order for a Mail Template to be successfully rendered, the
following parameters must be obtained through one of the four
options listed above:
- mto
- mfrom
- subject
If any of the above parameters cannot be obtained, an exception
will be raised.
Unicode and Encoded Strings
Please note that you can run into problems with Mail Templates in
exactly the same way as for normal Page Templates if you attempt
to insert strings encoded in anything other than us-ascii or
unicodes that cannot be encoded into us-ascii.
The way any problematic values should be handles depends on what
the content_type of your Mail Template is set to:
If content_type is set to text/html, then:
- any unicodes should be encoded using the character set you
intend to use for the final mail encoding
- you are responsible for ensuring that any strings are encoded
with the correct character set, which should be that used for
the final mail encoding.
If content_type is set to anything else:
- all string-like data inserted into the Mail Template during
rendering must be in the form of unicode objects.
Finally, the encoding property or parameter should match the
character set you use to encode or decode any string-like objects.
Here are some simple examples to illustrate correct and incorrect
usage. While 'encoding' is passed as a parameter here, you may
find it more convenient to set as a property or, if you are using
Zope 2.7.8, Zope 2.8.2 or above, but specifying the
default_zpublisher_encoding in your zope.conf file.
Bad Example 1:
Mail Template with content_type set to 'text/plain':
Your currency is <tal:x replace="options/currency"/>
Script Python:
return container.test_mt(currency=u'£'.encode('utf-8'),
encoding='utf-8')
Should have been:
Your currency is <tal:x replace="python:options.currency.decode('utf-8')"/>
...or alternatively:
return container.test_mt(currency=u'£',
encoding='utf-8')
Bad Example 2:
Mail Template with content_type set to 'text/html':
Your currency is <tal:x replace="options/currency"/>
Script Python:
return container.test_mt(currency=u'£',
encoding='utf-8')
Should have been:
Your currency is <tal:x replace="python:options.currency.encode('utf-8')"/>
...or alternatively:
return container.test_mt(currency=u'£'.encode('utf-8'),
encoding='utf-8')
Examples
The following are some simple contrived examples to show some of
the possible uses of Mail Templates. If you wish to test with
these, you will need to change the 'mto' addresses.
Simple Example
This example sends a simple templated mail to a specific
address.
Add the following to a Mail Template called my_mt:
<tal:body xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
>Dear <tal:x replace="options/mto"/>,
<tal:x replace="user/getId"/> would like to thank you for
your interest in:
<tal:x replace="root/absolute_url"/>
<tal:x replace="options/message"/>
cheers,
The Web Team
</tal:body>
Now add a Script (Python) in the same folder containing the
following:
container.my_mt(
mfrom='webmaster@example.com',
mto='user@example.com',
subject='This is a test!',
message='This is a test!'
)
return 'Mail Sent!'
..and now hit the Test tab of the Script (Python)!
File System Directory View Example
This example sends a simple templated mail to all the members of
a CMF portal. Please make sure your portal has at least one
member before you try this or you won't get any output!
Add a file called my_mt.mt to a filesystem directory that is
mapped though to an FSDV containing the following:
<tal:body xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
>Dear <tal:x replace="options/member/getUserName"/>,
Thankyou for you interest in our portal!
cheers,
The Web Team
</tal:body>
Now add a file called my_mt.mt.metadata in the same directory
containing the following:
[default]
mailhost=MailHost
mfrom=webmaster@example.com
subject=Thankyou for your interest!
Finally, add a file alled send_mails.py in the same directory
containing the following:
for member in context.portal_membership.listMembers():
context.my_mt(
member=member,
mto=member.getProperty('email')
)
Then, to test, visit your equivalent of the following while
logged in as a Manager:
http://localhost:8080/myportal/send_mails
Attachment Example
This example sends a simple templated mail with a file attached
to it to a specific address.
Add a Mail Template called my_mt and containing the following:
<tal:body xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
>Dear <tal:x replace="options/mto"/>,
Please find attached the file you requested.
cheers,
The Web Team
</tal:body>
Now add a Zope File object containing the file of your choice
but called 'myfile.bin'.
Finally add a Script (Python) called send_mail containing the
following:
msg = container.my_mt.as_message(
mfrom='from@example.com',
mto='to1@example.com',
subject='Your requested file',
)
msg.add_file(container['myfile.bin'])
msg.send()
return 'Mail Sent!'
When you tests this Script (Python), the two addresses in the
mto parameter will receive a multi-part MIME-encoded email with
your file attached.
Dynamic Subject Line Example
This example shows how to use a templated subject line.
Add the following to a Mail Template called my_mt:
<tal:body xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
>Dear <tal:x replace="options/mto"/>,
Welcome to our site!
cheers,
The Web Team
</tal:body>
Add a string property called 'subject' containing
'Welcome to %s', and a string property called 'mfrom' containing
'webmaster@example.com', to the my_mt Mail Template.
Now add a Script (Python) in the same folder containing the
following:
container.my_mt(
mto='user@example.com',
subject=container.my_mt.subject % container.absolute_url()
)
return 'Mail Sent!'
..and now hit the Test tab of the Script (Python)!
Licensing
Copyright (c) 2005-2006 Simplistix Ltd
This Software is released under the MIT License:
http://www.opensource.org/licenses/mit-license.html
See license.txt for more details.
Changes
1.1.1
- Fixed bug that meant rendering a MailTemplate or
FSMailTemplate would set the content type of the RESPONSE to
that of the MailTemplate.
- Fixed a deprecation warning with Zope 2.9 at the expense of
compatability with Zope 2.7 or less.
1.1.0
- Corrected and wrote tests for the examples in readme.txt.
(https://secure.simplistix.co.uk/support/issue185)
Thanks to Nicolas Georgakopoulos for testing these!
- Fixed the security declaration for BaseMailTemplate's _send
method.
Thanks to Jens Quade for the patch!
- Fixed a critical bug in MTMultipart's security declarations
that prevented add_file from being used within python
scripts.
(https://secure.simplistix.co.uk/support/issue184)
Thanks to Nicolas Georgakopoulos for reporting this!
- Fixed a bug where specifying content_type in a .metadata
accompanying an FSMailTemplate caused errors if you tried to
customise that FSMailTemplate.
- Check that the mail host specified is really a mail host and
give feedback on the edit screen if this isn't the case.
(https://secure.simplistix.co.uk/support/issue181)
Thanks to Nicolas Georgakopoulos for reporting this!
- Added support to the add_file method so that files can be
added using a string of data and a string containing the
filename.
- Added the ability to explicitly set the content type when
calling add_file.
- Fixed the tests to run in Zope 2.8, CMF 1.5 and
Zope 2.6, CMF 1.4.
- Implemented _readFile in FSMailTemplate. This is primarily to
work around a bug in CMF 1.4, but also means _readFile is
much simpler for FSMailTemplates and there won't be suprises later.
1.0.0
- Added encoding support and documentation explaining how
unicode and encoded string data must be handled.
- A date header is now set on all emails sent.
- An exception is now raised if certain required parameters
cannot be looked up anywhere.
- Made header lookup with respect to sourcing from parameter,
properties and the headers parameter/property work as
expected.
- Added documentation.
- as_message now returns an object that has both a 'send'
method and an 'add_file' method.
- Security declarations to allow the use of the whole email
package from restricted code.
- Corrected copyright statements.
- Add support for MaildropHost
- give friendlier error messages when no MailHost is specified
or where the specified one can no longer be found.
- Prevent \r showing up in rendered mails
- Make tests run on both unix and windows.
- Added new icons
0.8.0
- Initial release
# Copyright (c) 2005-2006 Simplistix Ltd
#
# This Software is released under the MIT License:
# http://www.opensource.org/licenses/mit-license.html
# See license.txt for more details.
<tal:body xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
>Dear <tal:x replace="options/mto"/>,
<tal:x replace="user/getId"/> would like to thank you for
your interest in:
<tal:x replace="root/absolute_url"/>
<tal:x replace="options/message"/>
cheers,
The Web Team
</tal:body>
container.my_mt(
mfrom='webmaster@example.com',
mto='user@example.com',
subject='This is a test!',
message='This is a test!'
)
return 'Mail Sent!'
Content-Type: text/plain; charset="iso-8859-15"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Date:
From: webmaster@example.com
Subject: This is a test!
To: user@example.com
Dear user@example.com,
Test User would like to thank you for
your interest in:
http://foo
This is a test!
cheers,
The Web Team
Content-Type: text/plain; charset="iso-8859-15"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Date:
From: webmaster@example.com
Subject: Thankyou for your interest!
To: member@example.com
Dear Test Member,
Thankyou for you interest in our portal!
cheers,
The Web Team
This is the file attachment
\ No newline at end of file
<tal:body xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
>Dear <tal:x replace="options/mto"/>,
Please find attached the file you requested.
cheers,
The Web Team
</tal:body>
msg = container.my_mt.as_message(
mfrom='from@example.com',
mto='to1@example.com',
subject='Your requested file',
boundary='111' # for testing only, so we get a consistent boundary
)
msg.add_file(container['myfile.bin'])
msg.send()
return 'Mail Sent!'
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="111"; charset="iso-8859-15"
Content-Transfer-Encoding: quoted-printable
Date:
From: from@example.com
Subject: Your requested file
To: to1@example.com
--111
Content-Type: text/plain; charset="iso-8859-15"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Dear to1@example.com,
=
Please find attached the file you requested.
=
cheers,
The Web Team
--111
Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="myfile.bin"
VGhpcyBpcyB0aGUgZmlsZSBhdHRhY2htZW50
--111--
\ No newline at end of file
<tal:body xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
>Dear <tal:x replace="options/mto"/>,
Welcome to our site!
cheers,
The Web Team
</tal:body>
container.my_mt(
mto='user@example.com',
subject=container.my_mt.subject % container.absolute_url()
)
return 'Mail Sent!'
Content-Type: text/plain; charset="iso-8859-15"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Date:
From: webmaster@example.com
Subject: Welcome to http://foo
To: user@example.com
Dear user@example.com,
Welcome to our site!
cheers,
The Web Team
<tal:body xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
>Dear <tal:x replace="options/member/getUserName"/>,
Thankyou for you interest in our portal!
cheers,
The Web Team
</tal:body>
[default]
mailhost=MailHost
mfrom=webmaster@example.com
subject=Thankyou for your interest!
for member in context.portal_membership.listMembers():
context.my_mt(
member=member,
mto=member.getProperty('email')
)
<tal:body xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
>Test Body
</tal:body>
[default]
mailhost = MailHost
subject = Hello %s there
mfrom = from@example.com
charset = latin-1
content_type = text/notplain
Content-Type: text/notplain; charset="iso-8859-15"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Bcc: bcc@example.com
Cc: cc@example.com
Date:
From: from@example.com
Subject: Hello out there
To: to@example.com, to2@example.com
Test Body
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="111"; charset="iso-8859-15"
Content-Transfer-Encoding: quoted-printable
Bcc: bcc@example.com
Cc: cc@example.com
Date:
From: from@example.com
Subject: Hello out there
To: to@example.com, to2@example.com
--111
Content-Type: text/plain; charset="iso-8859-15"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Test Body
--111
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="test.txt"
A Test Attachment
--111--
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="111"; charset="iso-8859-15"
Content-Transfer-Encoding: quoted-printable
Bcc: bcc@example.com
Cc: cc@example.com
Date:
From: from@example.com
Subject: Hello out there
To: to@example.com, to2@example.com
--111
Content-Type: text/plain; charset="iso-8859-15"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Test Body
--111
Content-Type: text/plain
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="test.txt"
QSBUZXN0IEF0dGFjaG1lbnQ=
--111--
\ No newline at end of file
Content-Type: text/plain; charset="iso-8859-15"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Bcc: bcc@example.com
Cc: cc@example.com
Date:
From: from@example.com
Subject: Hello out there
To: to@example.com, to2@example.com
X-Mailer: MailTemplates
Test Body
Content-Type: text/plain; charset="iso-8859-15"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Bcc: bcc@example.com
Cc: cc@example.com
Date:
From: from@example.com
Subject: Hello out there
To: to@example.com, to2@example.com
X-Mailer: MailTemplates
X-Mailer2: MailTemplates
Test Body
Content-Type: text/plain; charset="iso-8859-15"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Bcc: bcc@example.com
Cc: cc@example.com
Date:
From: from@example.com
Subject: Hello out there
To: to@example.com, to2@example.com
Test Body
Content-Type: text/plain; charset="iso-8859-15"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Date:
From: from@example.com
Subject: Test Subject
To: to@example.com
Test Body
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Date:
From: from@example.com
Subject: Test Subject
To: to@example.com
VGVzdCDCo8KjwqMK
Content-Type: text/html; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Date:
From: from@example.com
Subject: Test Subject
To: to@example.com
VGVzdCDCo8KjwqMK
A Test Attachment
\ No newline at end of file
# Copyright (c) 2005-2006 Simplistix Ltd
#
# This Software is released under the MIT License:
# http://www.opensource.org/licenses/mit-license.html
# See license.txt for more details.
import os
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager
from AccessControl.User import system as SystemUser,SimpleUser
from cStringIO import StringIO
from OFS.Folder import Folder
from Products.MailHost.MailHost import MailHost
from test_MailTemplate import DummyMailHost,Zope,get_transaction
from Testing.makerequest import makerequest
from unittest import TestCase,TestSuite,makeSuite,main
try:
import Products.CMFCore
except ImportError:
# no CMF, no use ;-)
class TestFSMailTemplate(TestCase):
pass
else:
from Products.CMFCore.DirectoryView import addDirectoryViews
from Products.CMFCore.tests.base.testcase import FSDVTest
from Products.CMFCore.tests.base.dummy import DummyFolder
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass
class DummyMember:
security = ClassSecurityInfo()
security.declareObjectPublic()
security.setDefaultAccess('allow')
security.declarePublic('getUserName')
def getUserName(self):
return 'Test Member'
security.declarePublic('getProperty')
def getProperty(self,name):
return 'member@example.com'
InitializeClass(DummyMember)
class DummyMembershipTool:
security = ClassSecurityInfo()
security.declareObjectPublic()
security.setDefaultAccess('allow')
security.declarePublic('listMembers')
def listMembers(self):
return (DummyMember(),)
InitializeClass(DummyMembershipTool)
class TestFSMailTemplate(FSDVTest):
_sourceprefix = os.path.dirname(__file__)
def setUp(self):
FSDVTest.setUp(self)
self.app = makerequest(Zope.app())
self._registerDirectory()
ob = self.ob = self.app
addDirectoryViews(ob, self._skinname, self.tempname)
self.r = self.app.REQUEST
self.r.other['URL1'] = 'http://foo/test_mt'
self._add= self.app.manage_addProduct['MailTemplates'].addMailTemplate
self.folder = Folder('folder')
if getattr(self.app,'test_mt',None):
self.app.manage_delObjects(ids=['test_mt'])
if getattr(self.app,'MailHost',None):
self.app.manage_delObjects(ids=['MailHost'])
self.MailHost = self.app.MailHost = DummyMailHost()
newSecurityManager( None, SystemUser )
def tearDown(self):
noSecurityManager()
get_transaction().abort()
self.app._p_jar.close()
try:
FSDVTest.tearDown(self)
except OSError:
# waggh, on windows, files in .svn get locked for some reason :-(
pass
def test_render(self):
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com','to2@example.com'),
filename='mail_FSSendSimple.txt')
self.ob.fake_skin.test.send(subject=self.ob.fake_skin.test.subject % 'out',
mcc=('cc@example.com',),
mbcc=('bcc@example.com',),
headers={
'To':('to@example.com','to2@example.com'),
'Subject':'cheese',
})
self.MailHost.checkSent()
# check we're not setting a content type
self.failIf(self.r.RESPONSE.headers.get('content-type'),
self.r.RESPONSE.headers)
def test_properties(self):
self.assertEqual(self.ob.fake_skin.test.mailhost,'MailHost')
self.assertEqual(self.ob.fake_skin.test.subject,'Hello %s there')
self.assertEqual(self.ob.fake_skin.test.mfrom,'from@example.com')
def test_zodbclone(self):
from Products.MailTemplates.MailTemplate import MailTemplate
clone = self.ob.fake_skin.test._createZODBClone()
self.failUnless(isinstance(clone,MailTemplate),'Clone not a MailTemplate!')
self.assertEqual(self.ob.fake_skin.test.read(),clone.read())
self.assertEqual(clone.getProperty('mailhost'),None)
self.assertEqual(clone.mailhost,'MailHost')
self.assertEqual(clone.getProperty('subject'),'Hello %s there')
self.assertEqual(clone.getProperty('mfrom'),'from@example.com')
self.assertEqual(clone.content_type,'text/notplain')
def test_view_manage_workspace(self):
from zExceptions import Redirect
try:
self.assertRaises(self.ob.fake_skin.test.manage_workspace(self.r))
except Redirect,r:
# this may appear to be incorrect, but http://foo/test_mt
# is what we set as REQUEST['URL1']
self.assertEqual(r.args,('http://foo/test_mt/manage_main',))
self.ob.fake_skin.test.manage_main()
# ugh, okay, so we can't really test for security, but lets
# test for the missing docstring that was causing problems!
self.failUnless(self.ob.fake_skin.test.__doc__)
def test_example2(self):
# login
noSecurityManager()
self.app.aq_chain[-1].id = 'testing'
newSecurityManager(
None,
SimpleUser('Test User','',('Manager',),[]).__of__(self.app)
)
try:
# setup
self.app.portal_membership = DummyMembershipTool()
# set expected
self.MailHost.setExpected(mfrom='webmaster@example.com',
mto='member@example.com',
filename='example2.txt')
# test
self.ob.fake_skin.send_mails()
finally:
# logout
noSecurityManager()
newSecurityManager( None, SystemUser )
def test_suite():
return TestSuite((
makeSuite(TestFSMailTemplate),
))
if __name__ == '__main__':
main(defaultTest='test_suite')
# -*- coding: latin-1 -*-
# Copyright (c) 2005-2006 Simplistix Ltd
#
# This Software is released under the MIT License:
# http://www.opensource.org/licenses/mit-license.html
# See license.txt for more details.
import os
try:
import Zope2 as Zope
except ImportError:
import Zope
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager
from AccessControl.User import system as SystemUser, SimpleUser
from cStringIO import StringIO
from difflib import unified_diff
from OFS.Folder import Folder
from Products.MailHost.MailHost import MailHost
from Testing.makerequest import makerequest
from unittest import TestCase,TestSuite,makeSuite,main
try:
# Zope 2.8 only
from transaction import get as get_transaction
except ImportError:
# Zope 2.7 only, allows get_transaction
# to be imported from test_FSMailTemplate.
get_transaction = get_transaction
test_folder = os.path.dirname(__file__)
class DummyFieldStorage:
def __init__(self,filename,value):
self.filename = filename
self.value = value
self.file = StringIO(value)
self.content_type = None
self.headers = {}
class DummyMailHost(MailHost):
sent = False
def setExpected(self,mfrom,mto,filename):
self.mfrom = mfrom
self.mto = mto
self.messageText = open(
os.path.join(test_folder,filename)
).read().replace('\r','')
self.filename = filename
def getId(self):
return 'MailHost'
def title_and_id(self):
return 'MHTID'
def assertEqual(self,x,y,message=None,field=None):
if x!=y:
if message:
raise AssertionError(message)
error = '%r!=%r' % (x,y)
if field:
error = field+':'+error
raise AssertionError(error)
def _send(self,mfrom,mto,messageText):
self.assertEqual(self.mfrom,mfrom,field='mfrom')
self.assertEqual(self.mto,mto,field='mto')
expected_data = self.messageText.strip().split('\n')
actual_data = messageText.strip().split('\n')
# ignore dates
for i in range(len(actual_data)):
if actual_data[i].startswith('Date:'):
actual_data[i]='Date:'
diff = tuple(unified_diff(
expected_data,
actual_data,
self.filename,
'Test results',
))
if diff:
raise AssertionError(
'Mail sent was not as expected:\n\n'+'\n'.join(diff)
)
self.sent = True
def checkSent(self,value=True):
if value:
error = "Mail not sent"
else:
error = "Mail sent when it shouldn't have been"
self.assertEqual(self.sent,value,error)
class DummyMailDropHost(DummyMailHost):
meta_type = 'Maildrop Host'
class TestMailTemplate(TestCase):
def setUp(self):
self.app = makerequest(Zope.app())
self.r = self.app.REQUEST
self.r.other['URL1'] = 'http://foo/test_mt'
self._add= self.app.manage_addProduct['MailTemplates'].addMailTemplate
if getattr(self.app,'test_mt',None):
self.app.manage_delObjects(ids=['test_mt'])
if getattr(self.app,'MailHost',None):
self.app.manage_delObjects(ids=['MailHost'])
self.MailHost = self.app.MailHost = DummyMailHost()
o = list(self.app._objects)
o.append({'meta_type': 'Mail Host', 'id': 'MailHost'})
self.app._objects = tuple(o)
newSecurityManager( None, SystemUser )
def tearDown(self):
noSecurityManager()
get_transaction().abort()
self.app._p_jar.close()
def makeFileUpload(self,filename='test.txt',value='test text',
diskname=''):
if diskname:
filename = diskname
value = open(
os.path.join(test_folder,diskname)
).read().replace('\r','').strip()
from ZPublisher.HTTPRequest import FileUpload
return FileUpload(DummyFieldStorage(
filename,
value
))
def checkContent(self,text='test text'):
if text is None:
text = open(os.path.join(test_folder,'..','www','default.txt')).read()
self.assertEqual(
self.app.test_mt.document_src({'raw':1}),
text
)
# Test Adding
def test_addAddForm(self):
self.app.manage_addProduct['MailTemplates'].addMailTemplateForm()
def test_addAddFormNoMailHosts(self):
self.app.manage_delObjects(ids=['MailHost'])
res = self.app.manage_addProduct['MailTemplates'].addMailTemplateForm()
self.failUnless(
res.find(
'<option value="MailHost">MHTID</option>'
)==-1
)
def test_addAddFormMailHost(self):
self.app._objects = ({'meta_type': 'Mail Host', 'id': 'MailHost'},)
res = self.app.manage_addProduct['MailTemplates'].addMailTemplateForm()
self.failIf(
res.find(
'<option value="MailHost">MHTID</option>'
)==-1
)
def test_addAddFormMailDropHost(self):
if getattr(self.app,'MailHost',None):
self.app.manage_delObjects(ids=['MailHost'])
self.MailHost = self.app.MailHost = DummyMailDropHost()
self.app._objects = ({'meta_type': 'Maildrop Host', 'id': 'MailHost'},)
res = self.app.manage_addProduct['MailTemplates'].addMailTemplateForm()
self.failIf(
res.find(
'<option value="MailHost">MHTID</option>'
)==-1
)
def test_addNoREQUEST(self):
self._add('test_mt','MailHost')
# check settings
self.assertEqual(self.app.test_mt.expand,0)
self.assertEqual(self.app.test_mt.mailhost,'MailHost')
self.assertEqual(self.app.test_mt.content_type,'text/plain')
# check default content
self.checkContent(None)
def test_addNoMailHostSelected(self):
self._add('test_mt',REQUEST=self.r)
# check settings
self.assertEqual(self.app.test_mt.expand,0)
self.assertEqual(self.app.test_mt.mailhost,None)
self.assertEqual(self.app.test_mt.content_type,'text/plain')
# check default content
self.checkContent(None)
# check the error we get when we try to send
self.assertRaises(
RuntimeError,self.app.test_mt,
mfrom='from@example.com',
mto='to@example.com',
subject='Test Subject',
)
# no put a mogus mailhost in and check we get the same error
self.app.test_mt.mailhost='bogus'
self.assertRaises(
RuntimeError,self.app.test_mt,
mfrom='from@example.com',
mto='to@example.com',
subject='Test Subject',
)
def test_add(self,body = None):
text = open(os.path.join(test_folder,'..','www','default.txt')).read()
if body is not None:
text = text[:-12] + body + text[-11:]
self._add('test_mt','MailHost',text=text,REQUEST=self.r)
else:
self._add('test_mt','MailHost',REQUEST=self.r)
self.assertEqual(
self.r.RESPONSE.headers,
{'status': '302 Moved Temporarily', 'location': 'http://foo/manage_main'}
)
self.mt = self.app.test_mt
# check settings
self.assertEqual(self.mt.expand,0)
self.assertEqual(self.mt.mailhost,'MailHost')
self.assertEqual(self.mt.content_type,'text/plain')
# check default content
self.assertEqual(
self.app.test_mt.read(),
text
)
# check default content type is text/plain
self.assertEqual(self.app.test_mt.content_type,'text/plain')
def test_addFile(self):
self.r.form['file'] = self.makeFileUpload()
self._add('test_mt','MailHost',REQUEST=self.r)
# check settings
self.assertEqual(self.app.test_mt.expand,0)
self.assertEqual(self.app.test_mt.mailhost,'MailHost')
self.assertEqual(self.app.test_mt.content_type,'text/plain')
# check default content
self.checkContent()
def test_addEdit(self):
self._add('test_mt','MailHost',REQUEST=self.r,submit=' Add and Edit ')
self.assertEqual(
self.r.RESPONSE.headers,
{'status': '302 Moved Temporarily', 'location': 'http://foo/test_mt/manage_main'}
)
# check settings
self.assertEqual(self.app.test_mt.expand,0)
self.assertEqual(self.app.test_mt.mailhost,'MailHost')
self.assertEqual(self.app.test_mt.content_type,'text/plain')
# check default content
self.checkContent(None)
def test_addEditFile(self):
self.r.form['file'] = self.makeFileUpload()
self._add('test_mt','MailHost',REQUEST=self.r,submit=' Add and Edit ')
self.assertEqual(
self.r.RESPONSE.headers,
{'status': '302 Moved Temporarily', 'location': 'http://foo/test_mt/manage_main'}
)
# check settings
self.assertEqual(self.app.test_mt.expand,0)
self.assertEqual(self.app.test_mt.mailhost,'MailHost')
self.assertEqual(self.app.test_mt.content_type,'text/plain')
# check default content
self.checkContent()
# Test Properties Tab
# Not much here, as we assume PropertyManager does its job ;-)
def test_PropertiesForm(self):
self.test_add()
self.mt.manage_propertiesForm()
def test_PropertiesStartsEmpty(self):
self.test_add()
self.failIf(self.mt.propertyMap())
# Test Test tab, well, actually, make sure it's not there ;-)
def test_NoTestTab(self):
from Products.MailTemplates.MailTemplate import MailTemplate
for option in MailTemplate.manage_options:
if option['label']=='Test':
self.fail('Test label found')
self.failIf(MailTemplate.ZScriptHTML_tryForm, 'try form not None')
# Test Editing
def test_editForm(self):
self.test_add()
self.mt.pt_editForm()
def test_editFormMailHostGone(self):
self.test_add()
self.app.manage_delObjects('MailHost')
r = self.mt.pt_editForm()
self.failIf(
r.find(
"""<option selected="selected" value="MailHost">'MailHost' is no longer valid!</option>"""
)==-1,'No warning for MailHost being invalid found in:\n'+r
)
def test_editAction(self):
self.test_add()
self.mt.pt_editAction(REQUEST=self.r,
mailhost='MH2',
text='new text',
content_type='text/fish',
expand=1)
self.assertEqual(self.mt.expand,1)
self.assertEqual(self.mt.mailhost,'MH2')
self.assertEqual(self.mt.content_type,'text/fish')
self.checkContent('new text')
def test_view_manage_workspace(self):
self.test_add()
from zExceptions import Redirect
try:
self.assertRaises(self.mt.manage_workspace(self.r))
except Redirect,r:
# this may appear to be incorrect, but http://foo/test_mt
# is what we set as REQUEST['URL1']
self.assertEqual(r.args,('http://foo/test_mt/pt_editForm',))
# ugh, okay, so we can't really test for security, but lets
# test for the missing docstring that was causing problems!
self.failUnless(self.mt.__doc__)
def test_view_manage_main(self):
self.test_add()
# for some bizare reason the output differs by a newline the first time these are called :-(
self.mt.manage_main()
self.mt.pt_editForm()
self.assertEqual(self.mt.manage_main(),self.mt.pt_editForm())
# Test Sending
def testSendSimple(self):
self.test_add('Test Body')
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com', 'to2@example.com',
'cc@example.com', 'bcc@example.com'),
filename='mail_SendSimple.txt')
self.mt.send(
mfrom='from@example.com',
mto=('to@example.com','to2@example.com'),
mcc=('cc@example.com',),
mbcc=('bcc@example.com',),
subject='Hello out there',
)
self.MailHost.checkSent()
# check we're not setting a content type
self.failIf(self.r.RESPONSE.headers.get('content-type'),
self.r.RESPONSE.headers)
def testMailHostNotAMailHost(self):
self.test_add('Test Body')
self.app.MailHost='Hahaha, not a MailHost'
self.assertRaises(
RuntimeError,
self.mt.send,
mfrom='from@example.com',
mto=('to@example.com','to2@example.com'),
mcc=('cc@example.com',),
mbcc=('bcc@example.com',),
subject='Hello out there',
)
def _shouldFail(self,error,**params):
self.test_add('Test Body')
try:
self.mt.send(**params)
except TypeError,e:
self.assertEqual(e.args[0],error)
else:
self.fail('Mail sent even though params missing')
self.MailHost.checkSent(False)
def testSendMissingParams1(self):
self._shouldFail(
'The following parameters were required by not specified: subject',
mto='to@example.com',
mfrom='from@example.com'
)
def testSendMissingParams2(self):
self._shouldFail(
'The following parameters were required by not specified: mfrom',
mto='to@example.com',
subject='Test Subject'
)
def testSendMissingParams3(self):
self._shouldFail(
'The following parameters were required by not specified: mto',
mfrom='from@example.com',
subject='Test Subject'
)
def testSendMissingParamsAll(self):
self._shouldFail(
'The following parameters were required by not specified: mfrom, mto, subject',
)
def testSendProperties(self):
self.test_add('Test Body')
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com', 'to2@example.com',
'cc@example.com', 'bcc@example.com'),
filename='mail_SendSimple.txt')
for name,type,value in (
('mfrom','string','from@example.com'),
('mto','string','to@example.com, to2@example.com'),
('mbcc','lines',('bcc@example.com',)),
('subject','string','Hello out there'),
('headers','lines',
('Cc:cc@example.com',)),
):
self.mt.manage_addProperty(name,value,type)
self.mt.send()
self.MailHost.checkSent()
def testSendHeadersDict(self):
self.test_add('Test Body')
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com','to2@example.com',
'cc@example.com', 'bcc@example.com'),
filename='mail_SendHeaders.txt')
self.mt.send(
headers = {
'From':'from@example.com',
'To':('to@example.com','to2@example.com'),
'Cc':('cc@example.com',),
'Bcc':('bcc@example.com',),
'Subject':'Hello out there',
'X-Mailer':'MailTemplates',
}
)
self.MailHost.checkSent()
def testSendParametersOverrideHeadersDictOverridesProperties(self):
self.test_add('Test Body')
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com','to2@example.com',
'cc@example.com', 'bcc@example.com'),
filename='mail_SendHeaders2.txt')
for name,type,value in (
('mfrom','string','from@example.com'),
('mto','string','frog@example.com'),
('mcc','lines',('cc@example.com',)),
('mbcc','lines',('bcc@example.com',)),
('subject','string','Hello %s there'),
('headers','lines',(
'X-Mailer: MailTemplates',
'X-Mailer2: MailTemplatesBad',
))
):
self.mt.manage_addProperty(name,value,type)
self.mt.send(subject=self.mt.subject % 'out',
headers={
'To':('to@example.com','to2@example.com'),
'Subject':'cheese',
'X-Mailer2':'MailTemplates',
})
self.MailHost.checkSent()
def testSendParametersGoToOptions(self):
self.test_add('Test <tal:x replace="options/body"/>')
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com','to2@example.com',
'cc@example.com', 'bcc@example.com'),
filename='mail_SendSimple.txt')
for name,type,value in (
('mfrom','string','from@example.com'),
('mto','string','frog@example.com'),
('mcc','lines',('cc@example.com',)),
('mbcc','lines',('bcc@example.com',)),
('subject','string','Hello %s there'),
):
self.mt.manage_addProperty(name,value,type)
self.mt.send(subject=self.mt.subject % 'out',
headers={
'To':('to@example.com','to2@example.com'),
'Subject':'cheese',
},
body='Body')
self.MailHost.checkSent()
def testPropertiesParametersAndSubstitution(self):
self.test_add('Test Body')
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com', 'to2@example.com',
'cc@example.com', 'bcc@example.com'),
filename='mail_SendSimple.txt')
for name,type,value in (
('mfrom','string','from@example.com'),
('mto','string','to@example.com, to2@example.com'),
('mcc','lines',('cc@example.com',)),
('mbcc','lines',('bcc@example.com',)),
('subject','string','Hello %s there'),
):
self.mt.manage_addProperty(name,value,type)
self.mt.send(subject=self.mt.subject % 'out')
self.MailHost.checkSent()
def testGetMessage(self):
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
self.test_add('Test <tal:x replace="options/body"/>')
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com','to2@example.com'),
filename='mail_SendAttachment.txt')
for name,type,value in (
('mfrom','string','from@example.com'),
('mto','string','frog@example.com'),
('mcc','lines',('cc@example.com',)),
('mbcc','lines',('bcc@example.com',)),
('subject','string','Hello %s there'),
):
self.mt.manage_addProperty(name,value,type)
msg = self.mt.as_message(subject=self.mt.subject % 'out',
headers={
'To':('to@example.com','to2@example.com'),
'Subject':'cheese',
},
body='Body',
boundary='111',
subtype='alternative')
self.failUnless(isinstance(msg,MIMEMultipart))
attachment = MIMEText('A Test Attachment',_subtype='plain')
attachment.add_header('Content-Disposition', 'attachment', filename='test.txt')
msg.attach(attachment)
msg.send()
self.MailHost.checkSent()
def _addFileSetup(self):
from email.MIMEMultipart import MIMEMultipart
self.test_add('Test <tal:x replace="options/body"/>')
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com','to2@example.com'),
filename='mail_SendFile.txt')
for name,type,value in (
('mfrom','string','from@example.com'),
('mto','string','frog@example.com'),
('mcc','lines',('cc@example.com',)),
('mbcc','lines',('bcc@example.com',)),
('subject','string','Hello %s there'),
):
self.mt.manage_addProperty(name,value,type)
msg = self.mt.as_message(subject=self.mt.subject % 'out',
headers={
'To':('to@example.com','to2@example.com'),
'Subject':'cheese',
},
body='Body',
boundary='111',
subtype='alternative')
self.failUnless(isinstance(msg,MIMEMultipart))
return msg
def testZopeFileObject(self):
self.app.manage_addFile('test.txt',
'A Test Attachment')
msg = self._addFileSetup()
msg.add_file(self.app['test.txt'])
msg.send()
self.MailHost.checkSent()
def testPythonFileObject(self):
msg = self._addFileSetup()
msg.add_file(open(
os.path.join(test_folder,'test.txt')
))
msg.send()
self.MailHost.checkSent()
def testFileUploadObject(self):
msg = self._addFileSetup()
msg.add_file(self.makeFileUpload(
value='A Test Attachment'
))
msg.send()
self.MailHost.checkSent()
def testStringWithContentType(self):
msg = self._addFileSetup()
msg.add_file(
data=open(
os.path.join(test_folder,'test.txt')
).read(),
filename='test.txt',
content_type='text/plain'
)
msg.send()
self.MailHost.checkSent()
def testStringWithoutContentType(self):
msg = self._addFileSetup()
msg.add_file(
data=open(
os.path.join(test_folder,'test.txt')
).read(),
filename='test.txt'
)
msg.send()
self.MailHost.checkSent()
def testTooManyParameters(self):
msg = self._addFileSetup()
self.assertRaises(
TypeError,
msg.add_file,
self.makeFileUpload(
value='A Test Attachment'
),
data=open(
os.path.join(test_folder,'test.txt')
).read(),
filename='test.txt',
content_type='text/plain'
)
def testTooFewParameters(self):
msg = self._addFileSetup()
self.assertRaises(
TypeError,
msg.add_file
)
def testDataWithoutFilename(self):
msg = self._addFileSetup()
self.assertRaises(
TypeError,
msg.add_file,
data=open(
os.path.join(test_folder,'test.txt')
).read(),
content_type='text/plain'
)
def testFilenameWithoutData(self):
msg = self._addFileSetup()
self.assertRaises(
TypeError,
msg.add_file,
filename='test.txt',
content_type='text/plain'
)
def testCallAliasesSend(self):
self.test_add('Test Body')
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com',),
filename='mail_SendSimpleSomeHeaders.txt')
self.mt(
mfrom='from@example.com',
mto=('to@example.com',),
subject='Test Subject'
)
self.MailHost.checkSent()
def test_encoded_not_html_mode(self):
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com',),
filename='mail_unicode.txt')
self.test_add('Test <tal:x replace="options/unicode"/>')
# we get a unicode error here because we're trying to
# use an encoded string in a non-html-mode page template.
# It should have been decoded first.
self.assertRaises(
UnicodeDecodeError,
self.mt,
mfrom='from@example.com',
mto=('to@example.com',),
subject='Test Subject',
unicode=u''.encode('utf-8'),
encoding='utf-8'
)
def test_encoded_html_mode(self):
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com',),
filename='mail_unicode2.txt')
self.test_add('')
self.mt.pt_edit('Test <tal:x replace="options/unicode"/>',
'text/html')
self.mt(
mfrom='from@example.com',
mto=('to@example.com',),
subject='Test Subject',
unicode=u''.encode('utf-8'),
encoding='utf-8'
)
def test_unicode_not_html_mode(self):
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com',),
filename='mail_unicode.txt')
self.test_add('Test <tal:x replace="options/unicode"/>')
self.mt(
mfrom='from@example.com',
mto=('to@example.com',),
subject='Test Subject',
unicode=u'',
encoding='utf-8'
)
def test_unicode_html_mode(self):
self.MailHost.setExpected(mfrom='from@example.com',
mto=('to@example.com',),
filename='mail_unicode2.txt')
self.test_add('')
self.mt.pt_edit('Test <tal:x replace="options/unicode"/>',
'text/html')
# We get a unicode error here because we're trying to
# insert a unicode into an html-mode template.
# It should have been encoded first.
self.assertRaises(
UnicodeEncodeError,
self.mt,
mfrom='from@example.com',
mto=('to@example.com',),
subject='Test Subject',
unicode=u'',
encoding='utf-8'
)
def test_example1(self):
# login
noSecurityManager()
self.app.aq_chain[-1].id = 'testing'
newSecurityManager(
None,
SimpleUser('Test User','',('Manager',),[]).__of__(self.app)
)
try:
# setup
self.r.form['file']=self.makeFileUpload(diskname='example1.mt')
self.app.manage_addProduct['MailTemplates'].addMailTemplate(
id='my_mt',
mailhost='MailHost',
REQUEST=self.r
)
self.r.form['file']=self.makeFileUpload(diskname='example1.py')
self.app.manage_addProduct['PythonScripts'].manage_addPythonScript(
id='test_mt',
REQUEST=self.r
)
# set expected
self.MailHost.setExpected(mfrom='webmaster@example.com',
mto=('user@example.com',),
filename='example1.txt')
# test
self.assertEqual(self.app.test_mt(),'Mail Sent!')
self.MailHost.checkSent()
finally:
# logout
noSecurityManager()
newSecurityManager( None, SystemUser )
def test_example3(self):
# login
noSecurityManager()
self.app.aq_chain[-1].id = 'testing'
newSecurityManager(
None,
SimpleUser('Test User','',('Manager',),[]).__of__(self.app)
)
try:
# setup
self.r.form['file']=self.makeFileUpload(diskname='example3.mt')
self.app.manage_addProduct['MailTemplates'].addMailTemplate(
id='my_mt',
mailhost='MailHost',
REQUEST=self.r
)
self.app.manage_addFile(
id='myfile.bin',
file=self.makeFileUpload(diskname='example3.bin')
)
self.r.form['file']=self.makeFileUpload(diskname='example3.py')
self.app.manage_addProduct['PythonScripts'].manage_addPythonScript(
id='send_mail',
REQUEST=self.r
)
# set expected
self.MailHost.setExpected(mfrom='from@example.com',
mto='to1@example.com',
filename='example3.txt')
# test
self.assertEqual(self.app.send_mail(),'Mail Sent!')
self.MailHost.checkSent()
finally:
# logout
noSecurityManager()
newSecurityManager( None, SystemUser )
def test_example4(self):
# login
noSecurityManager()
self.app.aq_chain[-1].id = 'testing'
newSecurityManager(
None,
SimpleUser('Test User','',('Manager',),[]).__of__(self.app)
)
try:
# setup
self.r.form['file']=self.makeFileUpload(diskname='example4.mt')
self.app.manage_addProduct['MailTemplates'].addMailTemplate(
id='my_mt',
mailhost='MailHost',
REQUEST=self.r
)
self.app.my_mt.manage_addProperty(
'subject','Welcome to %s','string'
)
self.app.my_mt.manage_addProperty(
'mfrom','webmaster@example.com','string'
)
self.r.form['file']=self.makeFileUpload(diskname='example4.py')
self.app.manage_addProduct['PythonScripts'].manage_addPythonScript(
id='send_mail',
REQUEST=self.r
)
# set expected
self.MailHost.setExpected(mfrom='webmaster@example.com',
mto=('user@example.com',),
filename='example4.txt')
# test
self.assertEqual(self.app.send_mail(),'Mail Sent!')
self.MailHost.checkSent()
finally:
# logout
noSecurityManager()
newSecurityManager( None, SystemUser )
def test_suite():
return TestSuite((
makeSuite(TestMailTemplate),
))
if __name__ == '__main__':
main(defaultTest='test_suite')
Mail Templates SVN TRUNK
<tal:body xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
>
</tal:body>
\ No newline at end of file
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add Mail Template"
tal:replace="structure here/manage_form_title">Form Title</h2>
<p class="form-help">
Mail Templates allow you to use TALES, METAL and other ZPT functionality
to generate and send emails.
</p>
<form action="addMailTemplate" method="post"
enctype="multipart/form-data">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
MailHost
</div>
</td>
<td align="left" valign="top">
<select name="mailhost">
<option tal:repeat="mh python:here.superValues(('Mail Host','Maildrop Host'))"
tal:content="mh/title_and_id"
tal:attributes="value mh/getId"/>
</select>
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-optional">
File
</div>
</td>
<td align="left" valign="top">
<input type="file" name="file" size="25" value="" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Add " />
<input class="form-element" type="submit" name="submit"
value=" Add and Edit " />
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="manage_tabs_message options/manage_tabs_message | nothing"
tal:replace="structure here/manage_tabs">Tabs</h2>
<tal:block define="global body request/other/text | request/form/text
| here/read" />
<form action="" method="post" tal:attributes="action request/URL1">
<input type="hidden" name=":default_method" value="pt_changePrefs">
<table width="100%" cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="middle">
<div class="form-optional">
MailHost
</div>
</td>
<td align="left" valign="middle">
<select name="mailhost"
tal:define="mh_meta_types python:('Mail Host','Maildrop Host')">
<option tal:define="mh python:here.restrictedTraverse(here.mailhost,None)"
tal:condition="python:getattr(mh,'meta_type',None) not in mh_meta_types"
tal:attributes="value here/mailhost"
tal:content="string:'${here/mailhost}' is no longer valid!"
selected="selected"/>
<option tal:repeat="mh python:here.superValues(mh_meta_types)"
tal:content="mh/title_and_id"
tal:attributes="value mh/getId;
selected python:here.mailhost==mh.getId()"/>
</select>
</td>
<td align="left" valign="middle">
<div class="form-optional">
Content-Type
</div>
</td>
<td align="left" valign="middle">
<input type="text" name="content_type" size="14"
tal:attributes="value request/content_type | here/content_type" />
</td>
</tr>
<tr>
<td align="left" valign="middle">
<div class="form-label">
Last Modified
</div>
</td>
<td align="left" valign="middle">
<div class="form-text"
tal:content="python:here.bobobase_modification_time().strftime('%Y-%m-%d %I:%M %p')">1/1/2000
</div>
</td>
<td align="left" valign="top" colspan=2>
<a href="source.html" tal:condition="here/html">Browse HTML source</a>
<a href="source.xml" tal:condition="not:here/html">Browse XML source</a>
<br>
<input type="hidden" name="expand:int:default" value="0">
<input type="checkbox" value="1" name="expand:int"
tal:attributes="checked request/expand | here/expand">
Expand macros when editing
</td>
</tr>
<tr tal:define="errors here/pt_errors" tal:condition="errors">
<tal:block define="global body python:here.document_src({'raw':1})"/>
<td align="left" valign="middle" class="form-label">Errors</td>
<td align="left" valign="middle" style="background-color: #FFDDDD"
colspan="3">
<pre tal:content="python:modules['string'].join(errors, '\n')">errors</pre>
</td>
</tr>
<tr tal:define="warnings here/pt_warnings" tal:condition="warnings">
<td align="left" valign="middle" class="form-label">Warnings</td>
<td align="left" valign="middle" style="background-color: #FFEEDD"
colspan="3">
<pre tal:content="python:modules['string'].join(warnings, '\n')">errors</pre>
</td>
</tr>
<tr>
<td align="left" valign="top" colspan="4"
tal:define="width request/dtpref_cols | string:100%;
relative_width python:str(width).endswith('%')">
<textarea name="text:text" wrap="off" style="width: 100%;" rows="20"
tal:condition="relative_width"
tal:attributes="style string:width: $width;;;
rows request/dtpref_rows | default"
tal:content="body">Template Body</textarea>
<textarea name="text:text" wrap="off" rows="20" cols="50"
tal:condition="not:relative_width"
tal:attributes="cols width; rows request/dtpref_rows | default"
tal:content="body">Template Body</textarea>
</td>
</tr>
<tr>
<td align="left" valign="top" colspan="4">
<div class="form-element">
<em tal:condition="here/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:here/wl_isLocked"
class="form-element" type="submit"
name="pt_editAction:method" value="Save Changes">
&nbsp;&nbsp;
<input class="form-element" type="submit" name="height" value="Taller">
<input class="form-element" type="submit" name="height" value="Shorter">
<input class="form-element" type="submit" name="width" value="Wider">
<input class="form-element" type="submit" name="width" value="Narrower">
</div>
</td>
</tr>
</table>
</form>
<p class="form-help">
You can upload the text for <span tal:replace="here/title_and_id" />
using the following form.
Choose an existing HTML or XML file from your local computer by clicking
<em>browse</em>. You can also <a href="document_src">click here</a>
to view or download the current text.
</p>
<form action="pt_upload" method="post"
enctype="multipart/form-data">
<table cellpadding="2" cellspacing="0" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
File &nbsp;
</div>
</td>
<td align="left" valign="top">
<input type="file" name="file" size="25" value="">
</td>
</tr>
<tr>
<td></td>
<td align="left" valign="top">
<div class="form-element">
<em tal:condition="here/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:here/wl_isLocked"
class="form-element" type="submit" value="Upload File">
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment