def has_permission(self, permission, obj): """ Return true if authenticated user has been granted the given permission on obj. """ # Check roles, strongest first to optimize caching. try: p_template = obj.model_class.permission_templates.get(obj.permissions) except KeyError: # XXX Perm template is invalid or incomplete... Should do something here... # But if we're on the system account, let's pass if issubclass(self.model_class, SystemAccount): return True log.warning("Invalid permission template: '%s' on %s" % (obj.permissions, obj.model_class, )) if self.has_role(p_template[permission], obj): return True # Didn't find, we disallow return False
def has_permission(self, permission, obj): """ Return true if authenticated user has been granted the given permission on obj. """ # Check roles, strongest first to optimize caching. try: p_template = obj.model_class.permission_templates.get( obj.permissions) except KeyError: # XXX Perm template is invalid or incomplete... Should do something here... # But if we're on the system account, let's pass if issubclass(self.model_class, SystemAccount): return True log.warning("Invalid permission template: '%s' on %s" % ( obj.permissions, obj.model_class, )) if self.has_role(p_template[permission], obj): return True # Didn't find, we disallow return False
def save(self, *args, **kw): """ Set various object attributes """ import account import community auth = Twistable.objects._getAuthenticatedAccount() # Check if we're saving a real object and not a generic Content one (which is prohibited). # This must be a programming error, then. if self.__class__.__name__ == Twistable.__name__: raise ValidationError( "You cannot save a raw content object. Use a derived class instead." ) # Set information used to retreive the actual subobject self.model_name = self._meta.object_name self.app_label = self._meta.app_label # Set owner, publisher upon object creation. Publisher is NEVER set as None by default. if self.id is None: # If self.owner is already set, ensure it's done by SystemAccount if self.owner_id: if not isinstance(auth, account.SystemAccount): raise PermissionDenied( "You're not allowed to set the content owner by yourself." ) else: self.owner = self.getDefaultOwner() if not self.publisher_id: self.publisher = self.getDefaultPublisher() else: if not self.publisher.can_publish: raise PermissionDenied( "You're not allowed to publish on %s" % self.publisher) else: # XXX TODO: Check that nobody sets /unsets the owner or the publisher of an object # raise PermissionDenied("You're not allowed to set the content owner by yourself.") if not self.can_edit: raise PermissionDenied( "You're not allowed to edit this content.") # Set created_by and modified_by fields if self.id is None: self.created_by = auth self.modified_by = auth # Check if publisher is set. Only GlobalCommunity may have its publisher to None to make a site visible on the internet. if not self.publisher_id: if not self.__class__._ALLOW_NO_PUBLISHER: raise ValueError( "Only the Global Community can have no publisher, not %s" % self) # Set permissions; we will apply them last to ensure we have an id. # We also ensure that the right permissions are set on the right object if not self.permissions: perm_template = self.model_class.permission_templates if not perm_template: raise ValueError( "permission_templates not defined on class %s" % self.__class__.__name__) self.permissions = perm_template.get_default() tpl = [ t for t in self.permission_templates.permissions() if t["id"] == self.permissions ] if not tpl: # Didn't find? We restore default setting. XXX Should log/alert something here! tpl = [ t for t in self.permission_templates.permissions() if t["id"] == self.model_class.permission_templates.get_default() ] log.warning("Restoring default permissions. Problem here.") log.warning( "Unable to find %s permission template %s in %s" % (self, self.permissions, self.permission_templates.perm_dict)) if tpl[0].get("disabled_for_community") and issubclass( self.publisher.model_class, community.Community): raise ValueError( "Invalid permission setting %s for this object (%s/%s)" % (tpl, self, self.title_or_description)) elif tpl[0].get("disabled_for_useraccount") and issubclass( self.publisher.model_class, account.UserAccount): raise ValueError( "Invalid permission setting %s for this object (%s/%s)" % (tpl, self, self.title_or_description)) for perm, role in tpl[0].items(): if perm.startswith("can_"): if callable(role): role = role(self) setattr(self, "_p_%s" % perm, role) # Check if we're creating or not created = not self.id # Generate slug (or not !) if not self.slug and self.__class__._FORCE_SLUG_CREATION: if self.title: self.slug = slugify(self.title) elif self.description: self.slug = slugify(self.description) else: self.slug = slugify(self.model_name) self.slug = self.slug[:40] if created and self.__class__._FORCE_SLUG_CREATION: while Twistable.objects.__booster__.filter( slug=self.slug).exists(): match = re.search("_(?P<num>[0-9]+)$", self.slug) if match: root = self.slug[:match.start()] num = int(match.groupdict()['num']) + 1 else: root = self.slug num = 1 self.slug = "%s_%i" % ( root, num, ) # Perform a full_clean on the model just to be sure it validates correctly self.full_clean() # Save and update access network information ret = super(Twistable, self).save(*args, **kw) self._update_access_network() # Send TN's post-save signal twistable_post_save.send(sender=self.__class__, instance=self, created=created) return ret
def get_query_set( self, __account__=None, request=None, ): """ Return a queryset of 100%-authorized objects. All (should) have the can_list perm to True. This is in fact a kind of 'has_permission(can_list)' method! This method IS very slow. But you can speed things up if you pass either 'request' or '__account__' along the lines. Be aware, however, that in this case you loose the 'safety belt' provided by the security model. """ # Check for anonymous query import community, account, community __account__ = self._getAuthenticatedAccount(__account__, request) base_query_set = super(TwistableManager, self).get_query_set() # System account: return all objects without asking any question. And with all permissions set. if __account__.id == account.SystemAccount.SYSTEMACCOUNT_ID: return base_query_set # XXX TODO: Make a special query for admin members? Or at least mgrs of the global community? # XXX Make this more efficient? # XXX Or, better, check if current user is manager of the owner ? if __account__.id > 0: managed_accounts = [ __account__.id, ] else: managed_accounts = [] # XXX This try/except is there so that things don't get stucked during boostrap try: if __account__.is_admin: return base_query_set.filter(_p_can_list__lte=roles.manager, ) except DatabaseError: log.warning( "DB error while checking AdminCommunity. This is NORMAL during syncdb or bootstrap." ) return base_query_set # Regular check. Works for anonymous as well... # network_ids = __account__.network_ids if not __account__.is_anonymous: qs = base_query_set.filter( Q( owner__id=__account__.id, _p_can_list=roles.owner, ) | Q( owner__id=__account__.id, _p_can_list=roles.manager, ) | Q( _access_network__targeted_network__target=__account__, _p_can_list=roles.network, ) | Q( _access_network__targeted_network__target=__account__, _p_can_list=roles.public, ) | Q( # Anonymous stuff _access_network__isnull=True, _p_can_list=roles.public, )) else: # Anon query. Easy: We just return public stuff. # Warning: nested query is surely inefficient... free_access_network = Twistable.objects.__booster__.filter( _access_network__isnull=True, _p_can_list=roles.public, ) qs = base_query_set.filter( Q( # Strictly anonymous stuff _access_network__isnull=True, _p_can_list=roles.public, ) | Q( # Incidently anonymous stuff (public stuff published by an anon account) _access_network__isnull=False, _access_network__id__in=free_access_network, _p_can_list=roles.public, )) return qs
def __call__(self, sender, **kwargs): """ Generate the message itself. XXX TODO: Handle translation correctly (not from the request only) """ # Fake-Login with SystemAccount so that everybody can be notified, # even users this current user can't list. from twistranet.twistapp.models import SystemAccount, Account, UserAccount, Community, Twistable __account__ = SystemAccount.get() from_email = settings.SERVER_EMAIL host = settings.EMAIL_HOST cache_mimeimages = {} if not host: # If host is disabled (EMAIL_HOST is None), skip that return # Handle recipients emails recipients = kwargs.get(self.recipient_arg, None) if not recipients: raise ValueError("Recipient must be provided as a '%s' parameter" % self.recipient_arg) to_list = [] if not isinstance(recipients, (list, tuple, QuerySet, )): recipients = (recipients, ) for recipient in recipients: if isinstance(recipient, Twistable): recipient = recipient.object if isinstance(recipient, UserAccount): to = recipient.email if not to: log.warning("Can't send email for '%s': %s doesn't have an email registered." % (sender, recipient, )) return to_list.append(to) elif isinstance(recipient, Community): if self.managers_only: members = recipient.managers else: members = recipient.members # XXX Suboptimal for very large communities to_list.extend([ member.email for member in members if member.email ]) elif type(recipient) in (str, unicode, ): to_list.append(recipient) # XXX Todo: check the '@' else: raise ValueError("Invalid recipient: %s (%s)" % (recipient, type(recipient), )) # Fetch templates text_template = kwargs.get('text_template', self.text_template) html_template = kwargs.get('html_template', self.html_template) # Now generate template and send mail for each recipient # XXX TODO: See http://docs.djangoproject.com/en/1.2/topics/email/#sending-multiple-e-mails # for the good approach to use. for to in to_list: # Append domain (and site info) to kwargs d = kwargs.copy() domain = cache.get("twistranet_site_domain") d.update({ "domain": domain, "site_name": utils.get_site_name(), "baseline": utils.get_baseline(), "recipient": to, # A string }) # Load both templates and render them with kwargs context text_tpl = get_template(text_template) c = Context(d) text_content = text_tpl.render(c).strip() if html_template: html_tpl = get_template(html_template) html_content = html_tpl.render(c) else: html_content = None # Fetch back subject from text template subject = self.subject if not subject: match = SUBJECT_REGEX.search(text_content) if match: subject = match.groups()[0] if not subject: raise ValueError("No subject provided nor 'Subject:' first line in your text template") # Remove empty lines and "Subject:" line from text templates text_content = SUBJECT_REGEX.sub('', text_content) text_content = EMPTY_LINE_REGEX.sub('\n', text_content) # Prepare messages msg = EmailMultiAlternatives(subject, text_content, from_email, [ to ], ) if html_content: if getattr(settings, 'SEND_EMAIL_IMAGES_AS_ATTACHMENTS', DEFAULT_SEND_EMAIL_IMAGES_AS_ATTACHMENTS): # we replace img links by img Mime Images mimeimages = [] def replace_img_url(match): """Change src url by mimeurl fill the mimeimages list """ urlpath = str(match.group('urlpath')) attribute = str(match.group('attribute')) is_static = False pathSplit = urlpath.split('/') if 'static' in pathSplit: filename = urlpath.split('/static/')[-1] is_static = True else: # XXX TODO : need to be improved split with site path (for vhosts) filename = urlpath.split('/')[-1] nb = len(mimeimages)+1 mimeimages.append((filename, 'img%i'%nb, is_static)) mimeurl = "cid:img%i" %nb return '%s="%s"' % (attribute,mimeurl) img_url_expr = re.compile('(?P<attribute>src)\s*=\s*([\'\"])(%s)?(?P<urlpath>[^\"\']*)\\2' %domain, re.IGNORECASE) html_content = img_url_expr.sub(replace_img_url, html_content) msg.attach_alternative(html_content, "text/html") if mimeimages: msg.mixed_subtype = 'related' for fkey, name, is_static in mimeimages: if cache_mimeimages.has_key(fkey): msgImage = cache_mimeimages[fkey] else: if is_static: f = open(path.join(settings.TWISTRANET_STATIC_PATH, fkey), 'rb') else: f = open(path.join(settings.MEDIA_ROOT, fkey), 'rb') cache_mimeimages[fkey] = msgImage = MIMEImage(f.read()) f.close() msgImage.add_header('Content-ID', '<%s>' % name) msgImage.add_header('Content-Disposition', 'inline') msg.attach(msgImage) # just inline images else: msg.attach_alternative(html_content, "text/html") # Send safely try: log.debug("Sending mail: '%s' from '%s' to '%s'" % (subject, from_email, to)) msg.send() except: log.warning("Unable to send message to %s" % to) log.exception("Here's what we've got as an error.")
def prepare_view(self): """ Render the import form or do the import (from csv file posted). """ if self.request.method == "POST" and \ self.request.FILES.get("csv_file", None): csv_file = self.request.FILES.get("csv_file") reader = csv.reader(csv_file, dialect=CSVDialect) for line in reader: if not line: continue # firstname;lastname;email firstname = line[0].decode('utf8') lastname = line[1].decode('utf8') email = line[2] username = email.split('@')[0] username = slugify(username).replace('_','-') if User.objects.filter(username = username).exists(): u = User.objects.get(username = username) useraccount = UserAccount.objects.get(user = u) log.info( "User account '%s' already exixts" %useraccount.title ) else: # create user try: __account__ = SystemAccount.get() u = User.objects.create( username = username, first_name = firstname, last_name = lastname, email = email, is_superuser = False, is_active = True, ) chars = string.ascii_letters + string.digits random.seed = (os.urandom(1024)) password = ''.join(random.choice(chars) for i in range(6)) u.set_password(password) u.save() useraccount = UserAccount.objects.get(user = u) useraccount.title = u"%s %s" % (firstname, lastname) useraccount.save() log.info( "User account '%s' for %s %s (%s) created !" %(username, firstname, lastname, email)) # notify imported user (a mail is sent to prevent user) h = "%s%s%s%s" % (settings.SECRET_KEY, email, password, time.strftime("%Y%m%d")) h = hashlib.md5(h).hexdigest() reset_link = reverse(ResetPassword.name, args = (h, urllib.quote_plus(email))) user_imported.send( sender = self.__class__, target = useraccount, reset_password_url = reset_link, ) del __account__ except: log.warning( "Impossible to create account '%s' for %s %s (%s)" %(username, firstname, lastname, email)) continue community_title = line[3].decode('utf8') cid = slugify(community_title) if Community.objects.filter(slug = cid).exists(): log.info( "Community %s already exists !" %community ) else: c = Community.objects.create( slug = cid, title = community_title, permissions = "workgroup" ) c.save() com = Community.objects.get(slug= cid) com.join(account=useraccount) log.info( "user %s join the community %s !" %(useraccount.title, community_title) ) messages.info( self.request, u"import finished",)
def __call__(self, sender, **kwargs): """ Generate the message itself. XXX TODO: Handle translation correctly (not from the request only) """ # Fake-Login with SystemAccount so that everybody can be notified, # even users this current user can't list. from twistranet.twistapp.models import SystemAccount, Account, UserAccount, Community, Twistable __account__ = SystemAccount.get() from_email = settings.SERVER_EMAIL host = settings.EMAIL_HOST cache_mimeimages = {} if not host: # If host is disabled (EMAIL_HOST is None), skip that return # Handle recipients emails recipients = kwargs.get(self.recipient_arg, None) if not recipients: raise ValueError("Recipient must be provided as a '%s' parameter" % self.recipient_arg) to_list = [] if not isinstance(recipients, ( list, tuple, QuerySet, )): recipients = (recipients, ) for recipient in recipients: if isinstance(recipient, Twistable): recipient = recipient.object if isinstance(recipient, UserAccount): to = recipient.email if not to: log.warning( "Can't send email for '%s': %s doesn't have an email registered." % ( sender, recipient, )) return to_list.append(to) elif isinstance(recipient, Community): if self.managers_only: members = recipient.managers else: members = recipient.members # XXX Suboptimal for very large communities to_list.extend( [member.email for member in members if member.email]) elif type(recipient) in ( str, unicode, ): to_list.append(recipient) # XXX Todo: check the '@' else: raise ValueError("Invalid recipient: %s (%s)" % ( recipient, type(recipient), )) # Fetch templates text_template = kwargs.get('text_template', self.text_template) html_template = kwargs.get('html_template', self.html_template) # Now generate template and send mail for each recipient # XXX TODO: See http://docs.djangoproject.com/en/1.2/topics/email/#sending-multiple-e-mails # for the good approach to use. for to in to_list: # Append domain (and site info) to kwargs d = kwargs.copy() domain = cache.get("twistranet_site_domain") d.update({ "domain": domain, "site_name": utils.get_site_name(), "baseline": utils.get_baseline(), "recipient": to, # A string }) # Load both templates and render them with kwargs context text_tpl = get_template(text_template) c = Context(d) text_content = text_tpl.render(c).strip() if html_template: html_tpl = get_template(html_template) html_content = html_tpl.render(c) else: html_content = None # Fetch back subject from text template subject = self.subject if not subject: match = SUBJECT_REGEX.search(text_content) if match: subject = match.groups()[0] if not subject: raise ValueError( "No subject provided nor 'Subject:' first line in your text template" ) # Remove empty lines and "Subject:" line from text templates text_content = SUBJECT_REGEX.sub('', text_content) text_content = EMPTY_LINE_REGEX.sub('\n', text_content) # Prepare messages msg = EmailMultiAlternatives( subject, text_content, from_email, [to], ) if html_content: if getattr(settings, 'SEND_EMAIL_IMAGES_AS_ATTACHMENTS', DEFAULT_SEND_EMAIL_IMAGES_AS_ATTACHMENTS): # we replace img links by img Mime Images mimeimages = [] def replace_img_url(match): """Change src url by mimeurl fill the mimeimages list """ urlpath = str(match.group('urlpath')) attribute = str(match.group('attribute')) is_static = False pathSplit = urlpath.split('/') if 'static' in pathSplit: filename = urlpath.split('/static/')[-1] is_static = True else: # XXX TODO : need to be improved split with site path (for vhosts) filename = urlpath.split('/')[-1] nb = len(mimeimages) + 1 mimeimages.append((filename, 'img%i' % nb, is_static)) mimeurl = "cid:img%i" % nb return '%s="%s"' % (attribute, mimeurl) img_url_expr = re.compile( '(?P<attribute>src)\s*=\s*([\'\"])(%s)?(?P<urlpath>[^\"\']*)\\2' % domain, re.IGNORECASE) html_content = img_url_expr.sub(replace_img_url, html_content) msg.attach_alternative(html_content, "text/html") if mimeimages: msg.mixed_subtype = 'related' for fkey, name, is_static in mimeimages: if cache_mimeimages.has_key(fkey): msgImage = cache_mimeimages[fkey] else: if is_static: f = open( path.join( settings.TWISTRANET_STATIC_PATH, fkey), 'rb') else: f = open( path.join(settings.MEDIA_ROOT, fkey), 'rb') cache_mimeimages[fkey] = msgImage = MIMEImage( f.read()) f.close() msgImage.add_header('Content-ID', '<%s>' % name) msgImage.add_header('Content-Disposition', 'inline') msg.attach(msgImage) # just inline images else: msg.attach_alternative(html_content, "text/html") # Send safely try: log.debug("Sending mail: '%s' from '%s' to '%s'" % (subject, from_email, to)) msg.send() except: log.warning("Unable to send message to %s" % to) log.exception("Here's what we've got as an error.")
def get_query_set(self, __account__ = None, request = None, ): """ Return a queryset of 100%-authorized objects. All (should) have the can_list perm to True. This is in fact a kind of 'has_permission(can_list)' method! This method IS very slow. But you can speed things up if you pass either 'request' or '__account__' along the lines. Be aware, however, that in this case you loose the 'safety belt' provided by the security model. """ # Check for anonymous query import community, account, community __account__ = self._getAuthenticatedAccount(__account__, request) base_query_set = super(TwistableManager, self).get_query_set() # System account: return all objects without asking any question. And with all permissions set. if __account__.id == account.SystemAccount.SYSTEMACCOUNT_ID: return base_query_set # XXX TODO: Make a special query for admin members? Or at least mgrs of the global community? # XXX Make this more efficient? # XXX Or, better, check if current user is manager of the owner ? if __account__.id > 0: managed_accounts = [__account__.id, ] else: managed_accounts = [] # XXX This try/except is there so that things don't get stucked during boostrap try: if __account__.is_admin: return base_query_set.filter( _p_can_list__lte = roles.manager, ) except DatabaseError: log.warning("DB error while checking AdminCommunity. This is NORMAL during syncdb or bootstrap.") return base_query_set # Regular check. Works for anonymous as well... # network_ids = __account__.network_ids if not __account__.is_anonymous: qs = base_query_set.filter( Q( owner__id = __account__.id, _p_can_list = roles.owner, ) | Q( owner__id = __account__.id, _p_can_list = roles.manager, ) | Q( _access_network__targeted_network__target = __account__, _p_can_list = roles.network, ) | Q( _access_network__targeted_network__target = __account__, _p_can_list = roles.public, ) | Q( # Anonymous stuff _access_network__isnull = True, _p_can_list = roles.public, ) ) else: # Anon query. Easy: We just return public stuff. # Warning: nested query is surely inefficient... free_access_network = Twistable.objects.__booster__.filter( _access_network__isnull = True, _p_can_list = roles.public, ) qs = base_query_set.filter( Q( # Strictly anonymous stuff _access_network__isnull = True, _p_can_list = roles.public, ) | Q( # Incidently anonymous stuff (public stuff published by an anon account) _access_network__isnull = False, _access_network__id__in = free_access_network, _p_can_list = roles.public, ) ) return qs
def save(self, *args, **kw): """ Set various object attributes """ import account import community auth = Twistable.objects._getAuthenticatedAccount() # Check if we're saving a real object and not a generic Content one (which is prohibited). # This must be a programming error, then. if self.__class__.__name__ == Twistable.__name__: raise ValidationError("You cannot save a raw content object. Use a derived class instead.") # Set information used to retreive the actual subobject self.model_name = self._meta.object_name self.app_label = self._meta.app_label # Set owner, publisher upon object creation. Publisher is NEVER set as None by default. if self.id is None: # If self.owner is already set, ensure it's done by SystemAccount if self.owner_id: if not isinstance(auth, account.SystemAccount): raise PermissionDenied("You're not allowed to set the content owner by yourself.") else: self.owner = self.getDefaultOwner() if not self.publisher_id: self.publisher = self.getDefaultPublisher() else: if not self.publisher.can_publish: raise PermissionDenied("You're not allowed to publish on %s" % self.publisher) else: # XXX TODO: Check that nobody sets /unsets the owner or the publisher of an object # raise PermissionDenied("You're not allowed to set the content owner by yourself.") if not self.can_edit: raise PermissionDenied("You're not allowed to edit this content.") # Set created_by and modified_by fields if self.id is None: self.created_by = auth self.modified_by = auth # Check if publisher is set. Only GlobalCommunity may have its publisher to None to make a site visible on the internet. if not self.publisher_id: if not self.__class__._ALLOW_NO_PUBLISHER: raise ValueError("Only the Global Community can have no publisher, not %s" % self) # Set permissions; we will apply them last to ensure we have an id. # We also ensure that the right permissions are set on the right object if not self.permissions: perm_template = self.model_class.permission_templates if not perm_template: raise ValueError("permission_templates not defined on class %s" % self.__class__.__name__) self.permissions = perm_template.get_default() tpl = [ t for t in self.permission_templates.permissions() if t["id"] == self.permissions ] if not tpl: # Didn't find? We restore default setting. XXX Should log/alert something here! tpl = [ t for t in self.permission_templates.permissions() if t["id"] == self.model_class.permission_templates.get_default() ] log.warning("Restoring default permissions. Problem here.") log.warning("Unable to find %s permission template %s in %s" % (self, self.permissions, self.permission_templates.perm_dict)) if tpl[0].get("disabled_for_community") and issubclass(self.publisher.model_class, community.Community): raise ValueError("Invalid permission setting %s for this object (%s/%s)" % (tpl, self, self.title_or_description)) elif tpl[0].get("disabled_for_useraccount") and issubclass(self.publisher.model_class, account.UserAccount): raise ValueError("Invalid permission setting %s for this object (%s/%s)" % (tpl, self, self.title_or_description)) for perm, role in tpl[0].items(): if perm.startswith("can_"): if callable(role): role = role(self) setattr(self, "_p_%s" % perm, role) # Check if we're creating or not created = not self.id # Generate slug (or not !) if not self.slug and self.__class__._FORCE_SLUG_CREATION: if self.title: self.slug = slugify(self.title) elif self.description: self.slug = slugify(self.description) else: self.slug = slugify(self.model_name) self.slug = self.slug[:40] if created and self.__class__._FORCE_SLUG_CREATION: while Twistable.objects.__booster__.filter(slug = self.slug).exists(): match = re.search("_(?P<num>[0-9]+)$", self.slug) if match: root = self.slug[:match.start()] num = int(match.groupdict()['num']) + 1 else: root = self.slug num = 1 self.slug = "%s_%i" % (root, num, ) # Perform a full_clean on the model just to be sure it validates correctly self.full_clean() # Save and update access network information ret = super(Twistable, self).save(*args, **kw) self._update_access_network() # Send TN's post-save signal twistable_post_save.send(sender = self.__class__, instance = self, created = created) return ret
def __call__(self, sender, **kwargs): """ Generate the message itself. XXX TODO: Handle translation correctly (not from the request only) """ # Fake-Login with SystemAccount so that everybody can be notified, # even users this current user can't list. from twistranet.twistapp.models import SystemAccount, Account, UserAccount, Community, Twistable __account__ = SystemAccount.get() from_email = settings.SERVER_EMAIL host = settings.EMAIL_HOST if not host: # If host is disabled (EMAIL_HOST is None), skip that return # Handle recipients emails recipients = kwargs.get(self.recipient_arg, None) if not recipients: raise ValueError("Recipient must be provided as a '%s' parameter" % self.recipient_arg) to_list = [] if not isinstance(recipients, (list, tuple, QuerySet, )): recipients = (recipients, ) for recipient in recipients: if isinstance(recipient, Twistable): recipient = recipient.object if isinstance(recipient, UserAccount): to = recipient.email if not to: log.warning("Can't send email for '%s': %s doesn't have an email registered." % (sender, recipient, )) return to_list.append(to) elif isinstance(recipient, Community): if self.managers_only: members = recipient.managers else: members = recipient.members # XXX Suboptimal for very large communities to_list.extend([ member.email for member in members if member.email ]) elif type(recipient) in (str, unicode, ): to_list.append(recipient) # XXX Todo: check the '@' else: raise ValueError("Invalid recipient: %s (%s)" % (recipient, type(recipient), )) # Fetch templates text_template = kwargs.get('text_template', self.text_template) html_template = kwargs.get('html_template', self.html_template) # Now generate template and send mail for each recipient # XXX TODO: See http://docs.djangoproject.com/en/1.2/topics/email/#sending-multiple-e-mails # for the good approach to use. for to in to_list: # Append domain (and site info) to kwargs d = kwargs.copy() domain = cache.get("twistranet_site_domain") d.update({ "domain": domain, "site_name": utils.get_site_name(), "baseline": utils.get_baseline(), "recipient": to, # A string }) # Load both templates and render them with kwargs context text_tpl = get_template(text_template) c = Context(d) text_content = text_tpl.render(c).strip() if html_template: html_tpl = get_template(html_template) html_content = html_tpl.render(c) else: html_content = None # Fetch back subject from text template subject = self.subject if not subject: match = SUBJECT_REGEX.search(text_content) if match: subject = match.groups()[0] if not subject: raise ValueError("No subject provided nor 'Subject:' first line in your text template") # Remove empty lines and "Subject:" line from text templates text_content = SUBJECT_REGEX.sub('', text_content) text_content = EMPTY_LINE_REGEX.sub('\n', text_content) # Prepare messages msg = EmailMultiAlternatives(subject, text_content, from_email, [ to ], ) if html_content: msg.attach_alternative(html_content, "text/html") # Send safely try: log.debug("Sending mail: '%s' from '%s' to '%s'" % (subject, from_email, to)) msg.send() except: log.warning("Unable to send message to %s" % to) log.exception("Here's what we've got as an error.")