pipermerriam / django-emailtools

Sending emails just got more awesomer.
https://django-emailtools.readthedocs.org/
BSD 2-Clause "Simplified" License
10 stars 5 forks source link

Where to hook in request = context['request']? #5

Closed jhagege closed 9 years ago

jhagege commented 9 years ago

Hi guys,

I've been playing around with your package and making progress with it.

I am now stuck with a particular issue: I am using another third-party package that needs access to the context['request'] when trying to render the absolute url path of elements that I put inside the template (such as in {% absolute_url 'functionfoo' %} but for some reason I get an error thrown when trying to access the context['request'] inside this template tag.

It looks like the HtmlEmail class doesn't pass the request object inside the context. What would be the best way to go to include it? I guess I would have somehow to pass the context whenever creating an HTMLEmail().

Thanks a lot for your help.

rockymeza commented 9 years ago

You could do something like this:

from emailtools import HTMLEmail

class ThingCreatedAdminEmail(HTMLEmail):
    template_name = 'things/mail/thing_created_admin.html'
    subject = "New Thing Created"

    def __init__(self, thing, request):
        self.thing = thing
        self.request = request

    def get_to(self):
        return [user.email for user in User.objects.filter(is_superuser=True)]

    def get_context_data(self, **kwargs):
        kwargs = super(ThingCreatedAdminEmail, self).get_context_data(**kwargs)
        kwargs.update(
            thing=self.thing,
            request=self.request,
        )
        return kwargs

Then when you want to send your emails, you can do so like this:

def view(request):
    # ...
    email = ThingCreatedAdminEmail(thing, request=request)
    email.send()
    # ...

Another thing you can do is use the BuildAbsoluteURIMixin mixin to build URLs, but it's not that user-friendly because you can't use it in the templates, here's an example usage:

from emailtools import HTMLEmail
from emailtools.cbe.mixins import BuildAbsoluteURIMixin

class ThingCreatedAdminEmail(BuildAbsoluteURIMixin, HTMLEmail):
    template_name = 'things/mail/thing_created_admin.html'
    subject = "New Thing Created"

    def __init__(self, thing):
        self.thing = thing

    def get_to(self):
        return [user.email for user in User.objects.filter(is_superuser=True)]

    def get_context_data(self, **kwargs):
        kwargs = super(ThingCreatedAdminEmail, self).get_context_data(**kwargs)
        kwargs.update(
            thing=self.thing,
            detail_link=self.build_absolute_uri(self.thing.get_absolute_url()),
            admin_link=self.reverse_absolute_uri('admin:things_thing_change', args=(self.thing.pk,)),
        )
        return kwargs

send_thing_created_admin_email = ThingCreatedAdminEmail.as_callable()

The template could just output using {{ detail_link }} or {{ admin_link }}. This has the benefit of not needing the request. This is useful if you are sending emails when you don't have a request (like from a cron job), however, it does require you to set your django.contrib.sites up correctly.

If you wanted to send emails from a cron job, but still reverse the URLs in the template using something like absolute_uri, here's an implementation that uses the django.contrib.sites framework:

# myapp/templatetags/absolute_uri.py

from django import template
from django.core import urlresolvers

from django.contrib.sites.models import Site

register = template.Library()

def build_absolute_uri(path):
    site = Site.objects.get_current()
    return '{protocol}://{domain}{path}'.format(
        protocol='http',
        domain=site.domain,
        path=path
    )

@register.simple_tag
def absolute_uri(view_name, *args, **kwargs):
    """Reverse a URL and make it absolute."""
    path = urlresolvers.reverse(view_name, args=args, kwargs=kwargs)
    return build_absolute_uri(path)

@register.simple_tag
def absolutize(path):
    """Turn a relative URL into an absolute URL."""
    return build_absolute_uri(path)

(I modified this code before posting it, so I haven't tested it, but it's based on something that I've used before.)

I hope this answers your question. I am going to go ahead and close the issue. Please feel free to keep discussing this if you need some more clarification.

jhagege commented 9 years ago

Thank you so much for your thorough explanations, it proves very useful, and thanks again for this great package. It helps me understand better some OOP concepts in Python / Django.

Regards.

rockymeza commented 9 years ago

Thanks @cyberjoac, if you have any more questions, feel free to keep asking!

pipermerriam commented 9 years ago

And just a small follow-up @cyberjoac.

I would really recommend using a pattern that doesn't involve needing the request object to send your emails. By doing so, you've forced yourself into a corner where you always need a request object to trigger an email, meaning that you can't do things like sending them asynchronously with cron/celery, or even triggering them manually via the command line.

rockymeza commented 9 years ago

I wrapped that pattern I mentioned in the previous comment up in a django package, you can get it from PyPI:

https://pypi.python.org/pypi/django-absoluteuri/1.0.0

pipermerriam commented 9 years ago

LOVE IT!

jhagege commented 9 years ago

Thanks a lot!!!