source: main/waeup.ikoba/trunk/src/waeup/ikoba/utils/utils.py @ 13810

Last change on this file since 13810 was 13802, checked in by Henrik Bettermann, 9 years ago

Do only allow one running export job.
Add switch to disable all exports.

See r13198 - r13201 and r13211.

  • Property svn:keywords set to Id
File size: 10.5 KB
Line 
1## $Id: utils.py 13802 2016-04-05 21:04:39Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""General helper utilities for Ikoba.
19"""
20import grok
21import psutil
22import string
23import pytz
24from random import SystemRandom as r
25from zope.i18n import translate
26from waeup.ikoba.interfaces import IIkobaUtils
27from waeup.ikoba.interfaces import MessageFactory as _
28from waeup.ikoba.smtp import send_mail as send_mail_internally
29from waeup.ikoba.utils.helpers import (
30    get_sorted_preferred, get_current_principal)
31from waeup.ikoba.payments.currencies import ISO_4217_CURRENCIES
32
33
34def send_mail(from_name, from_addr,
35              rcpt_name, rcpt_addr,
36              subject, body, config):
37    """Wrapper for the real SMTP functionality in :mod:`waeup.ikoba.smtp`.
38
39    Merely here to stay compatible with lots of calls to this place.
40    """
41    mail_id = send_mail_internally(
42        from_name, from_addr, rcpt_name, rcpt_addr,
43        subject, body, config)
44    return True
45
46
47#: A list of phone prefixes (order num, country, prefix).
48#: Items with same order num will be sorted alphabetically.
49#: The lower the order num, the higher the precedence.
50INT_PHONE_PREFIXES = [
51    (99, _('Germany'), '49'),
52    (1, _('Nigeria'), '234'),
53    (99, _('U.S.'), '1'),
54    ]
55
56
57def sorted_phone_prefixes(data=INT_PHONE_PREFIXES, request=None):
58    """Sorted tuples of phone prefixes.
59
60    Ordered as shown above and formatted for use in select boxes.
61
62    If request is given, we'll try to translate all country names in
63    order to sort alphabetically correctly.
64
65    XXX: This is a function (and not a constant) as different
66    languages might give different orders. This is not tested yet.
67
68    XXX: If we really want to use alphabetic ordering here, we might
69    think about caching results of translations.
70    """
71    if request is not None:
72        data = [
73            (x, translate(y, context=request), z)
74            for x, y, z in data]
75    return tuple([
76        ('%s (+%s)' % (x[1], x[2]), '+%s' % x[2])
77        for x in sorted(data)
78        ])
79
80
81class IkobaUtils(grok.GlobalUtility):
82    """A collection of parameters and methods subject to customization.
83
84    """
85    grok.implements(IIkobaUtils)
86    # This the only place where we define the portal language
87    # which is used for the translation of system messages
88    # (e.g. object histories).
89    PORTAL_LANGUAGE = 'en'
90
91    PREFERRED_LANGUAGES_DICT = {
92        'en': (1, u'English'),
93        'fr': (2, u'Français'),
94        'de': (3, u'Deutsch'),
95        'ha': (4, u'Hausa'),
96        'yo': (5, u'Yoruba'),
97        'ig': (6, u'Igbo'),
98        }
99
100    # CURRENCIES must be equal to or an excerpt of ISO_4217_CURRENCIES
101    CURRENCIES = ISO_4217_CURRENCIES
102
103    #: A function to return
104    @classmethod
105    def sorted_phone_prefixes(cls, data=INT_PHONE_PREFIXES, request=None):
106        return sorted_phone_prefixes(data, request)
107
108    CON_CATS_DICT = {
109        'sample': 'Sample Category',
110        'license': 'License',
111        'no': _('no contract'),
112        }
113
114    PAYMENT_CATEGORIES = {
115        'license': 'License Fee',
116        }
117
118    #: Set positive number for allowed max, negative for required min
119    #: avail.
120    #:
121    #: Use integer for bytes value, float for percent
122    #: value. `cpu-load`, of course, accepts float values only.
123    #: `swap-mem` = Swap Memory, `virt-mem` = Virtual Memory,
124    #: `cpu-load` = CPU load in percent.
125    SYSTEM_MAX_LOAD = {
126        'swap-mem': None,
127        'virt-mem': None,
128        'cpu-load': 100.0,
129        }
130
131    EXPORTER_NAMES = (
132        'pdfdocuments',
133        'htmldocuments',
134        'restdocuments',
135        'users',
136        'products',
137        'payments',
138        'customers',
139        'customersampledocuments',
140        'samplecontracts')
141
142    BATCH_PROCESSOR_NAMES = (
143        'customerprocessor',
144        'customersampledocumentprocessor',
145        'samplecontractprocessor',
146        'productprocessor',
147        'pdfdocumentprocessor',
148        'htmldocumentprocessor',
149        'restdocumentprocessor',
150        'userprocessor')
151
152    def sendContactForm(self, from_name, from_addr, rcpt_name, rcpt_addr,
153                        from_username, usertype, portal, body, subject):
154        """Send an email with data provided by forms.
155        """
156        config = grok.getSite()['configuration']
157        text = _(u"""Fullname: ${a}
158User Id: ${b}
159User Type: ${c}
160Portal: ${d}
161
162${e}
163""")
164        text = _(text, mapping={
165            'a': from_name,
166            'b': from_username,
167            'c': usertype,
168            'd': portal,
169            'e': body})
170        body = translate(text, 'waeup.ikoba',
171            target_language=self.PORTAL_LANGUAGE)
172        if not (from_addr and rcpt_addr):
173            return False
174        return send_mail(
175            from_name, from_addr, rcpt_name, rcpt_addr,
176            subject, body, config)
177
178    @property
179    def tzinfo(self):
180        # For Nigeria: pytz.timezone('Africa/Lagos')
181        # For Germany: pytz.timezone('Europe/Berlin')
182        return pytz.utc
183
184    def fullname(self, firstname, lastname, middlename=None):
185        """Full name constructor.
186        """
187        # We do not necessarily have the middlename attribute
188        if middlename:
189            name = '%s %s %s' % (firstname, middlename, lastname)
190        else:
191            name = '%s %s' % (firstname, lastname)
192        return string.capwords(
193            name.replace('-', ' - ')).replace(' - ', '-')
194
195    def genPassword(self, length=8, chars=string.letters + string.digits):
196        """Generate a random password.
197        """
198        return ''.join([r().choice(chars) for i in range(length)])
199
200    def sendCredentials(self, user, password=None, url_info=None, msg=None):
201        """Send credentials as email.
202
203        Input is the customer for which credentials are sent and the
204        password.
205
206        Returns True or False to indicate successful operation.
207        """
208        subject = 'Your Ikoba credentials'
209        text = _(u"""Dear ${a},
210
211${b}
212Registration and Application Portal of
213${c}.
214
215Your user name: ${d}
216Your password: ${e}
217${f}
218
219Please remember your user name and keep
220your password secret!
221
222Please also note that passwords are case-sensitive.
223
224Regards
225""")
226        config = grok.getSite()['configuration']
227        from_name = config.name_admin
228        from_addr = config.email_admin
229        rcpt_name = user.title
230        rcpt_addr = user.email
231        text = _(text, mapping={
232            'a': rcpt_name,
233            'b': msg,
234            'c': config.name,
235            'd': user.name,
236            'e': password,
237            'f': url_info})
238
239        body = translate(text, 'waeup.ikoba',
240            target_language=self.PORTAL_LANGUAGE)
241        return send_mail(
242            from_name, from_addr, rcpt_name, rcpt_addr,
243            subject, body, config)
244
245    def sendTransitionInfo(self, customer, obj, msg=None):
246        """Send transition information as email.
247
248        Input is the customer for which credentials are sent and the object
249        which was transitioned.
250
251        Returns True or False to indicate successful operation.
252        """
253        config = grok.getSite()['configuration']
254        if not config.email_notification:
255            return
256        subject = 'Ikoba status change information'
257        text = _(u"""Dear ${a},
258
259The status of the following object has been changed:
260
261Object Id: ${c}
262Title: ${d}
263Transition: ${e}
264New state: ${f}
265
266Regards,
267
268${b}
269
270--
271${g}
272""")
273        from_name = config.name_admin
274        from_addr = config.email_admin
275        rcpt_name = customer.title
276        rcpt_addr = customer.email
277
278        user = get_current_principal()
279        if user is None:
280            usertitle = 'system'
281        elif user.id == 'zope.anybody':
282            usertitle = 'Anonymous'
283        else:
284            usertitle = getattr(user, 'public_name', None)
285            if not usertitle:
286                usertitle = user.title
287
288        msg = translate(msg,'waeup.ikoba', target_language=self.PORTAL_LANGUAGE)
289        new_state = translate(obj.translated_state,'waeup.ikoba',
290            target_language=self.PORTAL_LANGUAGE)
291
292        text = _(text, mapping={
293            'a': rcpt_name,
294            'b': usertitle,
295            'c': obj.__name__,
296            'd': obj.title,
297            'e': msg.lower(),
298            'f': new_state,
299            'g': config.name})
300
301        body = translate(text, 'waeup.ikoba',
302            target_language=self.PORTAL_LANGUAGE)
303
304        return send_mail(
305            from_name, from_addr, rcpt_name, rcpt_addr,
306            subject, body, config)
307
308    def expensive_actions_allowed(self, type=None, request=None):
309        """Tell, whether expensive actions are currently allowed.
310
311        Check system load/health (or other external circumstances) and
312        locally set values to see, whether expensive actions should be
313        allowed (`True`) or better avoided (`False`).
314
315        Use this to allow or forbid exports, report generations, or
316        similar actions.
317        """
318        max_values = self.SYSTEM_MAX_LOAD
319        for (key, func) in (
320            ('swap-mem', psutil.swap_memory),
321            ('virt-mem', psutil.virtual_memory),
322            ):
323            max_val = max_values.get(key, None)
324            if max_val is None:
325                continue
326            mem_val = func()
327            if isinstance(max_val, float):
328                # percents
329                if max_val < 0.0:
330                    max_val = 100.0 + max_val
331                if mem_val.percent > max_val:
332                    return False
333            else:
334                # number of bytes
335                if max_val < 0:
336                    max_val = mem_val.total + max_val
337                if mem_val.used > max_val:
338                    return False
339        return True
340
341    def export_disabled_message(self):
342        export_disabled_message = grok.getSite()[
343            'configuration'].export_disabled_message
344        if export_disabled_message:
345            return export_disabled_message
346        return None
Note: See TracBrowser for help on using the repository browser.