Use variables in send_email() - python

I use html as message in one email and pass some variables like this:
subject = 'Some Subject'
plain = render_to_string('templates/email/message.txt',{'name':variableWithSomeValue,'email':otherVariable})
html = render_to_string('templates/email/message.html',{'name':variableWithSomeValue,'email':otherVariable})
from_email = setting.EMAIL_HOST_USER
send_email(subject, plain, from_email, [variableToEmail], fail_silently=False, html_message=html)
That works good but now I need to take the message content from one table from the database, the table have three columns, in the first register have this values in each column. Column subject have Account Info, column plain have Hello {{name}}. Now you can access to the site using this email address {{email}}. and the column html have <p>Hello <strong>{{name}}</strong>.</p> <p>Now you can access to the site using this email address <strong>email</strong>.</p>.
So to take the values from the database I do this obj = ModelTable.objects.get(id=1) then this:
subject = obj.subject
plain = (obj.plain,{'name':variableWithSomeValue,'email':otherVariable})
html = (obj.html,{'name':variableWithSomeValue,'email':otherVariable})
from_email = setting.EMAIL_HOST_USER
send_email(subject, plain, from_email, [variableToEmail], fail_silently=False, html_message=html)
But this give me the error
AttributeError: 'tuple' object has no attribute 'encode'
so I tried to passing .encode(´utf-8´) for the values and gives me the same error, then change the value for each variable and find that the problem comes from plain = (obj.plain,{'name':variableWithSomeValue,'email':otherVariable}) and html = (obj.html,{'name':variableWithSomeValue,'email':otherVariable}) so I think that I passing the variables in the wrong way, so How can I do it in the right way? or maybe is for the encoding of the database but I think that using .encode(utf-8) should solve that problem but I really think that I pass the variables name and email in the wrong way.
Sorry for the long post and my bad grammar, if need more info please let me know.

I'm assuming that obj.plain and obj.html are strings representing your templates (as stored in the database)?
If that is the case, then you still need to render your email content. However, instead of using render_to_string, which takes as it's first argument a template path, you will want to create a template based on your string, and then render that template. Consider something like the following:
...
from django.template import Context, Template
plain_template = Template(obj.plain)
context = Context({'name':variableWithSomeValue,'email':otherVariable})
email_context = plain_template.render(context)
...
send_email(...)
Here's a link that better explains rendering string templates, as opposed to rendering template files.
https://docs.djangoproject.com/en/1.7/ref/templates/api/#rendering-a-context

Related

Django how to send html-designed email bodies

On my Django project I am trying to send beautiful mails with colorful body and so on. My mail body constructing function looks like the following:
def construct_mail_body():
user = User.objects.get(id=1)
context = {
'username': user.username,
}
template = render_to_string('mail/mail_body.html', context=context)
message = strip_tags(template)
return message
mail_body.html:
{% load static %}
<h3>Hello <span style="color: red;">{{ username }}</span>,</h3>
<h3>You've successfully completed our Tutorial!></h3>
<h3>Congratulations!</h3>
But it doesn't work as expected. My mail bodies looks like:
Body 1:
Body 2 (Btw, why this happens? Why the mail is in purple?):
So how to make HTML tags work properly and is it possible to add some styling properties of css?
Thanks!
Solved:
I found a way how to do that:
from django.core.mail import EmailMessage
mail = EmailMessage(
subject,
body,
settings.EMAIL_HOST_USER,
[email],
)
mail.fail_silently = False
mail.content_subtype = 'html'
mail.send()
This way it works properly.
This is possible, but stick to hex colours, e.g. #ff0000, instead of a named colour.
Text can all go purple, as Willem commented, when something is repeated, or for replies. If you change the subject line when testing, it won't group them, and so it won't do this.
Keep using inline CSS, but stick to HTML4 and CSS2 (higher can be achieved but as a progressive enhancement for some email clients only).

Django coding - Why is it necessary to return two identical arguments?

I am looking to add email account verification in Django and found a nice open source code that I can adopt.
But there is one line which I'm not familiar with.
Why does the function "password_reset_confirm" need to return two **kwargs in separate brackets and
how each of them is used in the class "PasswordResetConfirm"?
This question might be related to Python rather than Django. But anyway, thank you for your help!
urls.py
url(r"^password/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-
z]{1,13}-[0-9A-Za-z]{1,20})/$",views.password_reset_confirm, name="reset-
password-confirm",)
views.py
from django.contrib.auth import views as django_views
class PasswordResetConfirm(django_views.PasswordResetConfirmView):
template_name = "account/password_reset_from_key.html"
success_url = reverse_lazy("account:reset-password-complete")
token = None
uidb64 = None
def form_valid(self, form):
response = super(PasswordResetConfirm, self).form_valid(form)
account_events.customer_password_reset_event(user=self.user)
return response
def password_reset_confirm(request, uidb64=None, token=None):
kwargs = {
"template_name": "account/password_reset_from_key.html",
"success_url": reverse_lazy("account:reset-password-complete"),
"token": token,
"uidb64": uidb64,
}
return PasswordResetConfirm.as_view(**kwargs)(request, **kwargs)
First variable (uidb64) is just base64-encoded ID of user, extracted from database. By this field Django can determine which user is requesting password reset.
Second variable (token) is an actual reset token that is used to verify user request. User can get this token only from email that was sent to him, so this verifies that user has access to provided email address, so we can proceed with password reset.
Why we can't use token alone? There are 2 reasons for that
Django doesn't store password reset tokens in database. A lot of other frameworks will do that and after user clicks password reset URL, token will be found in database and from that database entry, user requesting reset will be determined. Instead, Django uses some clever cryptography methods and generates this token from SECRET_KEY stored in database, current user password hash and time of generating this token. After that, only time can be extracted from token and by fetching user data together with SECRET_KEY from settings, Django can verify that password reset token is valid for specified user. Time of token generation is here, so every token can expire after some time.
User ID cannot be easily embedded in token, as time is, as User ID format can be customized (you can embed your own user model that uses for example UUID instead of numerical value). That's why Django is encoding this ID using Base64 - it guarantees that every format of user ID can be easily embedded in URL. For that reason, User ID can have different length and format, so it won't be easy to extract it from token string.
As for passing kwargs twice, here is quick explanation by example:
In python, you can return function from calling any other function or method (most often those kind of functions are called factories):
def some_factory():
def some_function():
print('abc')
return some_function
my_function = some_factory()
In this example, print won't be called as we are not executing some_function, some_factory returns it, so we can use it later:
my_function()
Now this print will be called and we will see abc in console output. But instead of assigning returned function to some variable or passing it somewhere, you can call it immediately:
some_factory()()
This is where the second parentheses come from. Of course both functions can take some arguments, then you will provide arguments for factory inside first pair of parentheses and arguments to some_function in second.
Back to your example, it is actually invalid, you shouldn't pass full kwargs to as_view in PasswordResetConfirm. It should take only first two (template_name and success_url). Actual view (second parentheses) should take other two (token and uidb64).
2nd thing that is wrong in your code is that you're calling as_view on every request. It is designed to be called only once, when creating this view. Instead of wrapping it in separate function, use it directly in your urls.py:
url(
r"^password/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$",
PasswordResetConfirm.as_view(
template_name="account/password_reset_from_key.html"
success_url=reverse_lazy("account:reset-password-complete"),
), name="reset-password-confirm",
)

Request Approvals by E-mail and process it Python + Django

Maybe I am not asking the right question in the search area, but I can't find an answer for this. I am pretty sure that many people have this use case, but as a beginner in Django + Python, I need to ask it.
I have user that fills up a form and the data is stored in the database. Basically this form asks for an access to a Database and after the form is submitted I want my program to send an email to the user's manager and to the DBA to APPROVE or DENY it. Very simple, right?
My idea is that in this e-mail I send two URL's, one for approving and one for denying the request. When the URL the is clicked I send a response to the server with an update in the manager_approval field.
Has anyone implemented this solution, or could point me to something that could help me?
I am doing everything using Django + Python.
Regards,
Marcos Freccia
Basically this technique used in email verification. This is where you should look into.
Let's say you have model, named request, which has field like username to identify the person who requested access, database name, well, everything. But it will also have two "password-like" fields which will be used to determine if request was declined or not.
class Request(models.Model):
user = models.ForeignKey ...
databasename =
date =
...
access_granted = models.BooleanField(default=False)
deny_token = models.CharField()
allow_token = models.CharField()
The point is to generate those tokens on saving request in the View:
if request.method == POST:
form = RequestForm(request.POST)
if form.is_valid():
data['user'] = form.cleaned_data['user'])
data['databasename'] = form.cleaned_data['databasename'])
...
data['access_token'] = GENERATE_USING_HASH_FUNCTION()
data['deny_token'] = GENERATE_USING_HASH_FUNCTION()
form.save(data)
Then you can use module EmailMultiAlternatives to send html email like so:
subject, from_email, to = 'Request', 'admin#example.com', form.cleaned_data['manager_email']
html_content = render_to_string(HTML_TEMPLATE, CONTEXT) # Just as any regular templates
text_content = strip_tags(html_content)
msg = EmailMultiAlternatives(subject, text_content, from_email, [to], reply_to=["admin#example.com"])
msg.attach_alternative(html_content, "text/html")
msg.send()
And inside that template you construct reverse url:
{% url 'app:grant_access' allow_token=token %} # "token" you get from context
{% url 'app:deny_access' deny_token=token %} # will become example.com/deny_access/7ea3c95, where 7ea3c95 is token
Then add lines to urls.py of your app like that:
url(r'^allow_access/(?P<allow_token>[0-9]+)$', CheckAcessView.as_view(), name="app:grant_access"),
url(r'^deny_access/(?P<deny_token>[0-9]+)$', CheckAcessView.as_view(), name="app:deny_access"),]
Then create CheckAcessView view. Where you access request stored in your database and check if, for example, parameter of url "allow_token" is equal stored allow_token. If so, change request status to allowed.

How can I get get_absolute_url to render a string in views.py in Django

I am trying to send an email with HTML content. The email would include a link to a DetailView of a specific instance of a model.
So in the "post" part of view which sends the email, I have something like:
html_content = ''+str(text)+''
The email that gets sent is:
my instance name
How do I render an HTTP string in views.py?
Thank you
albar was correct. I had tried get_absolute_url with and without parenthesis but never without parentheses and without the str() function. It did not occur to me to not use the str() function.

django-post_office - Using file-based instead of database-based email templates

I'm using django-post_office to send emails to users. Post office uses templates stored in the database in the EmailTemplate model. I'd prefer to use file-based templates so I can keep them under version control.
An Email is created and the template is rendered in mail.py:
def create(sender, recipients=None, cc=None, bcc=None, subject='', message='',
html_message='', context=None, scheduled_time=None, headers=None,
template=None, priority=None, render_on_delivery=False,
commit=True):
...
if template:
subject = template.subject
message = template.content
html_message = template.html_content
if context:
_context = Context(context)
subject = Template(subject).render(_context)
message = Template(message).render(_context)
html_message = Template(html_message).render(_context)
Can anyone suggest a nice way to override this behavior? I was thinking I'd like to be able to pass a string with the template location, and render based on this (some context variables), but any input would be appreciated.
One option is to render the email template before calling mail.send() along with any extra context variables.
from django.template import Context, loader
template = loader.get_template(template_name)
context = Context(extra_context)
html_message = template.render(context)
Then instead of passing a template:
mail.send(template=template)
pass rendered html with the html_message argument:
mail.send(html_message=html_message)
I don't think that is very simple, as when we see the code which creates and sends an email, I see that the mail.send(...) calls internally mail.create(...)
Now here is that code:
def send(recipients=None, sender=None, template=None, context=None, subject='',
message='', html_message='', scheduled_time=None, headers=None,
priority=None, attachments=None, render_on_delivery=False,
log_level=None, commit=True, cc=None, bcc=None):
#-------------
#code sections
#-------------
if template:
if subject:
raise ValueError('You can\'t specify both "template" and "subject" arguments')
if message:
raise ValueError('You can\'t specify both "template" and "message" arguments')
if html_message:
raise ValueError('You can\'t specify both "template" and "html_message" arguments')
# template can be an EmailTemplate instance or name
if isinstance(template, EmailTemplate):
template = template
else:
template = get_email_template(template)
email = create(sender, recipients, cc, bcc, subject, message, html_message,
context, scheduled_time, headers, template, priority,
render_on_delivery, commit=commit)
The interesting piece of code is:
# template can be an EmailTemplate instance or name
if isinstance(template, EmailTemplate):
template = template
else:
template = get_email_template(template)
The get_email_template gets the template using either the EmailTemplate or name of the EmailTemplate which is during cache creation.
Even if you do not pass the object of EmailTemplate (in their case the model), they have just two cases:
Either get the data from the cache (which is again stored for first access from DB, since template wont change)
Get directly from DB
Either way they use DB and have no interface for using from a file.
I would suggest something like this:
Create a json file that stores your template in the format that suffices the EmailTemplate object creation. Here are the properties of EmailTemplate model class:
name = models.CharField(max_length=255, help_text=("e.g: 'welcome_email'"))
description = models.TextField(blank=True, help_text='Description of this template.')
subject = models.CharField(max_length=255, blank=True, validators=[validate_template_syntax])
content = models.TextField(blank=True, validators=[validate_template_syntax])
html_content = models.TextField(blank=True, validators=[validate_template_syntax])
created = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
Read that json file in python using file read and convert the read string to python object by json dump or something (i guess you can figure that out)
Create the EmailTemplate object using the value you need and assign it to template in mail.send(...
Update:
If you use html_message, you still have maintain separate subject parameter, which can be avoided if you use template. So if you use json file, in your version control, from the subject,content,name and html_message everything will be stored in one file which can be handled in the file source control and recreate the template object and not worry about segregated values in the files to maintain.

Categories