How to send an email to a user programatically? (OpenERP & Python) - python

In my OpenERP modules I occasionally need to send notifications or messages to my users, but the obvious way (to me, at least) of using res.users.message_post put a message on the users record instead of sending the message to the user. Any ideas?

Before you can send messages you need to have the Social Network module installed (it lives in .../addons/mail if you want to see its code).
After that, the following snippet will do the job:
from openerp import SUPERUSER_ID
def send_message(oe, cr, user_ids, subject, body):
"sends message to user_ids"
mail_message = oe.pool.get('mail.message')
mail_message_subtype = oe.pool.get('mail.message.subtype')
[discussion_id] = mail_message_subtype.browse(
cr, SUPERUSER_ID, [('name','=','Discussions')]
)
users = oe.pool.get('res.users').search(cr, uid, user_ids)
mail_message.create(cr, SUPERUSER_ID, values=dict(
type='email',
subtype_id=discussion.id,
partner_ids=[(4, u.partner_id.id) for u in users],
subject=subject,
body=body,
))
Stuff this in a utility module somewhere and you're good to go.
A couple comments:
When calling, pass self as the first parameter
send_message(self, cr, ...)
if you want to make this a method of a class instead, just replace the oe's with self, and omit self from the parameters
This will send a message inside OpenERP to the users and, if any user has his/her preferences set up to forward to external email, an actual email will be generated for them.

Related

Django - testing emails with the outbox using post_office

I'm writing tests for an application that uses the django-post_office package for most of its email functionality.
The default django.core.mail library contains plenty of useful tools for testing whether or not there are actually emails being sent. (Without actually sending any during the tests)
class TestFunctionThatSendsEmail(Testcase):
#override_settings(
EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend'
)
def test_function_sends_email(self):
self.assertEqual(len(outbox), 0)
run_function_that_calls_email()
self.assertEqual(len(outbox), 1)
...
# other tests
However, the emails in our function are being sent with the django-post_office mail.send() function
# priority now to make sure that they are being sent right away
mail.send(
sender=sender,
recipients=to,
context=context,
template=template_name,
priority='now',
)
Which causes the test above to fail as for some reason the emails do not end up in the outbox.
The strange thing is that if I change the EMAIL_BACKEND to django.core.mail.backends.console.EmailBackend the emails do show up in my terminal, so it is listening to the EMAIL_BACKEND settings.
I've tried finding alternative methods / functions to test this functionality in the django-post_office github, but all I could find was the advice to check to see if the emails are saved to the database and verify their status. (Which I did and works) but the fact that django seems to be unable to detect any emails actually being sent is making me a little bit nervous.
Does anyone know of a way to make emails sent by django-post_office appear in the outbox or, if that is not possible, a way to make sure that they are actually being sent? (beyond checking the database)
The issue is that django-post_office stores the mail backend in the Email object:
class Email(models.Model):
backend_alias = models.CharField(_("Backend alias"), blank=True, default='',
max_length=64)
When dispatch() is called, it uses this backend.
When creating an email, DPO overrides create() to set the backend from def get_available_backends(), which looks up the backend in the settings conf.
This means that using #override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend') won't set the backend_alias in the Email object correctly.
Instead you need to do this manually when you create the object, as per the dispatch test:
def test_dispatch(self):
"""
Ensure that email.dispatch() actually sends out the email
"""
email = Email.objects.create(to=['to#example.com'], from_email='from#example.com',
subject='Test dispatch', message='Message', backend_alias='locmem')
email.dispatch()
self.assertEqual(mail.outbox[0].subject, 'Test dispatch')
If you are using mail.send() you can simply pass the mail.send(backend='locmem') as an argument; make sure you have locmem': 'django.core.mail.backends.locmem.EmailBackend' in your POST_OFFICE settings
I was having the same issue as you described and fixed it by setting the DEFAULT_PRIORITY to 'now':
class TestFunctionThatSendsEmail(Testcase):
#override_settings(
POST_OFFICE={
'BACKENDS': {'default': 'django.core.mail.backends.locmem.EmailBackend'},
'DEFAULT_PRIORITY': 'now',
}
)
def test_function_sends_email(self):
self.assertEqual(len(outbox), 0)
run_function_that_calls_email()
self.assertEqual(len(outbox), 1)
...
# other tests

Jupyterhub Custom Authenticator

I am a little stuck with writing a custom authenticator for jupyterhub. Most probably because I do not understand the inner workings of the available REMOTE_USER authenticator. I am not sure if it is applicable in my case... anyhow... this is what I'd like to do:
My general idea: I have a server that authenticates a user with his or her institutional login. After logging into the institution server/website, the users' data are encoded -- only some details to identify the user. They are then redirected to a the jupyterhub domain in the following way
https://<mydomain>/hub/login?data=<here go the encrypted data>
Now, if a request gets sent like this to my jupyterhub-domain, I'd like to decrypt the submitted data, and authenticate the user.
My trial:
I tried it with the following code. But it seems I am too nooby... :D
So please, pedantic comments are welcome :D
from tornado import gen
from jupyterhub.auth import Authenticator
class MyAuthenticator(Authenticator):
login_service = "my service"
authenticator_login_url="authentication url"
#gen.coroutine
def authenticate(self,handler,data=None):
# some verifications go here
# if data is verified the username is returned
My first problem... clicking the button on the login page, doesn't redirect me to my Authentication URL... it seems the variable authenticator_login_url from the login template is set somewhere else...
Second problem... a request made to .../hub/login?data=... is not evaluated by the authenticator (it seems...)
So: Has somebody any hints for me how to go about this?
As you see I followed the tutorials here:
https://universe-docs.readthedocs.io/en/latest/authenticators.html
So the following code does the job, however, I am always open to improvements.
So, what I did was redirect an empty login attempt to the login-url and deny access. If data is presented, check the validity of the data. If verified, user can login.
from tornado import gen, web
from jupyterhub.handlers import BaseHandler
from jupyterhub.auth import Authenticator
class MyAuthenticator(Authenticator):
login_service = "My Service"
#gen.coroutine
def authenticate(self,handler,data=None):
rawd = None
# If we receive no data we redirect to login page
while (rawd is None):
try:
rawd = handler.get_argument("data")
except:
handler.redirect("<The login URL>")
return None
# Do some verification and get the data here.
# Get the data from the parameters send to your hub from the login page, say username, access_token and email. Wrap everythin neatly in a dictionary and return it.
userdict = {"name": username}
userdict["auth_state"] = auth_state = {}
auth_state['access_token'] = verify
auth_state['email'] = email
#return the dictionary
return userdict
Simply add the file to the Python path, so that Jupyterhub is able to find it and make the necessary configurations in your jupyterhub_config.py file.

Tornado Websocket "error: Authentication missing"

So I've got redis feature and tornado running on my server and whenever I open my websocket chat through a login, the terminal displays the following message
Error: Authentication missing
I'm not sure why this is happening because there are cookies in the authentication part of the app,
# Save user when authentication was successful.
def on_user_find(result, user=user):
##todo: We should check if email is given even though we can assume.
if result == "null" or not result:
# If user does not exist, create a new entry.
self.application.client.set("user:" + user["email"], tornado.escape.json_encode(user))
else:
# Update existing user.
# #todo: Should use $set to update only needed attributes?
dbuser = tornado.escape.json_decode(result)
dbuser.update(user)
user = dbuser
self.application.client.set("user:" + user["email"], tornado.escape.json_encode(user))
# Save user id in cookie.
self.set_secure_cookie("user", user["email"])
self.application.usernames[user["email"]] = user.get("name") or user["email"]
And in the websocket.py (where I run the script) I've made it so that the websocket handle checks if there are cookies available first before user access the app,
class ChatSocketHandler(tornado.websocket.WebSocketHandler):
def open(self, user):
self.login = self.get_secure_cookie("user")
if not self.login:
# self.login = "anonymous"
print "Not authorized"
self.disconnect()
return
Yet it's still displaying the error, I've searched online and checked several SO answers but they don't show any solid solution in regards to this question. So far the most I've gotten is that I have to access the websocket header to put the above code inside, but I have no clue how I would do that. Help?

Avoid Mandrill errors with Sentry

I have installed a Sentry instance on my server, and I want to configure it to send alert emails using Mandrill and its Django integration djrill. Here's the relevant settings in the sentry.conf.py file I'm using:
EXTRA_INSTALLED_APPS = (
'djrill',
)
EMAIL_BACKEND = 'djrill.mail.backends.djrill.DjrillBackend'
MANDRILL_API_KEY = '[... Mandril API key ...]'
DEFAULT_FROM_EMAIL = 'my-mandrill-allowed#email.address'
SERVER_EMAIL = 'my-mandrill-allowed#email.address'
And this setup works, except for the part that for some reason Mandrill doesn't allow setting the Message-Id header:
NotSupportedByMandrillError: Invalid message header 'Message-Id' - Mandrill only allows Reply-To and X-* headers
(This exception is raised by djrill, is not a response from Mandrill)
Which is set by Sentry here:
class MessageBuilder(object):
# ...
#cached_property
def message_id(self):
if self.reference is not None:
return email_id_for_model(self.reference)
I have managed to make it work by editing that method and make it always return None, so no Message-Id header is set in the email. But I don't like to edit/patch 3rd party code and I have no idea if that header is needed elsewhere.
How to accomplish this correctly? Switching from Mandrill is not an option right now.
Thanks!
As you can't easily change Sentry's behavior, as far as I can tell, I'd suggest implementing a subclass of DjrillBackend that removes the Message-Id header before the messages are sent. Something like (untested):
class HeaderRemovingBackend(DjrillBackend):
def send_messages(self, email_messages):
for message in email_messages:
if 'Message-Id' in message.extra_headers:
del message.extra_headers['Message-Id']
super(HeaderRemovingBackend, self).send_messages(email_messages)

Send a Facebook Message with XMPP using Access Tokens in Python

This is a very specific question, but I cannot find any documentation on how I can do it. The Facebook Documentation is pretty vague with some horrible and useless PHP examples (really, it's code like the Facebook PHP Sample Code that make people think PHP sucks) but I cannot find anything around for Python.
I can't even work out how to apply the same principles from the PHP sample code into a Python world. The xmpppy and SleekXMPP docs are a bit bare (or broken) and Google only shows examples of people using passwords.
I have the access tokens coming from the database, I have no interest in spawning a browser to find stuff, or doing anything else to find a token. I have them, consider it a hardcoded string. I want to pass that string to XMPP and send a message, that is the whole scope of things.
Any suggestions?
The below code worked, but only after some modifications mentioned in this thread
I answered this with a link to a blog I wrote because it described the solution perfectly, but apparently that annoyed some moderators.
While that's clearly ridiculous, here is the answer reposted.
import sleekxmpp
import logging
logging.basicConfig(level=logging.DEBUG)
class SendMsgBot(sleekxmpp.ClientXMPP):
def init(self, jid, recipient, message):
sleekxmpp.ClientXMPP.__init__(self, jid, 'ignore')
# The message we wish to send, and the JID that
# will receive it.
self.recipient = recipient
self.msg = message
# The session_start event will be triggered when
# the bot establishes its connection with the server
# and the XML streams are ready for use. We want to
# listen for this event so that we we can initialize
# our roster.
self.add_event_handler("session_start", self.start, threaded=True)
def start(self, event):
self.send_presence()
self.get_roster()
self.send_message(mto=self.recipient, mbody=self.msg, mtype='chat')
# Using wait=True ensures that the send queue will be
#emptied before ending the session.
self.disconnect(wait=True)
I shoved that in a file called fbxmpp.py, then in another file (your worker, your command line app, your Flask controller, whatever) you'll need something like the following:
from fbxmpp import SendMsgBot
# The "From" Facebook ID
jid = '511501255#chat.facebook.com'
# The "Recipient" Facebook ID, with a hyphen for some reason
to = '-1000023894758#chat.facebook.com'
# Whatever you're sending
msg = 'Hey Other Phil, how is it going?'
xmpp = SendMsgBot(jid, to, unicode(msg))
xmpp.credentials['api_key'] = '123456'
xmpp.credentials['access_token'] = 'your-access-token'
if xmpp.connect(('chat.facebook.com', 5222)):
xmpp.process(block=True)
print("Done")
else:
print("Unable to connect.")

Categories