def test_send_subscriber(self): self.gfolder.invokeFactory('gazette.GazetteIssue', 'issue') issue = self.gfolder['issue'] issue.title = u'Test issue' issue.text = RichTextValue('Hello world!') request = self.request adapter = getMultiAdapter((self.gfolder, request), IGazetteSubscription) adapter.subscribe('*****@*****.**', '') # activation message self.failUnless(len(self.mailhost.messages) == 1) # Activate subsriber soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) s = soup.query(email='*****@*****.**').next() s.active = True s.key = u'' soup.reindex([s]) transaction.commit() browser = Browser(self.layer['app']) browser.handleErrors = False browser.addHeader('Authorization', 'Basic %s:%s' % (TEST_USER_NAME, TEST_USER_PASSWORD,)) browser.open(issue.absolute_url() + '/send') self.failUnless('Number of subscribers: 1' in browser.contents) browser.getControl(name='submit').click() self.failUnless('Gazette has been sent to 1 recipients' in browser.contents) # activation + issue self.failUnless(len(self.mailhost.messages) == 2)
def send_gazette(self): context = aq_inner(self.context) parent = aq_parent(context) now = datetime.now() CheckAuthenticator(self.request) ptool = getToolByName(self.context, 'plone_utils') soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) providers = self._providers() subject = context.Title() url = parent.absolute_url() + '/subscription?uuid=%(uuid)s' footer_text = parent.footer.output.replace('${url}', '$url') footer_text = footer_text.replace('$url', url) count = 0 text = context.text.output + '\n' for p in providers: text += p.get_gazette_text(parent, context) for s in soup.query(active=True): # returns email and fullname taken from memberdata if s.username is set and member exists subscriber_info = s.get_info(context) footer = footer_text % subscriber_info mail_text = "" if subscriber_info['salutation']: mail_text += "%s<br /><br />" % subscriber_info['salutation'] mail_text += "%s------------<br />%s" % (text, footer) try: if utils.send_mail(context, None, subscriber_info['email'], subscriber_info['fullname'], subject, mail_text): count += 1 except (SMTPException, SMTPRecipientsRefused): pass context.sent_at = now parent.most_recent_issue = context ptool.addPortalMessage(_(u'Gazette has been sent to $count recipients', mapping={'count': count})) self.request.response.redirect(context.absolute_url())
def subscribe(self, email, fullname, username=u'', send_activation_mail=True, wait_for_confirmation=True, **kwargs): # find if there is existing subscription soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) email = email.strip() if username: # ignore everything user = api.user.get(username='******') if user: email = user.getProperty('email', '') if email: # subscribe as portal user # is username is set, subscription is considered as subscription of portal member # override fullname, if any fullname = user.getProperty('fullname', fullname) if not email: return config.INVALID_DATA results = [r for r in soup.query(email=email)] _marker = object() if results: s = results[0] s.email = email s.fullname = fullname if s.active: send_activation_mail = False if wait_for_confirmation: s.key = GenerateSecret() result = config.WAITING_FOR_CONFIRMATION else: result = config.ALREADY_SUBSCRIBED else: s.active = True result = config.SUBSCRIPTION_SUCCESSFULL for k, v in kwargs.items(): has_attr = getattr(s, k, _marker) if has_attr is not _marker: setattr(s, k, v) soup.reindex([s]) if send_activation_mail: # Do not send if user is editor and has different email address self.activation_mail(s) return result else: # new subscriber s = Subscriber(email=email, fullname=fullname, active=False, username=username) if wait_for_confirmation: s.key = GenerateSecret() result = config.WAITING_FOR_CONFIRMATION else: s.active = True result = config.SUBSCRIPTION_SUCCESSFULL for k, v in kwargs.items(): has_attr = getattr(s, k, _marker) if has_attr is not _marker: setattr(s, k, v) soup.add(s) if send_activation_mail: self.activation_mail(s) return result
def test_subscribe_anon(self): logout() provideAdapter( adapts=(Interface, IBrowserRequest), provides=Interface, factory=SubscriberForm, name=u"subscriber-form" ) # empty form request = self.request adapter = getMultiAdapter((self.gfolder, request), IGazetteSubscription) self.gfolder.subscription_require_tos = True subscriberForm = getMultiAdapter((self.gfolder, request), name=u"subscriber-form") subscriberForm.update() data, errors = subscriberForm.extractData() self.assertEquals(len(errors), 1) self.assertEquals(adapter.subscribe("", ""), INVALID_DATA) self.gfolder.subscription_require_tos = False subscriberForm = getMultiAdapter((self.gfolder, request), name=u"subscriber-form") subscriberForm.update() data, errors = subscriberForm.extractData() self.assertEquals(len(errors), 1) # FIXME - there should be 1 in this test - TOS is disabled self.assertEquals(adapter.subscribe("", ""), INVALID_DATA) # fill email only request.form = {"form.widgets.email": u"*****@*****.**", "form.widgets.tos:list": u"selected"} subscriberForm = getMultiAdapter((self.context, request), name=u"subscriber-form") subscriberForm.update() data, errors = subscriberForm.extractData() self.assertEquals(len(errors), 0) self.assertEquals(adapter.subscribe("*****@*****.**", ""), WAITING_FOR_CONFIRMATION) self.assertEquals(len(self.mailhost.messages), 1) # second subscription of inactive user resends activation email self.assertEquals(adapter.subscribe("*****@*****.**", ""), WAITING_FOR_CONFIRMATION) self.assertEquals(len(self.mailhost.messages), 2) msg = _parseMessage(self.mailhost.messages[1]) self.assertEquals(msg["To"], "*****@*****.**") msgtext = msg.get_payload(decode=True) # let's retrieve the key from the message groups = re.search("\?key=([a-f0-9]+)", msgtext).groups() self.assertEquals(len(groups), 1) key = groups[0] activationView = ActivationView(self.context, request) soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) subscriber = soup.data.values()[0] self.assertEquals(subscriber.email, u"*****@*****.**") self.assertEquals(subscriber.active, False) self.assertEquals(subscriber.key, key) self.assertEquals(subscriber.username, "") activationView.activate(key) self.assertEquals(subscriber.key, u"") self.assertEquals(subscriber.active, True) self.assertEquals(subscriber.username, "")
def reindex_soup(self): id = self.request.form.get('id') if not id: return self.redirect_base('No id') soup = getSoup(self.context, id) soup.reindex() msg = '%s reindexed.' % id return self.redirect_base(msg)
def rebuild_soup(self): id = self.request.form.get('id') if not id: return self.redirect_base(msg) soup = getSoup(self.context, id) soup.rebuild() msg = '%s rebuilt.' % id return self.redirect_base(msg)
def rebuild_length(self): id = self.request.form.get('id') if not id: return self.redirect_base(msg) soup = getSoup(self.context, id) newlen = len(soup.storage.data) soup.storage.length.set(newlen) transaction.commit() return self.redirect_base(u'Length of storage %s is %s' % (id, newlen))
def subscriber(self): uuid = self.request.form.get("uuid") value = None if uuid: value = getattr(self, "_v_subscriber", None) soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) for s in soup.query(uuid=uuid): self._v_subscriber = value = s break return value
def update(self, uuid, **kwargs): soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) results = [r for r in soup.query(uuid=uuid)] _marker = object() if results: s = results[0] for k, v in kwargs.items(): if k == 'uuid': continue has_attr = getattr(s, k, _marker) if has_attr is not _marker: setattr(s, k, v) else: return config.NO_SUCH_SUBSCRIPTION
def test_subscribe_1000users(self): logout() soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) request = self.request adapter = getMultiAdapter((self.gfolder, request), IGazetteSubscription) for i in range(1, 1001): self.assertEquals(adapter.subscribe("bleh@blah-%d.com" % i, "Dummy %d" % i), WAITING_FOR_CONFIRMATION) subscribers = soup.data.values() self.assertEquals(len(subscribers), 1000) subscriber = soup.query(email="*****@*****.**").next() self.assertEquals(subscriber.fullname, "Dummy 149")
def setUp(self): self.portal = self.layer["portal"] self.request = self.layer["request"] setRoles(self.portal, TEST_USER_ID, ["Manager"]) login(self.portal, TEST_USER_NAME) _createObjectByType("gazette.GazetteFolder", self.portal, "gazettefolder") self.gfolder = self.portal.gazettefolder self.context = self.gfolder self.soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) self.soup.add(Subscriber(email="*****@*****.**", fullname="Tester One", active=True)) self.soup.add(Subscriber(email="*****@*****.**", fullname="Tester Two", active=False)) self.soup.add(Subscriber(email="*****@*****.**", fullname="Tester Three", active=True)) self.soup.add(Subscriber(email="*****@*****.**", fullname="Tester Four", active=False)) self.soup.add(Subscriber(email="*****@*****.**", fullname="Tester Five", active=True))
def __call__(self): result = [] if self.request.get('form.submitted') == '1': query = {} fulltext = self.request.get('fulltext', '') if fulltext: query['SearchableText'] = fulltext active = self.request.get('active', '') if active == '1': query['active'] = True elif active == '0': query['active'] = False result = self.search(**query) if self.request.get('form.import.submitted') == '1': fp = self.request.get('subscribers') putil = getToolByName(self.context, 'plone_utils') book = xlrd.open_workbook(filename=fp.filename, file_contents=fp.read()) sh = book.sheet_by_index(0) valid = 0 invalid = 0 duplicates = 0 if sh.nrows > 0: email = sh.row(0)[0].value if putil.validateSingleEmailAddress(email): start = 0 else: start = 1 soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) for rx in range(start, sh.nrows): # [text:u'*****@*****.**', text:u'Test 2', empty:'', empty:'', empty:''] row = sh.row(rx) email = row[0].value.strip() fullname = row[1].value.strip() if putil.validateSingleEmailAddress(email): if not [r for r in soup.lazy(email=email)]: # subscribe (don't try to pair with portal users) s = Subscriber(email=email, fullname=fullname, active=True) soup.add(s) valid += 1 else: duplicates += 1 else: invalid += 1 putil.addPortalMessage(_("Imported subscribers: ${imported}, Invalid: ${invalid}, Duplicates: ${duplicates}", mapping={'imported': valid, 'duplicates': duplicates, 'invalid': invalid})) else: putil.addPortalMessage(_("XLS file is empty")) return self.template(searchresults=result)
def deactivate(self, key): """ """ if not key: raise Forbidden("Authenticator is invalid.") ptool = getToolByName(self.context, "plone_utils") # get subscriber by key soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) results = [r for r in soup.query(key=key)] if len(results) != 1: ptool.addPortalMessage(_(u"Invalid key")) else: s = results[0] s.active = False s.key = u"" soup.reindex([s]) ptool.addPortalMessage(_(u"Your subscription has been successfully deactivated.")) self.request.response.redirect(self.context.absolute_url())
def search(self, **query): soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) result = [] ACTIVE = _(u'label_yes', default=u'Yes') INACTIVE = _(u'label_no', default=u'No') if query: rows = soup.query(**query) else: # all records rows = soup.data.values() for row in rows: result.append(dict( fullname=row.fullname, email=row.email, active=row.active and ACTIVE or INACTIVE, username=row.username, uuid=row.uuid, )) return result
def unsubscribe(self, email, send_deactivation_mail=True, wait_for_confirmation=True): soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) results = [r for r in soup.query(email=email)] if results: if not results[0].active: return config.ALREADY_UNSUBSCRIBED else: s = results[0] if wait_for_confirmation: s.key = GenerateSecret() result = config.WAITING_FOR_CONFIRMATION else: s.active = False result = config.UNSUBSCRIBED soup.reindex([s]) if send_deactivation_mail: self.deactivation_mail(s) return result else: return config.NO_SUCH_SUBSCRIPTION
def test_subscribe_user(self): soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) user = self.portal.acl_users.getUserById("user1") user.setProperties(email="*****@*****.**", fullname="Joe User") login(self.portal, "user1") self.gfolder.subscription_require_tos = False request = self.request adapter = getMultiAdapter((self.gfolder, request), IGazetteSubscription) subscriberForm = getMultiAdapter((self.context, request), name=u"subscriber-form") subscriberForm.update() data, errors = subscriberForm.extractData() # email and fullname are prefilled self.assertEquals(len(errors), 0) # even if I provide different email, it is ignored. self.assertEquals(adapter.subscribe("*****@*****.**", "Dummy", "user1"), WAITING_FOR_CONFIRMATION) subscriber = soup.data.values()[0] self.assertEquals(subscriber.active, False) self.assertEquals(subscriber.email, "*****@*****.**") self.assertEquals(subscriber.username, "user1")
def test_autoissue(self): view = self.gfolder.restrictedTraverse('auto-issue') result = view.render(test_mode=True, html_only=True) self.assertEqual(result, u"""<!doctype html>\n<html>\n<head>\n<meta http-equiv="content-type" content="text/html; charset=utf-8" />\n</head>\n<body>\n<p>Hello world</p>\n</body>\n</html>\n""") # this was a test mode - no recent issue created/set self.failIf(self.gfolder.objectIds()) self.failIf(self.gfolder.most_recent_issue) result = view.render(test_mode=False, html_only=True) # no subscribers, no email self.failIf(len(self.mailhost.messages) != 0) self.failUnless(len(self.gfolder.objectIds()) == 1) self.failUnless(self.gfolder.objectIds()[0] == self.gfolder.most_recent_issue.getId()) # subscibe a subscriber request = self.request adapter = getMultiAdapter((self.gfolder, request), IGazetteSubscription) adapter.subscribe('*****@*****.**', '') result = view.render(test_mode=False, html_only=True) # subscription request only -- user is not active yet self.failUnless(len(self.mailhost.messages) == 1) # Activate subsriber soup = getSoup(self.gfolder, config.SUBSCRIBERS_SOUP_ID) s = soup.query(email='*****@*****.**').next() s.active = True s.key = u'' soup.reindex([s]) result = view.render(test_mode=False, html_only=True) # subscription request and issue emails self.failUnless(len(self.mailhost.messages) == 2) # but all issues are stored as sub objects self.failUnless(len(self.gfolder.objectIds()) == 3) # finally try to make a PDF result = view.render(test_mode=True, html_only=False) self.failUnless(result.startswith(r'%PDF-'), msg='Please check wkhtmltopdf is installed in your system.')
def test_unsubscribe_anon(self): logout() provideAdapter( adapts=(Interface, IBrowserRequest), provides=Interface, factory=SubscriberForm, name=u"subscriber-form" ) # subscribe user request = self.request adapter = getMultiAdapter((self.gfolder, request), IGazetteSubscription) request.form = {"form.widgets.email": u"*****@*****.**"} subscriberForm = getMultiAdapter((self.context, request), name=u"subscriber-form") subscriberForm.update() data, errors = subscriberForm.extractData() self.assertEquals(adapter.subscribe("*****@*****.**", ""), WAITING_FOR_CONFIRMATION) msg = _parseMessage(self.mailhost.messages[0]) self.assertEquals(msg["To"], "*****@*****.**") msgtext = msg.get_payload(decode=True) groups = re.search("\?key=([a-f0-9]+)", msgtext).groups() self.assertEquals(len(groups), 1) key = groups[0] # activate activationView = ActivationView(self.context, request) activationView.activate(key) self.assertEquals(adapter.unsubscribe("*****@*****.**"), WAITING_FOR_CONFIRMATION) msg = _parseMessage(self.mailhost.messages[1]) msgtext = msg.get_payload(decode=True) key = re.search("\?key=([a-f0-9]+)", msgtext).groups()[0] # deactivate activationView.deactivate(key) soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) subscriber = soup.data.values()[0] self.assertEquals(subscriber.active, False)
def render(self, test_mode=False, html_only=False): """ This method does all the hard work """ context = aq_inner(self.context) if not context.auto_enabled: return 'N/A' now = datetime.now() wtool = getToolByName(context, 'portal_workflow') soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) # strftime accepts any text, not only strftime characters subject = now.strftime(context.auto_subject.encode('utf-8')) url = context.absolute_url() + '/subscription?uuid=%(uuid)s' footer_text = context.footer.output.replace('${url}', '$url') footer_text = footer_text.replace('$url', url) count = 0 base_text = '' if context.auto_text: base_text += now.strftime(context.auto_text.output.encode('utf-8')) + '\n' providers = self._providers() gid = 'issue-%s' % now.strftime("%Y-%m-%d-%H-%M-%S.%f") idx = 0 while context.check_id(gid): # python script in skins idx += 1 gid = 'issue-%s-%d' % (now.strftime("%Y-%m-%d-%H-%M-%S.%f"), idx) # create anonymous issue text to be stored to portal text = safe_unicode(base_text) auto_text = u'' provider_names = [] for p in providers: auto_text += safe_unicode(p.get_gazette_text(context, None)) provider_names.append(repr(p)) if not auto_text: # There is no automatically geenrated text. Discard sending of newsletter. return 'Nothing to send' text = text + auto_text # Create PDF version of the newsletter using wkhtml2pdf as archive of the issue pdf_raw = self.make_pdf(text, html_only) if not pdf_raw: logger.warning('Unable to create PDF of automatically issued gazette.') if not test_mode: # create Gazette object representing this issue gid = context.invokeFactory('gazette.GazetteIssue', gid) gazette = context[gid] # Fill the newly create Gazette object with generated data gazette.title = subject gazette.text = RichTextValue(text, mimeType='text/html', outputMimeType='text/html') gazette.providers = provider_names gazette.sent_at = now try: # ignore if there is no publish option for now wtool.doActionFor(gazette, 'publish') except: pass # Attach PDF to gazette but only if it is not HTML only mode if pdf_raw and not html_only: fid = gazette.invokeFactory('File', gid + '.pdf') file_pdf = gazette[fid] file_pdf.setTitle(gazette.title) file_pdf.setFile(pdf_raw, mimetype='application/pdf') file_pdf.processForm() for s in soup.query(active=True): # returns email and fullname taken from memberdata if s.username is set and member exists subscriber_info = s.get_info(context) footer = footer_text % subscriber_info mail_text = "" if subscriber_info['salutation']: mail_text += "%s<br /><br />" % subscriber_info['salutation'] mail_text += "%s------------<br />%s" % (text, footer) try: if utils.send_mail(context, None, subscriber_info['email'], subscriber_info['fullname'], subject, mail_text): count += 1 except (SMTPException, SMTPRecipientsRefused): pass context.most_recent_issue = gazette else: if html_only: self.request.response.setHeader('Content-Type', 'text/html;charset=utf-8') else: self.request.response.setHeader('Content-Type', 'application/pdf') return pdf_raw return str(count)
def count(self, soup): soup = getSoup(aq_inner(self.context), soup) return len(soup.storage)
def count(self): """ Number of subscribers """ soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) return len([x for x in soup.lazy(active=True)])
def count(self): soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) result = dict() result['active'] = len([x for x in soup.lazy(active=True)]) result['inactive'] = len([x for x in soup.lazy(active=False)]) return result
def status(self, email): soup = getSoup(self.context, config.SUBSCRIBERS_SOUP_ID) results = [r for r in soup.query(email=email)] if results: return results[0].active return False