Ejemplo n.º 1
0
    def __call__(self, match):
        """
        Function passed to re.sub()
        """
        label = match.group(0)
        title = None
        obj = None
        if self.lookup:
            try:
                kw = {self.lookup_field: match.groupdict()['Alias']}
                obj = self.model.objects.get(**kw)
                url = obj.get_absolute_url()
                if self.lookup_field != "slug" and obj.slug:
                    label = match.group(0).replace(match.groupdict()['Alias'],
                                                   obj.slug)
                title = obj.title
            except self.model.DoesNotExist:
                log.debug("Doesn't exist: %s->%s" %
                          (self.lookup_field, match.groupdict()['Alias']))
                return match.group(0)
        else:
            url = reverse(self.fast_reverse,
                          args=(match.groupdict()['Alias'], ))

        if self.func and obj:
            subst = self.func(obj)
        else:
            subst = '<a href="%s" title="%s">%s</a>' % (url, title
                                                        or label, label)

        return subst
Ejemplo n.º 2
0
def create_profile(sender, instance, created, *args, **kw):
    """
    Create an empty profile for a new user.
    XXX TODO: Handle particular LDAP attributes? 
    See http://packages.python.org/django-auth-ldap/#user-objects for more info
    """
    if created:
        # Here we check if AdminCommunity exists.
        # If it doesn't, that probably means we're inside the bootstrap process,
        # and in such case we don't want to create a profile now.
        import community
        if not community.AdminCommunity.objects.__booster__.exists():
            log.debug("Admin community doesn't exist (yet)")
            return
            
        # We consider we're the SystemAccount now.
        __account__ = SystemAccount.get()
        
        # Actually create profile
        log.info("Automatic creation of a UserAccount for %s" % instance)
        profile = UserAccount(
            user = instance,
            slug = slugify.slugify(instance.username),
        )
        profile.save()
Ejemplo n.º 3
0
    def __call__(self, match):
        """
        Function passed to re.sub()
        """
        label = match.group(0)
        title = None
        obj = None
        if self.lookup:
            try:
                kw = {self.lookup_field: match.groupdict()['Alias']}
                obj = self.model.objects.get(**kw)
                url = obj.get_absolute_url()
                if self.lookup_field != "slug" and obj.slug:
                    label = match.group(0).replace(match.groupdict()['Alias'], obj.slug)
                title = obj.title
            except self.model.DoesNotExist:
                log.debug("Doesn't exist: %s->%s" % (self.lookup_field, match.groupdict()['Alias']))
                return match.group(0)
        else:
            url = reverse(self.fast_reverse, args = (match.groupdict()['Alias'],))

        if self.func and obj:
            subst = self.func(obj)
        else:
            subst = '<a href="%s" title="%s">%s</a>' % (url, title or label, label)

        return subst
Ejemplo n.º 4
0
def create_profile(sender, instance, created, *args, **kw):
    """
    Create an empty profile for a new user.
    XXX TODO: Handle particular LDAP attributes? 
    See http://packages.python.org/django-auth-ldap/#user-objects for more info
    """
    if created:
        # Here we check if AdminCommunity exists.
        # If it doesn't, that probably means we're inside the bootstrap process,
        # and in such case we don't want to create a profile now.
        import community
        if not community.AdminCommunity.objects.__booster__.exists():
            log.debug("Admin community doesn't exist (yet)")
            return

        # We consider we're the SystemAccount now.
        __account__ = SystemAccount.get()

        # Actually create profile
        log.info("Automatic creation of a UserAccount for %s" % instance)
        profile = UserAccount(
            user=instance,
            slug=slugify.slugify(instance.username),
        )
        profile.save()
Ejemplo n.º 5
0
    def save(self, *args, **kw):
        """
        Set the 'name' attibute from User Source.
        We don't bother checking the security here, the parent will do it.
        If this is a creation, ensure we join the GlobalCommunity as well.
        """
        from twistranet.twistapp.models import community
        if not self.slug:
            self.slug = self.user.username
        if not self.title:
            self.title = "%s %s" % (self.user.first_name, self.user.last_name, )
        creation = not self.id
        ret = super(UserAccount, self).save(*args, **kw)

        # Join the global community. For security reasons, it's SystemAccount who does this.
        # Add myself to my own community as well.
        # XXX Maybe this has to be done BEFORE calling super() ?
        if creation:
            glob = community.GlobalCommunity.objects.get()
            __account__ = SystemAccount.objects.get()
            glob.join(self)
            self.follow(self)
            del __account__
            
        log.debug("Saved %s (title = %s)" % (self, self.title, ))
        return ret
Ejemplo n.º 6
0
    def save(self, *args, **kw):
        """
        Set the 'name' attibute from User Source.
        We don't bother checking the security here, the parent will do it.
        If this is a creation, ensure we join the GlobalCommunity as well.
        """
        from twistranet.twistapp.models import community
        if not self.slug:
            self.slug = self.user.username
        if not self.title:
            self.title = "%s %s" % (
                self.user.first_name,
                self.user.last_name,
            )
        creation = not self.id
        ret = super(UserAccount, self).save(*args, **kw)

        # Join the global community. For security reasons, it's SystemAccount who does this.
        # Add myself to my own community as well.
        # XXX Maybe this has to be done BEFORE calling super() ?
        if creation:
            glob = community.GlobalCommunity.objects.get()
            __account__ = SystemAccount.objects.get()
            glob.join(self)
            self.follow(self)
            del __account__

        log.debug("Saved %s (title = %s)" % (
            self,
            self.title,
        ))
        return ret
Ejemplo n.º 7
0
    def can_leave(self):
        # Special check if we're not the last (human) manager inside
        if self.is_manager:
            log.debug("Is manager on %s" % self)
            if self.managers.count() == 1:
                return False

        # Regular checks
        auth = Account.objects._getAuthenticatedAccount()
        return auth.has_permission(permissions.can_leave, self)
Ejemplo n.º 8
0
 def can_leave(self):
     # Special check if we're not the last (human) manager inside
     if self.is_manager:
         log.debug("Is manager on %s" % self)
         if self.managers.count() == 1:
             return False
     
     # Regular checks
     auth = Account.objects._getAuthenticatedAccount()
     return auth.has_permission(permissions.can_leave, self)        
Ejemplo n.º 9
0
    def apply(self, ):
        """
        Create / update model. Use the 'slug' attribute to define unicity of the content.
        """
        from twistranet.twistapp.models import Account
        slug = self.dict.get('slug', None)
        obj = None
        log.debug("Trying to import %s" % slug)

        # Check if slug is given. Mandatory.
        if not self.dict.has_key('slug'):
            raise ValueError(
                "You can't apply this fixture without a slug attribute. This is so to avoid duplicates."
            )

        # Set auth if necessary
        if self.logged_account:
            __account__ = Account.objects.get(slug=self.logged_account)

        # Create/get object
        if slug:
            obj_q = Twistable.objects.__booster__.filter(slug=slug)
            if obj_q.exists():
                obj = obj_q.get().object
                if not self.force_update:
                    # Object already exists and we don't want to update. Keep it that way.
                    return obj
        if not obj:
            obj = self.model()

        # Set properties & save
        for k, v in self.dict.items():
            # print self.model, k, v
            if isinstance(v, QuerySet):
                v = v.get()
            setattr(obj, k, v)
        try:
            obj.save()
        except:
            log.debug("Exception while attempting to generate %s" % self.dict)
            raise

        return obj
 def apply(self,):
     """
     Create / update model. Use the 'slug' attribute to define unicity of the content.
     """
     from twistranet.twistapp.models import Account
     slug = self.dict.get('slug', None)
     obj = None
     log.debug("Trying to import %s" % slug)
     
     # Check if slug is given. Mandatory.
     if not self.dict.has_key('slug'):
         raise ValueError("You can't apply this fixture without a slug attribute. This is so to avoid duplicates.")
     
     # Set auth if necessary
     if self.logged_account:
         __account__ = Account.objects.get(slug = self.logged_account)
     
     # Create/get object
     if slug:
         obj_q = Twistable.objects.__booster__.filter(slug = slug)
         if obj_q.exists():
             obj = obj_q.get().object
             if not self.force_update:
                 # Object already exists and we don't want to update. Keep it that way.
                 return obj
     if not obj:
         obj = self.model()
         
     # Set properties & save
     for k, v in self.dict.items():
         # print self.model, k, v
         if isinstance(v, QuerySet):
             v = v.get()
         setattr(obj, k, v)
     try:
         obj.save()
     except:
         log.debug("Exception while attempting to generate %s" % self.dict)
         raise
     
     return obj
Ejemplo n.º 11
0
def resource_quickupload_file(request):
    """
    json view used by quikupload script
    when uploading a file
    return success/error + file infos (url/preview/title ...)
    """               
    msg = {}
    if request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest':
        file_name = urllib.unquote(request.META.get('HTTP_X_FILE_NAME'))
        title = request.GET.get('title', '')
        upload_with = "XHR"        
        try:
            # the solution for sync ajax file upload
            file_data = SimpleUploadedFile(file_name, request.raw_post_data)
        except:
            log.debug("XHR Upload of %s has been aborted" %file_name)
            file_data = None
            # not really useful here since the upload block
            # is removed by "cancel" action, but
            # could be useful if someone change the js behavior
            msg = {u'error': u'emptyError'}
    else:
        # MSIE fallback behavior (classic upload with iframe)
        file_data = request.FILES.get("qqfile", None)
        filename = getattr(file_data,'name', '')
        file_name = filename.split("\\")[-1]
        title = request.POST.get('title', '')
        upload_with = "CLASSIC FORM POST"
        # we must test the file size in this case (because there is no size client test with msie file field)
        if not utils._check_file_size(file_data):
            log.debug("The file %s is too big, quick iframe upload rejected" % file_name) 
            msg = {u'error': u'sizeError'}

    if file_data and not msg:
        publisher_id = request.GET.get('publisher_id', request.POST.get('publisher_id', ''))
        try:                publisher_id = int(publisher_id)
        except ValueError:  publisher_id = None
        content_type = mimetypes.guess_type(file_name)[0] or 'application/octet-stream'

        try:
            # Create the resource itself
            resource = Resource.objects.create(
                resource_file = file_data,
                title = title,
                publisher_id = publisher_id and int(publisher_id),
                filename = file_name,
            )
            is_image = resource.is_image
            type = is_image and 'image' or 'file'
            # Generate the preview thumbnails
            thumbnails = resource.thumbnails
            msg = {
                'success':       True,
                'value':         resource.id,
                'url':           resource.get_absolute_url(),
                'thumbnail_url': is_image and thumbnails['medium'].url or thumbnails['big_icon'].url,
                'mini_url':      is_image and thumbnails['summary_preview'].url or thumbnails['big_icon'].url,
                # only used by image size selection (wysiwyg browser)
                'summary_url':   is_image and thumbnails['summary'].url or '',
                'preview_url':   is_image and thumbnails['preview'].url or '',
                'legend':        title and title or file_name, 
                'scope':         publisher_id,
                'type':          type
            }
        except PermissionDenied, e:
            log.exception(e)
            msg = {u'error': u'pemissiondeniedError'}
        except:
Ejemplo n.º 12
0
from twistranet import __author__, VERSION
__version__ = '.'.join(map(str, VERSION))

# Import / Load config & logger
from twistranet.twistapp.lib.log import log

# Then import models
from twistranet.twistapp.models import *

# Import forms because they're not automatically imported
from twistranet.twistapp.forms import community_forms, resource_forms

# Do the mandatory database checkup and initial buiding
from twistranet.core import bootstrap
bootstrap.check_consistancy()

# monkey patches
from twistranet.core import patches

log.debug("Twistranet loaded successfuly!")
Ejemplo n.º 13
0
    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.")
Ejemplo n.º 14
0
    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.")
Ejemplo n.º 15
0
 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.")