Send mass mail in Django -- Too many values to unpack - python

I'm experimenting with Django's mass_mail function. The code below keeps raising a "Too Many Values to Unpack" error, and I can't figure out why. I'm following the docs (https://docs.djangoproject.com/en/1.5/topics/email/#send-mass-mail) which seem pretty straightforward--what am I doing wrong? If it matters, the send-email address is made up, but I can't see that mattering.
if matching_record.level == 1:
users = self._get_users_to_be_notified(matching_record.category)
email_recipients = [str(user.email) for user in users if user.email]
message = 'Here is your requested notification that the service "%s" is having technical difficulties and has been set to "Critical".' %matching_record.name
mail_tuple = ('Notification',
message,
'notifications#service.com',
email_recipients)
send_mass_mail(mail_tuple)

send_mass_mail method's first argument is a tuple of message tuples, but you are sending just a message tuple. Change the function calling like below and check if it works:
send_mass_mail((mail_tuple,))

Related

Making 1 message handler return the same function to every input

I'm trying to make 1 message handler return one same function to every input it gets.
For example, instead of making these 2 message handler return the same function 'editing' when the input is equal to 'Edit' or 'ADD':
messageHandler(
Filters.regex('^(Edit)$'), editing),
messageHandler(
Filters.regex('^(ADD)$'), editing)
I wanted to add something like :
messageHandler(
Filters.regex('^(%s)$' %(user_input)), editing)
So everything its typed returns the same function 'editing'
For a more specific case, if necessary, consider this code . If, for some reason, I wanted to add only 1 message handler which returns the function 'start' to everything the user types. How could it be done?
This question has already been answered at the Github discussions of python-telegram-bot where it was pointed out that one can use Filters.text to filter for all incoming text messages. Alternatively, Filters.all would even accept all non-text messages.

How to find 'user_hash' on telegram?

I'm trying to use telethon and telebot to send notification from my code in python.
I have configureted everythings, but the code ask me the following:
receiver = InputPeerUser('user_id', 'user_hash')
I fund user_id, but I don't understand where I could catch my user_hash.
You must have an open dialogue with that user. Or you received a message from him. When all the dialogs are loaded, the information of that user comes with it.
If you don't have a 'user_hash', just enter 0.
I.e.:
userid=555555555
userhash=0
receiver=InputPeerUser(userid, userhash)
For those requests having a “limit” parameter, you can often set it to
zero to signify “return default amount”. This won’t work for all of
them though, for instance, in “messages.search” it will actually
return 0 items.
Reference:
https://telethonn.readthedocs.io/en/latest/extra/advanced-usage/accessing-the-full-api.html

GAE REQUEST_LOG_ID env variable is wrong

Using the code below I'm sending an email on error. I'm trying to include a link to the Cloud Console logs in the email but the request ID seems to be wrong about 30% of the time.
If I find the request with the wrong ID it's always almost a perfect match except the last three characters are 0 (in the Stackdriver console) instead of 101 (returned from the env variable), always the same substitution - is this a bug with cloud console or am I trying to use these IDs wrong?
The code (stripped down version):
class ErrorAlertMiddleware(object):
def process_response(self, request, response):
if response.status_code == 500:
logger.info(os.environ.get('REQUEST_LOG_ID'))
msg = 'Link to logs: https://console.cloud.google.com/logs/viewer?' + '&'.join((
'project=%s' % MY_APP_ID,
'expandAll=true',
'filters=request_id:%s' % os.environ.get('REQUEST_LOG_ID'),
'resource=gae_app',
))
# this is a utility func that simply sends email
sendemail(ERROR_RECIPIENT, msg)
return response
Note I've also logged the REQUEST_LOG_ID to ensure it's not being encoded or something and the log output matches what shows in the link
Instead of os.environ.get('REQUEST_LOG_ID'), use request.environ.get('REQUEST_LOG_ID').
It may be possible that os.environ['REQUEST_LOG_ID'] changes between the start of the current request and the time you access it, but request.environ['REQUEST_LOG_ID'] should not change once the request is initialized. The docs state that if one request ID is greater than another, than it occurred later than the other. This implies that the requestID in the Stackdriver console was generated before the one in your email link. This makes me think that somewhere along the line, os.environ['REQUEST_LOG_ID'] is being updated from '....000' to '....101' before you access it, while the copy in request.environ['REQUEST_LOG_ID'] should remain unchanged.
For more info on the request.environ, take a look at the source code in google.appengine.runtime.request_environment.py. I haven't really found documentation on that, but that code led me to believe that the os.environ is not as safe to access as the request.environ.

Redis publish - Wrong number of argument for 'set'

I'm trying to use websockets with Django for small parts of my application.
Trying the first example to broadcast a message with django-websocket-redis
from ws4redis.publisher import RedisPublisher
redis_publisher = RedisPublisher(facility='foobar', broadcast=True)
redis_publisher.publish_message('Hello World')
I'm actually receiving the message into subscribed clients but I'm getting this error:
wrong number of arguments for 'set' command
[...]
Exception location my_virtualenv/local/lib/python2.7/site-packages/redis/connection.py in read_response, line 344
(traced from the publish_message() call)
My versions:
Django==1.6.2
django-websocket-redis==0.4.0
redis==2.9.1
Can someone help me to debug that ?
Looks like it's a bug.
Fix:
in ws4redis.redis_store.RedisStore in publish_message, change
self._connection.set(channel, message, ex=expire)
to
self._connection.setex(channel, expire, message)
the redis SET command does not take a 3rd argument. What I believe was meant was to set a value that expires after a number of seconds, which is the redis SETEX command. The py-redis setex method is called like setex(name, time, value).
This resolves the "Wrong number of argument for 'set'" error.
ref: https://github.com/jrief/django-websocket-redis/pull/30
I finally set the expiration time to 0 as a workaround
WS4REDIS_EXPIRE = 0
This prevents ws4redis to store anything in redis.
Fixed since 0.4.1

LDAP: ldap.SIZELIMIT_EXCEEDED

I am getting an ldap.SIZELIMIT_EXCEEDED error when I run this code:
import ldap
url = 'ldap://<domain>:389'
binddn = 'cn=<username> readonly,cn=users,dc=tnc,dc=org'
password = '<password>'
conn = ldap.initialize(url)
conn.simple_bind_s(binddn,password)
base_dn = "ou=People,dc=tnc,dc=org"
filter = '(objectClass=*)'
attrs = ['sn']
conn.search_s( base_dn, ldap.SCOPE_SUBTREE, filter, attrs )
Where username is my actual username, password is my actual password, and domain is the actual domain.
I don't understand why this is. Can somebody shed some light?
Manual: http://www.python-ldap.org/doc/html/ldap.html
exception ldap.SIZELIMIT_EXCEEDED
An LDAP size limit was exceeded. This
could be due to a sizelimit
configuration on the LDAP server.
I think your best bet here is to limit the sizelimit on the message you receive from the server. You can do that by setting the attribute LDAPObject.sizelimit (deprecated) or using the sizelimit parameter when using search_ext()
You should also make sure your bind was actually successful...
You're encountering that exception most likely because the server you're communicating with has more results than can be returned by a single request. In order to get around this you need to use paged results which can be done by using SimplePagedResultsControl.
Here's a Python3 implementation that I came up with after heavily editing what I found here and in the official documentation. At the time of writing this it works with the pip3 package python-ldap version 3.2.0.
def get_list_of_ldap_users():
hostname = "<domain>:389"
username = "username_here"
password = "password_here"
base = "ou=People,dc=tnc,dc=org"
print(f"Connecting to the LDAP server at '{hostname}'...")
connect = ldap.initialize(f"ldap://{hostname}")
connect.set_option(ldap.OPT_REFERRALS, 0)
connect.simple_bind_s(username, password)
search_flt = "(objectClass=*)"
page_size = 500 # how many users to search for in each page, this depends on the server maximum setting (default highest value is 1000)
searchreq_attrlist=["sn"] # change these to the attributes you care about
req_ctrl = SimplePagedResultsControl(criticality=True, size=page_size, cookie='')
msgid = connect.search_ext(base=base, scope=ldap.SCOPE_SUBTREE, filterstr=search_flt, attrlist=searchreq_attrlist, serverctrls=[req_ctrl])
total_results = []
pages = 0
while True: # loop over all of the pages using the same cookie, otherwise the search will fail
pages += 1
rtype, rdata, rmsgid, serverctrls = connect.result3(msgid)
for user in rdata:
total_results.append(user)
pctrls = [c for c in serverctrls if c.controlType == SimplePagedResultsControl.controlType]
if pctrls:
if pctrls[0].cookie: # Copy cookie from response control to request control
req_ctrl.cookie = pctrls[0].cookie
msgid = connect.search_ext(base=base, scope=ldap.SCOPE_SUBTREE, filterstr=search_flt, attrlist=searchreq_attrlist, serverctrls=[req_ctrl])
else:
break
else:
break
return total_results
This will return a list of all users but you can edit it as required to return what you want without hitting the SIZELIMIT_EXCEEDED issue :)
see here for what to do when you get this error:
How get get more search results than the server's sizelimit with Python LDAP?
The filter you provided (objectClass=*) is a presence filter. In this case it limits the results to the search request to objects in the directory at and underneath the base object you supplied - which is every object underneath the base object since every object has at least one objectClass. Restrict your search by using a more restrictive filter, or a tighter scope, or a lower base object, or all three. For more information on the topic of the search request, see Using ldapsearch and LDAP: Programming Practices.
Directory Server administrators are free to impose a server-wide limit on entries that can be returned to LDAP clients, these are known as a server-imposed size limit. There is a time limit which follows the same rules.
LDAP clients should always supply a size limit and time limit with a search request, these limits, known as client-requested limits cannot override the server-imposed limits, however.
Active Directory defaults to returning a max of 1000 results. What is sort of annoying is that rather than return 1000, with an associated error code, it seems to send the error code without the data.
eDirectory starts with no default, and is completely conifgurable to whatever you like.
Other directories handle it differently. (Edit and add in, if you know).
You must use paged search to achieve this.
The page size would depend on your ldap server, 1000 would work for Active Directory.
Have a look at http://google-apps-for-your-domain-ldap-sync.googlecode.com/svn/trunk/ldap_ctxt.py for an example

Categories