Pixel Tracking for a django based email - python

I wanted to know, which of the emails, that I have sent to have opened the email.
Here is how I approach to solve the problem -
Create an image file in the html template that should be rendered.
< img src="{{ tracking_url }}" height="1" width="1">
Once an email is opened a request will be made to a url, which will have base64 encoded url pattern:
base64_pattern = r'([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)'
url(r'^tracking/(?P{})/$'.format(base64_pattern), 'tracking_image_url', name='tracking_image_url'),
That URL will serve an image, and update the counter, as follows -
TRANSPARENT_1_PIXEL_GIF = "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00\x21\xf9\x04\x01\x00\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b"
view -
def tracking_image_url(request, mail_64=None):
eml = base64.b64decode(mail_64)
// Update the counters in the DB table for the email address.
return HttpResponse(TRANSPARENT_1_PIXEL_GIF, content_type='image/gif')
I do all this, but can't track the opens, what am I missing?

Try this
import base64
from django.http import HttpResponse
PIXEL_GIF_DATA = base64.b64decode(
b"R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
def index(request):
return HttpResponse(PIXEL_GIF_DATA, content_type='image/gif')

Related

Unable to load correct page in Bottle Python

i'm having a hard time understanding why i can't load the "/ticket" page properly, but i can load the "/" fine. I should have the /ticket page running and able to type the information under username and email but i'm missing something.
I'm running Python 3.10.4
"localhost:8080/"
"localhost:8080/ticket"
import os
import bottle
import base64
import traceback
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from binascii import hexlify
from html import escape
from bottle import route, request, get, post, response
page = '''
<html>
<head><title>Welcome</title></head>
<body>
<p><h2>Welcome, %s!</h2></p>
<p><h3>There are %s tickets left</h3></p>
<p>%s</p>
</body>
</html>
'''
key = os.urandom(32)
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
def get_ticket(username, email, amount):
encryptor = cipher.encryptor()
padder = padding.PKCS7(128).padder()
message = padder.update("%s&%s&%d" % (username, email, amount)) + padder.finalize()
ct = encryptor.update(message) + encryptor.finalize()
return base64.b64encode(ct)
#bottle.route('/', method=('GET', 'POST'))
def default():
decryptor = cipher.decryptor()
unpadder = padding.PKCS7(128).unpadder()
try:
ticket = bottle.request.get_cookie('ticket')
if ticket:
message = decryptor.update(base64.b64decode(ticket)) + decryptor.finalize()
username, email, amount = (unpadder.update(message) + unpadder.finalize()).split('&')
return page % (escape(username), escape(amount),
'Already done' if amount == '1' else
'Go ahead')
else:
return '<html><body><p><a href=/ticket>Fill in the forms</a></p></body><html>\n'
except Exception as e:
print(traceback.format_exc())
raise bottle.HTTPError(status=401, body=e.message)
#bottle.post('/ticket', ['GET', 'POST'])
def ticket():
username = bottle.request.forms.get('username')
email = bottle.request.forms.get('email')
if username and email:
ticket = get_ticket(username, email, 1)
bottle.response.set_cookie('ticket', ticket)
return '<html><body><p>Thank you!</p></body></html>\n'
raise bottle.HTTPError(status=400, body='Please fill in the form')
bottle.run(host='localhost', port=8080)
The reason this code hits an error is that you never actually serve a form for the user to fill in (and thus your code correctly errors). I don't really understand your flow at all, but I think it's supposed to look something like this:
user navigates to /
if user doesn't have a ticket, user is sent to a login page
user logs in and gets a ticket
user goes back to / and requests it, this time with a ticket (authentication cookie) in the get request.
Mind you I don't understand what these tickets are, as they appear to be a hashed form of whatever the user submits and the constant 1, but I guess there's more logic coming along later.
Currently there are quite a few problems with the setup:
/ accepts post requests, but never does anything with them. It also doesn't make sense for it to accept post requests, since POST is to send data to a server.
your /ticket endpoint would do what you want if you sent it a form, but you never serve a form for the user to fill in.
your /ticket endpoint needs to return one thing if you make a GET request to it, another completely different thing if you make a POST request. This is possible, but it's a bad way to build these endpoints in general.
after getting the cookie the browser is simply going to throw it away. If you want to do cookie-based authentication, you need to store the cookie and handle it (normally you use this with JS in the webbrowser, and take care to add it to all requests).
If we add a login endpoint:
#bottle.route("/login")
def login():
return """<html><body>
<form action="./ticket" method="POST">
Username: <input type="text" name="username"><br>
Email: <input type="text" name="email"><br>
<input type="submit">
</form>
</body></html>"""
And edit the previous href to point us to /login, we now get a form. Filling that form in and pressing 'submit' correctly sends the data to the /ticket endpoint, calling the code to get the ticket. Unfortunately that fails for unrelated reasons (your hashing needs bytes, not strings) but that's a different question.
I suggest you have a think about the overall flow of this application---what are these tickets for, and where is the user supposed to end up? Regardless you will need to serve some kind of login form at some point. You probably want this to be a static file, by the way, and served as such---see the bottle docs.

Django channels and file uploads

I'm learning Django on the fly so bear with me. My goal here is for a user to be able to upload a file, the server script process it and send the results on each iteration to the client live.
My current structure is: User uploads a file (csv), this is read via views.py then renders the blank html view, JS in the html creates a new websocket, and well this is where I'm not able to pass data outside of consumers.py or process the data.
I'm also not sure the structure of view > JS script > consumers > ? is the best route for processing files but I've not found much documentation on file uploads and channels.
views.py
from django.shortcuts import render
import pandas as pd
def someFunc(request):
if request.method == "POST":
global csvUpload
csvUpload = pd.read_csv(request.FILES['filename'])
return render(request, 'app/appPost.html')
I render the view here first so that the blank page displays prior to the script running.
appPost.html JS script
var socket = new WebSocket('ws://localhost:8000/ws/app_ws/')
socket.onmessage = function(event){
var data = JSON.parse(event.data);
console.log(data);
var para = document.createElement("P");
para.innerText = data.message;
document.body.appendChild(para);
}
consumers.py
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
class WSConsumer(WebsocketConsumer):
def connect(self):
self.accept()
self.render()
async_to_sync(self.add_group)('appDel_updates_group')
Ultimately I'd then like to pass what's in this alarm file to the websocket connection.
app_alarm.py
from requests.api import request
import pandas as pd
import requests as r
def app_process(csvUpload):
csvUpload=pd.read_csv(request.FILES['filename'])
csvFile = pd.DataFrame(csvUpload)
colUsernames = csvFile.usernames
for user in colUsernames:
req = r.get('https://reqres.in/api/users/2')
t = req.json()
data = t['data']['email']
message = user + " " + data
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
'appDel_updates_group',
{'type': 'render', 'message': message}
)
How do you do something after you render the view? (Django)
This seems somewhat relevant, I'm going to see about creating a thread to run the file processing only after the render is done in views.
Edit: Threading allowed me to render the page then pass the data over to the alarm.py file for processing. This has solved the issue I was having by adding the following code into the views.py:
x = threading.Thread(target=app_alarm.sub_process, args=[csvUpload])
x.setDaemon(False)
x.start()
return render(request, 'efax/appPost.html')
This allows views to grab the file using request.FILES and pass it to the script before the page was rendered without waiting on the completion of the processing. That would have rendered the entire point of channels in this situation pointless. Hopefully this helps someone.
Copying from the original post edit:
Threading allowed me to render the page then pass the data over to the alarm.py file for processing. This has solved the issue I was having by adding the following code into the views.py:
x = threading.Thread(target=app_alarm.sub_process, args=[csvUpload])
x.setDaemon(False)
x.start()
return render(request, 'efax/appPost.html')
This allows views to grab the file using request.FILES and pass it to the script before the page was rendered without waiting on the completion of the processing. That would have rendered the entire point of channels in this situation pointless. Hopefully this helps someone.

How can I make SSE with Python (Django)?

I have two different pages, one (A) that displays data taken from a model object, and one (B) that changes its fields.
I would like that when the post data is sent from B to the server, the server changes the values in A.
What is the best way to do it?
This example could work for me but it's in PHP... there is a way to replicate it whit Python?
https://www.w3schools.com/html/html5_serversentevents.asp
This is working example from w3schools in Django:
template
<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
<div id="result"></div>
<script>
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("stream/");
source.onmessage = function(event) {
document.getElementById("result").innerHTML += event.data + "<br>";
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
</script>
</body>
</html>
views
import datetime
import time
from django.http import StreamingHttpResponse
def stream(request):
def event_stream():
while True:
time.sleep(3)
yield 'data: The server time is: %s\n\n' % datetime.datetime.now()
return StreamingHttpResponse(event_stream(), content_type='text/event-stream')
urls
urlpatterns = [
path('stream/', views.stream, name='stream')
]
Update:
If you want to manage your notifications you can create the model like:
from django.db import models
class Notification(models.Model):
text = models.CharField(max_length=200)
user = models.ForeignKey(User, on_delete=models.CASCADE)
sent = models.BooleanField(default=False)
Then create the view that is looking for the first unsent notification and sends it:
#login_required
def stream(request):
def event_stream():
while True:
time.sleep(3)
notification = Notification.objects.filter(
sent=False, user=request.user
).first()
text = ''
if notification:
text = notification.text
notification.sent = True
notification.save()
yield 'data: %s\n\n' % text
return StreamingHttpResponse(event_stream(), content_type='text/event-stream')
And the send_notification function that creates an entry in the Notification model (just call this function from anywhere in your code):
def send_notification(user, text):
Notification.objects.create(
user=user, text=text
)
After reading this, I think I understood the whole thing (please comment if I’m wrong).

Django does NOT natively support keep-alive connections. 
This means, when the client gets the message from the server, the connection is immediately closed after (like any classic HTTP request/response cycle).

What’s different with text/event-stream request, is that the client automatically tries to reconnect to the server every second (the length can be changed with a retry parameter).

Unfortunately, it seems using SSE in that case has no interest since it has the same con’s that polling (ie. a request/response cycle occurs each X seconds).


As expected and mentioned in other answers, I would need django-channels to create a persistent connection that prevent HTTP request/response overheads and ensure the message is sent immediately.

As mentioned in other answers, you will need to use Django Channels to properly handle asynchronous communication without tying up threads.
For an example, see the django-eventstream library which uses Channels to implement SSE.

Send html email using flask in Python

I want to send HTML webpage as a mail body using Python and Flask. I tried using MIME module, but somehow I am not being able to send mail. If anyone has any expertise on this, can you please help me.
It would be great if you could provide some code as well.
flask-mail is a good tool that I use to render the template to render HTML as the body of my mail.
from flask_mail import Message
from flask import render_template
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
def send_mail_flask(to,subject,template,**kwargs):
msg = Message(subject=subject,sender='email#ofTheSender.com', recipients=to)
msg.body = render_template('template.txt', **kwargs)
msg.html = render_template('template.html', **kwargs)
mail.send(msg)
The template is the HTML file you wish to include in your mail and you can also add a text version using msg.body!
You may need to add more environment variables according to the SMTP service you use.
I recently made a pretty awesome email library and I decided to make a Flask extension for it as well: Flask-Redmail
Install
pip install flask-redmail
Configuration
from flask import Flask
from flask_redmail import RedMail
app = Flask(__name__)
email = RedMail(app)
# Setting configs
app.config["EMAIL_HOST"] = "localhost"
app.config["EMAIL_PORT"] = 0
app.config["EMAIL_USERNAME"] = "me#example.com"
app.config["EMAIL_PASSWORD"] = "<PASSWORD>"
app.config["EMAIL_SENDER"] = "no-reply#example.com"
Simple Example
You may send emails simply:
#app.route("/send-email")
def send_email():
email.send(
subject="Example email",
receivers=["you#example.com"],
html="""
<h1>Hi,</h1>
<p>this is an example.</p>
"""
)
Template Example
It can also use your default Jinja env. Create a file emails/verify.html to your Flask template folder that looks like this:
<h1>Hi, you visited {{ request.endpoint }}.</h1>
Then use this template:
#app.route("/send-email-template")
def send_email():
email.send(
subject="Template example",
receivers=["you#example.com"],
html_template="emails/verify.html"
)
Flask Redmail is quite a simple wrapper for Red Mail and Red Mail is pretty extensive. It can:
Send attachments from various types
Embed images and plots to the HTML
Embed prettified tables to the HTML
Supports Jinja out-of-the-box
Gmail and Outlook are preconfigured
I hope you find it useful.
Links:
Documentation
Source Code
Releases
Red Mail:
Documentation
Source Code
Releases
Try using FlaskMail https://pythonhosted.org/flask-mail/
msg = Message(
recipients=[''],
sender='xx#zz.yy',
reply_to='aa#bb.cc',
subject=mail.subject
)
msg.html = mail.body
mail.send(msg)
here, mail is an imported file from "mails" directory,
and body is an HTML markup.
On a separate python file (emails.py) I created an email class that contains all the emails (as class methods) to be sent to the user based on his actions as below:
class Email:
def accountCreation():
body = "<html> <body> <p> {} </p> <br> <br> <p> Lorem Ipsum is simply dummy text of the printing and typesetting industry.</p> </body> </html>"
return body
then on another file (app.py) where I made use of flask_mail library I have:
from flask_mail import Mail, Message
import emails
mailer = emails.Email()
try:
email = "johndoe#mail.com"
body = mailer.accountCreation()
msg = Message('Hello',recipients=[email])
msg.html = body
mail.send(msg)
except Exception as e:
print(e)
Firstly, ensure there's a folder in your apps directory called templates. This is where all your html templates should be.
**server.py**
from flask-mail import Mail, Message
from flask import Flask
import os
app = Flask(__name__)
send_mail = Mail(app)
context={
"fullname": fullname,
"otp_code": otp_code,
"year": datetime.now().year,
"subject":"Registration Mail",
}
email_message = Message(
subject=mail_subject,
sender=os.getenv("ADMIN_EMAIL"),
recipients=[receipients_email],
html=render_template(template_name_or_list="otp.html", **context)
)
send_mail.send(email_message)
where "otp.html" is an existing html template in your templates folder

Run particular Django script in background

I am developing a Gmail extracting app and using Gmail API to fetch mail from server. the problem lies in the fact that fetch time for mails is too large even though I used threading in back end framework. now I am going to implement one feature which will suggest user opting for bulk download that "once your download is ready, we will mail you" but for that i want to run download.py mentioned below in app tree in background and once the fetch is over it will get terminated.
And in the very bottom of the code i want to mail user that their download is ready but its not working though i have defined the mail server in settings.py .
download.py
import httplib2, base64
from stripogram import html2text
from oauth2client.django_orm import Storage
from apiclient.discovery import build
from oauth2client import client
from django.contrib.auth.models import User
from .models import CredentialsModel
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from gextracto import models
from gextracto.models import UserData
from django.core.mail import EmailMessage
from django.core import mail
connection = mail.get_connection()
class ListMails(APIView):
"""
Gets a list of a specified number mail ids for a particular label
Extracts the email in the form of plain/text
The API returns all the extracted mails
"""
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def extract_headers(self, message):
"""
Extract the headers for a single mail and returns it
{To, From, Subject}
"""
needed_fields = ('From', 'To', 'Subject')
return {i['name']:i['value'] for i in message['payload']['headers'] if i['name'] in needed_fields}
def get_message_body(self, message):
"""
Get the body of an email
Recursively look for the body for different mimetypes
Returns the body as text/plain
"""
if 'payload' in message:
return self.get_message_body(message['payload'])
elif 'parts' in message:
return self.get_message_body(message['parts'][0])
else:
data = base64.urlsafe_b64decode(message['body']['data'].encode('ASCII'))
markdown_data = html2text(data)#.decode('utf-8', "replace")
data = data.replace("\n", "<br/>")
# return {markdown, html}
return {'markdown':unicode( markdown_data,"ISO-8859-1"), 'html':unicode(data,"ISO-8859-1")} if markdown_data else {'html':unicode(data,"ISO-8859-1")}
def message_content_html(self, userId, message_id, service):
"""
Make queries to get the content for a mail given its message id
Returns all the content
"""
content = {'id':message_id}
# try
message = service.users().messages().get(userId=userId, id=message_id).execute()
mimetype = message['payload']['mimeType']
if mimetype == 'text/html':
return {}
#
else:
body = self.get_message_body(message)
if body == "":
body = "<empty message>"
headers = self.extract_headers(message)
content['body'] = body
content.update(headers)
return content
def collect_mails(self, user, messages, service):
"""
Collect the content for all the mails currently downloaded
"""
all_messages = []
try:
for message in messages:
content = self.message_content_html(user.username, message['id'], service)
if content:
all_messages.append(content)
return all_messages
# return empty list if no messages were downloaded
except KeyError:
return []
def get(self, request, format=None):
"""
Handles the GET request to get all the mails for a label
Paginages through the GAPI content if required
API returns all the messages
{To, From, Subject, body}
"""
user = request.user
storage = Storage(CredentialsModel, 'id', user, 'credential')
credentials = storage.get()
http_auth = credentials.authorize(httplib2.Http())
service = build('gmail', 'v1', http=http_auth)
user_Id = user.username
label_id = request.GET['label']
# try
# call Google API with a request to get a list of all the labels
response = service.users().messages().list(userId=user_Id, labelIds=label_id, maxResults=100).execute()
all_messages = self.collect_mails(user, response['messages'], service)
if not all_messages:
return Response([])
else:
if 'nextPageToken' in response:
page_token_flag = True
# request more more mails if the download limit has not yet been satisfied
while(page_token_flag):
response = service.users().messages().list(userId=user_Id, pageToken=response['nextPageToken'], maxResults=100).execute()
all_messages.append(self.collect_mails(user, response['messages'], service))
print(all_messages)
#for x in range(0,len(all_messages)):
#b=all_messages[10]
#instance= UserData(user_id=user ,label=label_id, sender = b['From'] , subject=b['Subject'] , body=b['body'])
#instance.save()
page_token_flag = 'nextPageToken' in response
##
for x in range(0,len(all_messages)):
b=all_messages[10]
instance= UserData(user_id=user ,label=label_id, sender = b['From'] , subject=b['Subject'] , body=b['body'])
instance.save()
print ("Hi i am here!!!")
email = EmailMessage('Your Download Ready!', 'http://127.0.0.1:8000/admin/gextracto/userdata/', to=[user], connection=connection)
email.send()
connection.close()
return Response(all_messages)
Please tell me the way to run it in background. if need any other info please do ask. Thanks
Don't know the exact requirements but I'll think about Celery to run background tasks. This approach allows to manage all post-script activities in native Django manner.
Also you can think about running the Django script using cron (as manage.py command) - but it can lead to some limitations.
What about sending emails failure - believe, you don't need to close connection after sending email. Usually I use send_mail()/send_mass_mail() functions - please, check their code to get an idea.

Categories