0001-remove-obsolete-abelium-domino-modules-17244.patch
auquotidien/auquotidien.py | ||
---|---|---|
13 | 13 |
from modules import root |
14 | 14 |
from modules import payments |
15 | 15 |
from modules import connectors |
16 |
from modules import abelium_domino_ui |
|
17 |
from modules import abelium_domino_vars |
|
18 |
from modules import abelium_domino_synchro |
|
19 | 16 | |
20 | 17 |
get_publisher_class().register_translation_domain('auquotidien') |
21 | 18 |
get_publisher_class().default_configuration_path = 'au-quotidien-wcs-settings.xml' |
... | ... | |
32 | 29 |
import wcs.admin.forms |
33 | 30 |
wcs.admin.forms.FormsDirectory.categories = categories_admin.CategoriesDirectory() |
34 | 31 | |
35 |
import wcs.admin.settings |
|
36 |
wcs.admin.settings.SettingsDirectory.domino = abelium_domino_ui.AbeliumDominoDirectory() |
|
37 |
wcs.admin.settings.SettingsDirectory._q_exports.append('domino') |
|
38 | ||
39 | 32 |
import wcs.categories |
40 | 33 |
wcs.categories.Category.XML_NODES = [('name', 'str'), ('url_name', 'str'), |
41 | 34 |
('description', 'str'), ('position', 'int'), |
auquotidien/modules/abelium_domino_synchro.py | ||
---|---|---|
1 |
import sys |
|
2 |
from datetime import datetime |
|
3 |
import collections |
|
4 |
from decimal import Decimal |
|
5 | ||
6 |
from wcs.qommon import _ |
|
7 |
from wcs.qommon.cron import CronJob |
|
8 |
from wcs.qommon.publisher import get_publisher_class |
|
9 |
from wcs.qommon import get_logger |
|
10 | ||
11 |
from wcs.users import User |
|
12 | ||
13 |
from .abelium_domino_ui import (get_client, is_activated, get_invoice_regie, |
|
14 |
abelium_domino_ws) |
|
15 |
from .payments import Invoice, Transaction |
|
16 | ||
17 |
DOMINO_ID_PREFIX = 'DOMINO-' |
|
18 | ||
19 |
def synchronize_domino(publisher): |
|
20 |
regie = get_invoice_regie(publisher) |
|
21 |
logger = get_logger() |
|
22 |
if not is_activated(publisher) or not regie: |
|
23 |
return |
|
24 |
client = get_client(publisher) |
|
25 |
if client is None: |
|
26 |
logger.warning('Unable to create a DominoWS object') |
|
27 |
return |
|
28 |
client.clear_cache() |
|
29 |
users = User.values() |
|
30 |
users_by_mail = dict(((user.email, user) for user in users)) |
|
31 |
users_by_code_interne = {} |
|
32 |
for user in users: |
|
33 |
if hasattr(user, 'abelium_domino_code_famille'): |
|
34 |
users_by_code_interne[user.abelium_domino_code_famille] = user |
|
35 |
try: |
|
36 |
invoices = client.invoices |
|
37 |
except abelium_domino_ws.DominoException as e: |
|
38 |
publisher.notify_of_exception(sys.exc_info(), context='[DOMINO]') |
|
39 |
logger.error('domino cron: failure to retrieve invoice list from domino ' |
|
40 |
'for synchronization [error:%s]', e) |
|
41 |
return |
|
42 |
# import new invoices |
|
43 |
logger.info('domino cron: retrieved %i invoices', len(invoices)) |
|
44 |
for invoice_id, invoice in invoices.items(): |
|
45 |
user = None |
|
46 |
if invoice.family.code_interne in users_by_code_interne: |
|
47 |
user = users_by_code_interne[invoice.family.code_interne] |
|
48 |
if user is None: |
|
49 |
for email in (invoice.family.email_pere, invoice.family.email_mere, |
|
50 |
invoice.family.adresse_internet): |
|
51 |
user = users_by_mail.get(email) |
|
52 |
if user: |
|
53 |
break |
|
54 |
else: |
|
55 |
continue |
|
56 |
external_id = '%s%s' % (DOMINO_ID_PREFIX, invoice.id) |
|
57 |
payment_invoice = Invoice.get_on_index(external_id, 'external_id', ignore_errors=True) |
|
58 |
if payment_invoice: |
|
59 |
continue |
|
60 |
if invoice.reste_du == Decimal(0) or invoice.reste_du < Decimal(0): |
|
61 |
continue |
|
62 |
payment_invoice = Invoice() |
|
63 |
payment_invoice.user_id = user.id |
|
64 |
payment_invoice.external_id = external_id |
|
65 |
payment_invoice.regie_id = regie.id |
|
66 |
payment_invoice.formdef_id = None |
|
67 |
payment_invoice.formdata_id = None |
|
68 |
payment_invoice.amount = invoice.reste_du |
|
69 |
payment_invoice.date = invoice.creation |
|
70 |
payment_invoice.domino_synchro_date = datetime.now() |
|
71 |
if 'etablissement' in invoice._detail: |
|
72 |
etablissement = invoice._detail['etablissement'].encode('utf-8') |
|
73 |
payment_invoice.subject = _('%s - Childcare services') % etablissement |
|
74 |
else: |
|
75 |
payment_invoice.subject = _('Childcare services') |
|
76 |
if invoice._detail.get('lignes'): |
|
77 |
details = [] |
|
78 |
details.append('<table class="invoice-details"><thead>') |
|
79 |
tpl = '''<tr> |
|
80 |
<td>%(designation)s</td> |
|
81 |
<td>%(quantite)s</td> |
|
82 |
<td>%(prix)s</td> |
|
83 |
<td>%(montant)s</td> |
|
84 |
</tr>''' |
|
85 |
captions = { |
|
86 |
'designation': _('Caption'), |
|
87 |
'quantite': _('Quantity'), |
|
88 |
'prix': _('Price'), |
|
89 |
'amount': _('Amount') |
|
90 |
} |
|
91 |
details.append(tpl % captions) |
|
92 |
details.append('</thead>') |
|
93 |
details.append('<tbody>') |
|
94 |
for ligne in invoice._detail['lignes']: |
|
95 |
def encode(x): |
|
96 |
a, b = x |
|
97 |
b = b.encode('utf-8') |
|
98 |
return (a,b) |
|
99 |
ligne = map(encode, ligne) |
|
100 |
ligne = dict(ligne) |
|
101 |
base = collections.defaultdict(lambda:'') |
|
102 |
base.update(ligne) |
|
103 |
details.append(tpl % base) |
|
104 |
details.append('</tbody></table>') |
|
105 |
payment_invoice.details = '\n'.join(details) |
|
106 |
payment_invoice.store() |
|
107 |
logger.info('domino cron: remote invoice %s for family %s added to user %s invoices with id %s', |
|
108 |
invoice.id, invoice.family.id, user.id, payment_invoice.id) |
|
109 | ||
110 |
# update invoices |
|
111 |
invoices_ids = dict(invoices.items()) |
|
112 |
for payment_invoice in Invoice.values(): |
|
113 |
if payment_invoice.external_id is None or not payment_invoice.external_id.startswith(DOMINO_ID_PREFIX): |
|
114 |
continue # not a payment related to domino we skip |
|
115 |
i = payment_invoice.external_id[len(DOMINO_ID_PREFIX):] |
|
116 |
i = int(i) |
|
117 |
invoice = invoices_ids.get(i) |
|
118 |
if payment_invoice.paid: |
|
119 |
if not invoice: |
|
120 |
# invoice has been paid (locally or not) but remote invoice has |
|
121 |
# been deleted do, we do nothing. |
|
122 |
continue |
|
123 |
if getattr(payment_invoice, 'domino_knows_its_paid', None) or getattr(payment_invoice, 'paid_by_domino', None): |
|
124 |
# synchronization of payment already done, skip |
|
125 |
continue |
|
126 |
transactions = list(Transaction.get_with_indexed_value('invoice_ids', |
|
127 |
payment_invoice.id)) |
|
128 |
if not transactions: |
|
129 |
logger.warning("domino cron: invoice %s is marked paid but does " |
|
130 |
"not have any linked transaction.", payment_invoice.id) |
|
131 |
details = '' # no details about the payment, problem |
|
132 |
else: |
|
133 |
details = repr(transactions[0].__dict__) |
|
134 |
if invoice.montant != payment_invoice.amount: |
|
135 |
pass # add warning logs |
|
136 |
try: |
|
137 |
client.pay_invoice([invoice], invoice.montant, details, |
|
138 |
payment_invoice.paid_date) |
|
139 |
except abelium_domino_ws.DominoException as e: |
|
140 |
logger.error('domino cron: invoice %s has been paid, but the remote system ' |
|
141 |
'is unreachable, notification will be done again ' |
|
142 |
'later [error: %s]', invoice.id, e) |
|
143 |
else: |
|
144 |
# memorize the date of synchronization |
|
145 |
payment_invoice.domino_knows_its_paid = datetime.now() |
|
146 |
payment_invoice.store() |
|
147 |
logger.info('domino cron: domino: invoice %s has been paid; remote system has been ' |
|
148 |
'notified', payment_invoice.id) |
|
149 |
else: # unpaid |
|
150 |
if not invoice: |
|
151 |
logger.info('domino cron: remote invoice %s disapearred, so its ' |
|
152 |
'still-unpaid local counterpart invoice %s was deleted.', |
|
153 |
i, payment_invoice.id) |
|
154 |
payment_invoice.remove_self() |
|
155 |
elif invoice.paid(): |
|
156 |
payment_invoice.paid_by_domino = True |
|
157 |
payment_invoice.pay() |
|
158 |
logger.info('domino cron: remote invoice %s has beend paid, ' |
|
159 |
'local invoice %s of user %s is now marked as paid.', |
|
160 |
invoice.id, payment_invoice.id, payment_invoice.user_id) |
|
161 |
else: # not invoice.paid() |
|
162 |
pass # still waiting for the payment |
|
163 | ||
164 |
get_publisher_class().register_cronjob(CronJob(function=synchronize_domino, |
|
165 |
hours=range(0, 24), minutes=range(0, 60, 30))) |
auquotidien/modules/abelium_domino_ui.py | ||
---|---|---|
1 |
from quixote import get_publisher, redirect, get_request |
|
2 |
from quixote.directory import Directory, AccessControlled |
|
3 |
from quixote.html import TemplateIO, htmltext |
|
4 | ||
5 |
from wcs.qommon import _, N_ |
|
6 |
from wcs.qommon import get_cfg, get_logger |
|
7 |
from wcs.qommon.form import Form, StringWidget, CheckboxWidget, SingleSelectWidget |
|
8 |
from wcs.qommon.backoffice.menu import html_top |
|
9 |
from quixote.html import htmltext |
|
10 | ||
11 |
from .payments import Regie |
|
12 | ||
13 | ||
14 |
# constants |
|
15 |
ABELIUM_DOMINO = 'abelium_domino' |
|
16 |
ACTIVATED = 'activated' |
|
17 |
WSDL_URL = 'wsdl_url' |
|
18 |
SERVICE_URL = 'service_url' |
|
19 |
DOMAIN = 'domain' |
|
20 |
LOGIN = 'login' |
|
21 |
PASSWORD = 'password' |
|
22 |
INVOICE_REGIE = 'invoice_regie' |
|
23 | ||
24 |
try: |
|
25 |
import abelium_domino_ws |
|
26 |
except ImportError as e: |
|
27 |
abelium_domino_ws = None |
|
28 |
import_error = e |
|
29 | ||
30 |
def get_abelium_cfg(publisher): |
|
31 |
if not publisher: |
|
32 |
publisher = get_publisher() |
|
33 |
return publisher.cfg.get(ABELIUM_DOMINO, {}) |
|
34 | ||
35 |
def is_activated(publisher=None): |
|
36 |
cfg = get_abelium_cfg(publisher) |
|
37 |
return cfg.get(ACTIVATED, False) and abelium_domino_ws is not None |
|
38 | ||
39 |
def get_client(publisher=None): |
|
40 |
publisher = publisher or get_publisher() |
|
41 | ||
42 |
cfg = get_abelium_cfg(publisher) |
|
43 |
try: |
|
44 |
publisher._ws_cache = abelium_domino_ws.DominoWs( |
|
45 |
url=cfg.get(WSDL_URL, ''), |
|
46 |
domain=cfg.get(DOMAIN,''), |
|
47 |
login=cfg.get(LOGIN, ''), |
|
48 |
password=cfg.get(PASSWORD, ''), |
|
49 |
location=cfg.get(SERVICE_URL), |
|
50 |
logger=get_logger()) |
|
51 |
except IOError: |
|
52 |
return None |
|
53 |
return publisher._ws_cache |
|
54 | ||
55 |
def get_family(user, publisher=None): |
|
56 |
family = None |
|
57 |
if user is None: |
|
58 |
return None |
|
59 |
client = get_client(publisher) |
|
60 |
if not client: |
|
61 |
return None |
|
62 |
if hasattr(user, 'abelium_domino_code_famille'): |
|
63 |
family = client.get_family_by_code_interne( |
|
64 |
user.abelium_domino_code_famille) |
|
65 |
if family is None and user.email: |
|
66 |
family = client.get_family_by_mail(user.email) |
|
67 |
return family |
|
68 | ||
69 |
def get_invoice_regie(publisher=None): |
|
70 |
cfg = get_abelium_cfg(publisher) |
|
71 |
regie_id = cfg.get(INVOICE_REGIE) |
|
72 |
if not regie_id: |
|
73 |
return None |
|
74 |
return Regie.get(regie_id, ignore_errors=True) |
|
75 | ||
76 |
class AbeliumDominoDirectory(Directory): |
|
77 |
_q_exports = [ '' , 'debug' ] |
|
78 |
label = N_('Domino') |
|
79 | ||
80 |
def debug(self): |
|
81 |
from abelium_domino_vars import SESSION_CACHE |
|
82 |
html_top(ABELIUM_DOMINO) |
|
83 |
r = TemplateIO(html=True) |
|
84 |
r += htmltext('<form method="post"><button>Lancer le cron</button></form>') |
|
85 |
if get_request().get_method() == 'POST': |
|
86 |
try: |
|
87 |
from abelium_domino_synchro import synchronize_domino |
|
88 |
synchronize_domino(get_publisher()) |
|
89 |
except Exception as e: |
|
90 |
r += htmltext('<pre>%s</pre>') % repr(e) |
|
91 |
r += htmltext('<p>code interne: %s</p>') % getattr(get_request().user, str('abelium_domino_code_famille'), None) |
|
92 |
r += htmltext('<dl>') |
|
93 |
context = get_publisher().substitutions.get_context_variables() |
|
94 |
for var in sorted(context.keys()): |
|
95 |
value = context[var] |
|
96 |
if value: |
|
97 |
r += htmltext('<dt>%s</dt>') % var |
|
98 |
r += htmltext('<dd>%s</dt>') % value |
|
99 |
r += htmltext('</dl>') |
|
100 |
delattr(get_request().session, SESSION_CACHE) |
|
101 | ||
102 |
def _q_index(self): |
|
103 |
publisher = get_publisher() |
|
104 |
cfg = get_cfg(ABELIUM_DOMINO, {}) |
|
105 |
form = self.form(cfg) |
|
106 | ||
107 |
title = _('Abelium Domino') |
|
108 |
html_top(ABELIUM_DOMINO, title = title) |
|
109 |
r = TemplateIO(html=True) |
|
110 |
r += htmltext('<h2>%s</h2>') % title |
|
111 | ||
112 |
if form.is_submitted() and not form.has_errors(): |
|
113 |
if form.get_widget('cancel').parse(): |
|
114 |
return redirect('..') |
|
115 |
if form.get_widget('submit').parse(): |
|
116 |
for name in [f[0] for f in self.form_desc] + [INVOICE_REGIE]: |
|
117 |
widget = form.get_widget(name) |
|
118 |
if widget: |
|
119 |
cfg[name] = widget.parse() |
|
120 |
publisher.cfg[ABELIUM_DOMINO] = cfg |
|
121 |
publisher.write_cfg() |
|
122 |
return redirect('.') |
|
123 | ||
124 |
if abelium_domino_ws: |
|
125 |
r += form.render() |
|
126 |
else: |
|
127 |
message = _('The Abelium Domino module is not ' |
|
128 |
'activated because of this error when ' |
|
129 |
'loading it: %r') % import_error |
|
130 |
r += htmltext('<p class="errornotice">%s</p>') % message |
|
131 |
r += htmltext('<dl style="display: none">') |
|
132 |
context = get_publisher().substitutions.get_context_variables() |
|
133 |
for var in sorted(context.keys()): |
|
134 |
value = context[var] |
|
135 |
if value: |
|
136 |
r += htmltext('<dt>%s</dt>') % var |
|
137 |
r += htmltext('<dd>%s</dt>') % value |
|
138 |
r += htmltext('</dl>') |
|
139 |
return r.getvalue() |
|
140 | ||
141 |
form_desc = ( |
|
142 |
# name, required, title, kind |
|
143 |
(ACTIVATED, False, _('Activated'), bool), |
|
144 |
(WSDL_URL, True, _('WSDL URL'), str), |
|
145 |
(SERVICE_URL, False, _('Service URL'), str), |
|
146 |
(DOMAIN, True, _('Domain'), str), |
|
147 |
(LOGIN, True, _('Login'), str), |
|
148 |
(PASSWORD, True, _('Password'), str), |
|
149 |
) |
|
150 | ||
151 | ||
152 |
def form(self, initial_value={}): |
|
153 |
form = Form(enctype='multipart/form-data') |
|
154 |
kinds = { str: StringWidget, bool: CheckboxWidget } |
|
155 |
for name, required, title, kind in self.form_desc: |
|
156 |
widget = kinds[kind] |
|
157 |
form.add(widget, name, required=required, title=title, |
|
158 |
value=initial_value.get(name, '')) |
|
159 |
options = [(regie.id, regie.label) \ |
|
160 |
for regie in Regie.values()] |
|
161 |
options.insert(0, (None, _('None'))) |
|
162 |
form.add(SingleSelectWidget, INVOICE_REGIE, |
|
163 |
title=_('Regie which will receive payments'), |
|
164 |
value=initial_value.get(INVOICE_REGIE), |
|
165 |
options=options) |
|
166 | ||
167 |
form.add_submit('submit', _('Submit')) |
|
168 |
form.add_submit('cancel', _('Cancel')) |
|
169 | ||
170 |
return form |
auquotidien/modules/abelium_domino_vars.py | ||
---|---|---|
1 |
from decimal import Decimal |
|
2 |
import logging |
|
3 | ||
4 |
from quixote.publish import get_publisher |
|
5 | ||
6 |
from wcs.qommon import _ |
|
7 |
from wcs.qommon.substitution import Substitutions |
|
8 |
from wcs.publisher import WcsPublisher |
|
9 | ||
10 |
from .abelium_domino_ui import (is_activated, abelium_domino_ws, get_client, get_family) |
|
11 | ||
12 |
SESSION_CACHE = 'abelium_domino_variable_cache' |
|
13 | ||
14 |
class DominoVariables(object): |
|
15 |
VARIABLE_TEMPLATE = 'domino_var_%s' |
|
16 |
CHILD_VARIABLE_TEMPLATE = 'domino_var_%s_enfant%s' |
|
17 | ||
18 |
@classmethod |
|
19 |
def CHILD_COLUMNS(cls): |
|
20 |
return abelium_domino_ws.Child.COLUMNS |
|
21 | ||
22 |
@classmethod |
|
23 |
def FAMILY_COLUMNS(cls): |
|
24 |
return abelium_domino_ws.Family.COLUMNS + abelium_domino_ws.Family.MORE_COLUMNS |
|
25 | ||
26 |
def __init__(self, publisher=None, request=None): |
|
27 |
self.publisher = publisher |
|
28 |
self.request = request |
|
29 | ||
30 |
def get_substitution_variables(self): |
|
31 |
vars = {} |
|
32 |
if not is_activated() or not self.request or not self.request.user \ |
|
33 |
or not getattr(self.request.user, 'email'): |
|
34 |
return vars |
|
35 | ||
36 |
# test cache |
|
37 |
cache = getattr(self.request.session, SESSION_CACHE, None) |
|
38 |
if cache is not None: |
|
39 |
return cache |
|
40 |
# call the web service |
|
41 |
try: |
|
42 |
charset = get_publisher().site_charset |
|
43 |
family = get_family(self.request.user) |
|
44 |
if family: |
|
45 |
family.complete() |
|
46 |
for i, child in enumerate(family.children): |
|
47 |
for remote_name, name, converter, desc in self.CHILD_COLUMNS(): |
|
48 |
v = getattr(child, name, None) |
|
49 |
if v is None: |
|
50 |
continue |
|
51 |
if hasattr(v, 'encode'): |
|
52 |
v = v.encode(charset) |
|
53 |
vars[self.CHILD_VARIABLE_TEMPLATE % (name, i+1)] = v |
|
54 |
vars[self.VARIABLE_TEMPLATE % 'nombre_enfants'] = len(family.children) |
|
55 |
for remote_name, name, converted, desc in self.FAMILY_COLUMNS(): |
|
56 |
if hasattr(family, name): |
|
57 |
v = getattr(family, name) |
|
58 |
if v is None: |
|
59 |
continue |
|
60 |
if hasattr(v, 'encode'): |
|
61 |
v = v.encode(charset) |
|
62 |
vars[self.VARIABLE_TEMPLATE % name] = v |
|
63 |
amount = Decimal(0) |
|
64 |
for invoice in family.invoices: |
|
65 |
amount += invoice.reste_du |
|
66 |
if amount: |
|
67 |
vars['user_famille_reste_du'] = str(amount) |
|
68 |
except abelium_domino_ws.DominoException: |
|
69 |
logging.exception('unable to call the domino ws for user %s', self.request.user.id) |
|
70 |
setattr(self.request.session, SESSION_CACHE, vars) |
|
71 |
self.request.session.store() |
|
72 |
return vars |
|
73 | ||
74 |
def get_substitution_variables_list(cls): |
|
75 |
if not is_activated(): |
|
76 |
return () |
|
77 |
vars = [] |
|
78 |
for remote_name, name, converted, desc in cls.FAMILY_COLUMNS(): |
|
79 |
vars.append((_('Domino'), cls.VARIABLE_TEMPLATE % name, desc)) |
|
80 |
for remote_name, name, converted, desc in cls.CHILD_COLUMNS(): |
|
81 |
vars.append((_('Domino'), cls.CHILD_VARIABLE_TEMPLATE % (name, '{0,1,2,..}'), desc)) |
|
82 |
return vars |
|
83 |
get_substitution_variables_list = classmethod(get_substitution_variables_list) |
|
84 | ||
85 |
Substitutions.register_dynamic_source(DominoVariables) |
|
86 |
WcsPublisher.register_extra_source(DominoVariables) |
auquotidien/modules/abelium_domino_workflow.py | ||
---|---|---|
1 |
import re |
|
2 |
import time |
|
3 | ||
4 |
from quixote import get_request, get_publisher, get_session |
|
5 |
from quixote.directory import Directory |
|
6 | ||
7 |
from wcs.qommon import _, N_ |
|
8 |
from wcs.qommon.substitution import Substitutions |
|
9 |
from wcs.qommon.form import Form, ValidatedStringWidget |
|
10 |
from wcs.qommon import get_logger |
|
11 | ||
12 |
from wcs.workflows import Workflow, WorkflowStatusJumpItem, register_item_class |
|
13 |
from wcs.forms.common import FormStatusPage |
|
14 | ||
15 |
from .abelium_domino_ui import (is_activated, abelium_domino_ws, get_client, get_family) |
|
16 |
from . import abelium_domino_ws |
|
17 | ||
18 |
class InternalCodeStringWidget(ValidatedStringWidget): |
|
19 |
regex = '\d*' |
|
20 | ||
21 |
class AbeliumDominoRegisterFamilyWorkflowStatusItem(WorkflowStatusJumpItem): |
|
22 |
status = None |
|
23 |
description = N_('Abelium Domino: Register a Family') |
|
24 |
key = 'abelium-domino-register-family' |
|
25 |
category = 'interaction' |
|
26 |
label = None |
|
27 | ||
28 |
def render_as_line(self): |
|
29 |
return _('Register a Family into Abelium Domino') |
|
30 | ||
31 |
def get_family(self, formdata): |
|
32 |
try: |
|
33 |
user = formdata.get_user() |
|
34 |
if user: |
|
35 |
family = get_family(user) |
|
36 |
if family: |
|
37 |
family.complete() |
|
38 |
return family |
|
39 |
except abelium_domino_ws.DominoException: |
|
40 |
pass |
|
41 |
return None |
|
42 | ||
43 |
def fill_form(self, form, formdata, user): |
|
44 |
family = self.get_family(formdata) |
|
45 |
if 'family_id' not in form._names: |
|
46 |
form.add(InternalCodeStringWidget, 'family_id', |
|
47 |
title=_('Family internal code'), |
|
48 |
value=family and family.code_interne.encode('utf8'), |
|
49 |
hint=_('If a family internal code is present, the ' |
|
50 |
'family is updated, if not it is created')) |
|
51 |
form.add_submit('create_update_button%s' % self.id, |
|
52 |
_('Create or update the family')) |
|
53 | ||
54 |
def update(self, form, formdata, user, evo): |
|
55 |
fid_widget = form.get_widget('family_id') |
|
56 |
code_interne = fid_widget.parse() |
|
57 |
try: |
|
58 |
code_interne = int(code_interne) |
|
59 |
except ValueError: |
|
60 |
raise ValueError('Le code interne est invalide') |
|
61 |
code_interne = '%05d' % code_interne |
|
62 |
family = get_client().get_family_by_code_interne(code_interne) |
|
63 |
if not family: |
|
64 |
raise ValueError('Le code interne est invalide') |
|
65 |
family.complete() |
|
66 |
self.extract_family(form, formdata, user, evo, family) |
|
67 |
family.save() |
|
68 |
return family |
|
69 | ||
70 |
def create(self, form, formdata, user, evo): |
|
71 |
family = abelium_domino_ws.Family(client=get_client()) |
|
72 |
self.extract_family(form, formdata, user, evo, family) |
|
73 |
return family |
|
74 | ||
75 |
def extract_family(self, form, formdata, user, evo, family): |
|
76 |
formdef = formdata.formdef |
|
77 |
children = [abelium_domino_ws.Child() for i in range(5)] |
|
78 |
max_i = 0 |
|
79 |
for field in formdef.fields: |
|
80 |
value = formdata.data.get(field.id) |
|
81 |
if value in (None, ''): |
|
82 |
continue |
|
83 |
if hasattr(field, 'date_in_the_past'): |
|
84 |
value = time.strftime('%Y%m%d', value) |
|
85 |
value = unicode(value, 'utf8') |
|
86 |
if field.prefill and \ |
|
87 |
field.prefill.get('type') == 'formula': |
|
88 |
v = field.prefill.get('value', '').strip() |
|
89 |
i = None |
|
90 |
name = None |
|
91 |
m = re.search('domino_var_([^ ]*)_enfant([0-9]*)', v) |
|
92 |
m2 = re.search('domino_var_([^ ]*)', v) |
|
93 |
if m: |
|
94 |
name, i = m.groups() |
|
95 |
try: |
|
96 |
i = int(i) |
|
97 |
except ValueError: |
|
98 |
continue |
|
99 |
max_i = max(i, max_i) |
|
100 |
setattr(children[i-1], name, value) |
|
101 |
elif m2: |
|
102 |
name = m2.group(1) |
|
103 |
setattr(family, name, value) |
|
104 |
for child1, child2 in zip(family.children, children[:max_i]): |
|
105 |
child1.__dict__.update(child2.__dict__) |
|
106 |
family.save() |
|
107 |
if max_i > len(family.children): # add new children |
|
108 |
for child in children[len(family.children):max_i]: |
|
109 |
family.add_child(child) |
|
110 | ||
111 |
def submit_form(self, form, formdata, user, evo): |
|
112 |
logger = get_logger() |
|
113 |
if form.get_submit() != 'create_update_button%s' % self.id: |
|
114 |
return |
|
115 |
try: |
|
116 |
if form.get_widget('family_id').parse(): |
|
117 |
family = self.update(form, formdata, user, evo) |
|
118 |
msg = _('Sucessfully updated the family %s') |
|
119 |
log_msg = _('Sucessfully updated the family %(code)s of %(user)s') |
|
120 |
else: |
|
121 |
family = self.create(form, formdata, user, evo) |
|
122 |
msg = _('Sucessfully created the family %s') |
|
123 |
log_msg = _('Sucessfully created the family %(code)s of %(user)s') |
|
124 |
code_interne = family.code_interne.encode('utf8') |
|
125 |
msg = msg % code_interne |
|
126 |
logger.info(log_msg, {'code': code_interne, 'user': formdata.get_user()}) |
|
127 |
form_user = formdata.get_user() |
|
128 |
form_user.abelium_domino_code_famille = code_interne |
|
129 |
form_user.store() |
|
130 |
except Exception as e: |
|
131 |
if form.get_widget('family_id').parse(): |
|
132 |
msg = _('Unable to update family: %s') % str(e) |
|
133 |
else: |
|
134 |
msg = _('Unable to create family: %s') % str(e) |
|
135 |
evo.comment = msg |
|
136 |
logger.exception(msg % formdata.get_user()) |
|
137 |
else: |
|
138 |
evo.comment = msg |
|
139 |
wf_status = self.get_target_status() |
|
140 |
if wf_status: |
|
141 |
evo.status = 'wf-%s' % wf_status[0].id |
|
142 |
return False |
|
143 | ||
144 |
def is_available(self, workflow=None): |
|
145 |
return get_publisher().has_site_option('domino') |
|
146 |
is_available = classmethod(is_available) |
|
147 | ||
148 |
register_item_class(AbeliumDominoRegisterFamilyWorkflowStatusItem) |
auquotidien/modules/abelium_domino_ws.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from decimal import Decimal |
|
3 |
import time |
|
4 |
import datetime |
|
5 |
from xml.etree import ElementTree as etree |
|
6 |
import logging |
|
7 | ||
8 |
try: |
|
9 |
from suds.client import Client |
|
10 |
from suds.bindings.binding import Binding |
|
11 |
# Webdev is bugged and using an HTML generator to produce XML content, |
|
12 |
Binding.replyfilter = lambda self, x: x.replace(' ', ' ') |
|
13 |
except ImportError: |
|
14 |
Client = None |
|
15 |
Binding = None |
|
16 | ||
17 |
logger = logging.getLogger(__name__) |
|
18 | ||
19 |
# cleaning and parsing functions |
|
20 | ||
21 |
LINE_SEPARATOR = '\n' |
|
22 |
COLUMN_SEPARATOR = '\t' |
|
23 | ||
24 |
def unicode_and_strip(x): |
|
25 |
return unicode(x).strip() |
|
26 | ||
27 |
def strip_and_int(x): |
|
28 |
try: |
|
29 |
return int(x.strip()) |
|
30 |
except ValueError: |
|
31 |
return None |
|
32 | ||
33 |
def strip_and_date(x): |
|
34 |
try: |
|
35 |
return datetime.datetime.strptime(x.strip(), '%Y%m%d').date() |
|
36 |
except ValueError: |
|
37 |
return None |
|
38 | ||
39 |
def parse_date(date_string): |
|
40 |
if date_string: |
|
41 |
return datetime.datetime.strptime(date_string, "%Y%m%d") |
|
42 |
else: |
|
43 |
None |
|
44 | ||
45 |
class DominoException(Exception): |
|
46 |
pass |
|
47 | ||
48 |
def object_cached(function): |
|
49 |
'''Decorate an object method so that its results is cached on the object |
|
50 |
instance after the first call. |
|
51 |
''' |
|
52 |
def decorated_function(self, *args, **kwargs): |
|
53 |
cache_name = '__%s_cache' % function.__name__ |
|
54 |
if not hasattr(self, cache_name): |
|
55 |
setattr(self, cache_name, (time.time(), {})) |
|
56 |
t, d = getattr(self, cache_name) |
|
57 |
if time.time() - t > 80: |
|
58 |
setattr(self, cache_name, (time.time(), {})) |
|
59 |
t, d = getattr(self, cache_name) |
|
60 |
k = tuple(*args) + tuple(sorted(kwargs.items())) |
|
61 |
if not k in d: |
|
62 |
d[k] = function(self, *args, **kwargs) |
|
63 |
return d[k] |
|
64 |
return decorated_function |
|
65 | ||
66 |
# Data model |
|
67 |
class SimpleObject(object): |
|
68 |
'''Base class for object returned by the web service''' |
|
69 | ||
70 |
'''Describe basic columns''' |
|
71 |
COLUMNS = () |
|
72 |
'''Describe extended object columns''' |
|
73 |
MORE_COLUMNS = () |
|
74 | ||
75 |
def __init__(self, **kwargs): |
|
76 |
self.__dict__.update(kwargs) |
|
77 | ||
78 |
def __repr__(self): |
|
79 |
c = {} |
|
80 |
for remote_name, name, converter, desc in self.COLUMNS: |
|
81 |
if hasattr(self, name): |
|
82 |
c[name] = getattr(self, name) |
|
83 |
return str(c) |
|
84 | ||
85 |
def serialize(self): |
|
86 |
l = [] |
|
87 |
for remote_name, local_name, converter, desc in self.COLUMNS + self.MORE_COLUMNS: |
|
88 |
if local_name == 'id': |
|
89 |
continue |
|
90 |
v = getattr(self, local_name, None) |
|
91 |
if v is None: |
|
92 |
continue |
|
93 |
if isinstance(v, (datetime.date, datetime.datetime)): |
|
94 |
v = v.strftime('%Y%m%d') |
|
95 |
if remote_name.endswith('_DA') and '-' in v: |
|
96 |
v = v.replace('-', '') |
|
97 |
l.append(u'{0}: "{1}"'.format(remote_name, v)) |
|
98 |
return u','.join(l) |
|
99 | ||
100 |
def debug(self): |
|
101 |
'''Output a debugging view of this object''' |
|
102 |
res = '' |
|
103 |
for remote_name, name, converter, desc in self.MORE_COLUMNS or self.COLUMNS: |
|
104 |
if hasattr(self, name): |
|
105 |
res += name + ':' + repr(getattr(self, name)) + '\n' |
|
106 |
return res |
|
107 | ||
108 |
def __int__(self): |
|
109 |
'''Return the object id''' |
|
110 |
return self.id |
|
111 | ||
112 |
class UrgentContact(SimpleObject): |
|
113 |
COLUMNS = ( |
|
114 |
('IDENFANTS', 'id_enfant', strip_and_int, 'IDENFANTS'), |
|
115 |
('IDCONTACT_AUTORISE', 'id', strip_and_int, 'IDCONTACT_AUTORISE'), |
|
116 |
('LIENFAMILLE_CH', 'lien_de_famille', unicode_and_strip, 'LIENFAMILLE_CH'), |
|
117 |
('PERE_MERE_CH', 'lien_pere_ou_pere', unicode_and_strip, 'PERE_MERE_CH'), |
|
118 |
('IDFAMILLES', 'id_famille', unicode_and_strip, 'IDFAMILLES'), |
|
119 |
('TYPE_CH', 'type', unicode_and_strip, 'TYPE_CH'), |
|
120 |
('NOM_CH', 'nom', unicode_and_strip, 'NOM_CH'), |
|
121 |
('PRENOM_CH', 'prenom', unicode_and_strip, 'PRENOM_CH'), |
|
122 |
('RUE_CH', 'rue', unicode_and_strip, 'RUE_CH'), |
|
123 |
('RUE2_CH', 'rue2', unicode_and_strip, 'RUE2_CH'), |
|
124 |
('RUE3_CH', 'rue3', unicode_and_strip, 'RUE3_CH'), |
|
125 |
('CODEPOSTAL_CH', 'code_postal', unicode_and_strip, 'CODEPOSTAL_CH'), |
|
126 |
('VILLE_CH', 'ville', unicode_and_strip, 'VILLE_CH'), |
|
127 |
('TELEPHONE_CH', 'telephone', unicode_and_strip, 'TELEPHONE_CH'), |
|
128 |
('TELEPHONE2_CH', 'telephone2', unicode_and_strip, 'TELEPHONE2_CH'), |
|
129 |
('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'ADRESSEINT_CH'), |
|
130 |
) |
|
131 | ||
132 |
class Child(SimpleObject): |
|
133 |
COLUMNS = ( |
|
134 |
('IDENFANTS', 'id', strip_and_int, 'Identifiant de ENFANTS'), |
|
135 |
('NOM_CH', 'nom', unicode_and_strip, 'Nom'), |
|
136 |
('PRENOM_CH', 'prenom', unicode_and_strip, 'Prénom'), |
|
137 |
('NAISSANCE_DA', 'date_naissance', strip_and_date, 'Date de Naissance'), |
|
138 |
('COMMENTAIRE_ME', 'commentaire', unicode_and_strip, 'Commentaires / Notes'), |
|
139 |
('IDFAMILLES', 'id_famille', unicode_and_strip, 'IDFAMILLES'), |
|
140 |
('CODEPOSTAL_CH', 'code_postal', unicode_and_strip, 'Code Postal'), |
|
141 |
('VILLE_CH', 'ville', unicode_and_strip, 'Ville'), |
|
142 |
('CODEINTERNE_CH', 'code_interne', unicode_and_strip, 'Code Interne'), |
|
143 |
('LIEUNAISSANCE_CH', 'lieu_naissance', unicode_and_strip, 'Lieu de Naissance'), |
|
144 |
('DEPNAISSANCE_CH', 'departement_naissance', unicode_and_strip, 'Département Naissance'), |
|
145 |
('NUMSECU_CH', 'num_securite_sociale', unicode_and_strip, 'N° de SECU'), |
|
146 |
('NATIONALITE_CH', 'nationalite', unicode_and_strip, 'Nationalité'), |
|
147 |
('PRENOM2_CH', 'prenom2', unicode_and_strip, 'Prénom 2'), |
|
148 |
('SEXE_CH', 'sexe', unicode_and_strip, 'Sexe'), |
|
149 |
('IDTABLELIBRE1', 'IDTABLELIBRE1', unicode_and_strip, 'IDTABLELIBRE1'), |
|
150 |
('IDTABLELIBRE2', 'IDTABLELIBRE2', unicode_and_strip, 'IDTABLELIBRE2'), |
|
151 |
('IDTABLELIBRE3', 'IDTABLELIBRE3', unicode_and_strip, 'IDTABLELIBRE3'), |
|
152 |
('IDTABLELIBRE4', 'IDTABLELIBRE4', unicode_and_strip, 'IDTABLELIBRE4'), |
|
153 |
('CHAMPLIBRE1_CH', 'CHAMPLIBRE1_CH', unicode_and_strip, 'Valeur Champ Libre 1'), |
|
154 |
('CHAMPLIBRE2_CH', 'CHAMPLIBRE2_CH', unicode_and_strip, 'Valeur Champ Libre 2'), |
|
155 |
('CHAMPCALCULE1_CH', 'CHAMPCALCULE1_CH', unicode_and_strip, 'Valeur Champ Calculé 1'), |
|
156 |
('CHAMPCALCULE2_CH', 'CHAMPCALCULE2_CH', unicode_and_strip, 'Valeur Champ Calculé 2'), |
|
157 |
('SOMMEIL_ME', 'sommeil', unicode_and_strip, 'Sommeil'), |
|
158 |
('ACTIVITE_ME', 'activite', unicode_and_strip, 'Activités'), |
|
159 |
('HABITUDE_ME', 'habitude', unicode_and_strip, 'Habitudes'), |
|
160 |
('PHOTO_CH', 'photographie', unicode_and_strip, 'Photographie'), |
|
161 |
('NUMCOMPTE_CH', 'numcompte', unicode_and_strip, 'N° Compte Comptable'), |
|
162 |
('TELEPHONE_CH', 'telephone', unicode_and_strip, 'Téléphone'), |
|
163 |
('IDFAMILLES2', 'id_famille2', unicode_and_strip, 'Identifiant famille 2'), |
|
164 |
('PERE_CH', 'pere', unicode_and_strip, 'Nom du père'), |
|
165 |
('MERE_CH', 'mere', unicode_and_strip, 'Nom de la mère'), |
|
166 |
('AUTOPARENTALEMERE_IN', 'autorisation_parentale_mere', unicode_and_strip, 'Autorisation Parentale Mère'), |
|
167 |
('AUTOPARENTALEPERE_IN', 'autorisation_parentale_pere', unicode_and_strip, 'Autorisation Parentale de Père'), |
|
168 |
('IDPORTAIL_ENFANTS', 'id_portail_enfants', unicode_and_strip, 'Identifiant de PORTAIL_ENFANTS'), |
|
169 |
('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'Adresse Internet'), |
|
170 |
) |
|
171 | ||
172 |
def save(self): |
|
173 |
if hasattr(self, 'id'): |
|
174 |
self.client.update_child(self) |
|
175 |
else: |
|
176 |
self.id = self.client.add_child(self) |
|
177 |
self.client.clear_cache() |
|
178 | ||
179 |
class Family(SimpleObject): |
|
180 |
COLUMNS = ( |
|
181 |
('IDFAMILLES', 'id', strip_and_int, 'identifiant de famille'), |
|
182 |
('NOMFAMILLE_CH', 'famille_nom', unicode_and_strip, 'nom de famille'), |
|
183 |
('EMAILPERE_CH', 'email_pere', unicode_and_strip, 'email du père'), |
|
184 |
('EMAILMERE_CH', 'email_mere', unicode_and_strip, 'email de la mère'), |
|
185 |
('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'adresse internet'), |
|
186 |
('CODEINTERNE_CH', 'code_interne', unicode_and_strip, 'code interne'), |
|
187 |
) |
|
188 | ||
189 |
MORE_COLUMNS = ( |
|
190 |
('IDFAMILLES', 'id', strip_and_int, 'identifiant de famille'), |
|
191 |
('CODEINTERNE_CH', 'code_interne', unicode_and_strip, 'code interne'), |
|
192 |
('CIVILITE_CH', 'civilite', unicode_and_strip, 'civilité'), |
|
193 |
('NOMFAMILLE_CH', 'famille_nom', unicode_and_strip, 'nom de famille'), |
|
194 |
('RUE_CH', 'rue', unicode_and_strip, 'rue'), |
|
195 |
('RUE2_CH', 'rue2', unicode_and_strip, 'rue 2'), |
|
196 |
('RUE3_CH', 'rue3', unicode_and_strip, 'rue 3'), |
|
197 |
('CODEPOSTAL_CH', 'code_postal', unicode_and_strip, 'code postal'), |
|
198 |
('VILLE_CH', 'ville', unicode_and_strip, 'ville'), |
|
199 |
('TELEPHONE_CH', 'telephone', unicode_and_strip, 'téléphone'), |
|
200 |
('TELEPHONE2_CH', 'telephone2', unicode_and_strip, 'téléphone 2'), |
|
201 |
('TELECOPIE_CH', 'telecopie', unicode_and_strip, 'télécopie'), |
|
202 |
('TELECOPIE2_CH', 'telecopie2', unicode_and_strip, 'télécopie 2'), |
|
203 |
('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'adresse internet'), |
|
204 |
('SITUATION_CH', 'situation', unicode_and_strip, 'situation familiale'), |
|
205 |
('REVENUMENSUEL_MO', 'revenu_mensuel', unicode_and_strip, 'revenu mensuel de la famille'), |
|
206 |
('REVENUANNUEL_MO', 'revenu_annuel', unicode_and_strip, 'revenu annuel de la famille'), |
|
207 |
('QUOTIENTFAMILIAL_MO', 'quotient_familial', unicode_and_strip, 'quotient familial'), |
|
208 |
('NBTOTALENFANTS_EN', 'nb_total_enfants', unicode_and_strip, 'nombre total d\'enfants'), |
|
209 |
('NBENFANTSACHARGE_EN', 'nb_enfants_a_charge', unicode_and_strip, 'nombre d\'enfants à charge'), |
|
210 |
('NOMPERE_CH', 'nom_pere', unicode_and_strip, 'monsieur'), |
|
211 |
('PRENOMPERE_CH', 'prenom_pere', unicode_and_strip, 'prénom monsieur'), |
|
212 |
('AUTOPARENTALEPERE_IN', 'autoparentale_pere', unicode_and_strip, 'autorisation parentale de père'), |
|
213 |
('DATENAISPERE_DA', 'date_naissance_pere', strip_and_date, 'date de naisance du père'), |
|
214 |
('DEPNAISPERE_EN', 'departement_naissance_pere', unicode_and_strip, 'département de naissance du père'), |
|
215 |
('LIEUNAISPERE_CH', 'lieu_naissance_pere', unicode_and_strip, 'lieu de naissance du père'), |
|
216 |
('RUEPERE_CH', 'rue_pere', unicode_and_strip, 'rue père'), |
|
217 |
('RUE2PERE_CH', 'rue2_pere', unicode_and_strip, 'rue 2 père'), |
|
218 |
('RUE3PERE_CH', 'rue3_pere', unicode_and_strip, 'rue 3 père'), |
|
219 |
('CODEPOSTALPERE_CH', 'code_postal_pere', unicode_and_strip, 'code postal père'), |
|
220 |
('VILLEPERE_CH', 'ville_pere', unicode_and_strip, 'ville père'), |
|
221 |
('TELEPHONEPERE_CH', 'telephone_pere', unicode_and_strip, 'téléphone père'), |
|
222 |
('TELEPHONE2PERE_CH', 'telephone2_pere', unicode_and_strip, 'téléphone 2 père'), |
|
223 |
('TELPERE_LR_IN', 'tel_pere_liste_rouge', unicode_and_strip, 'téléphone liste rouge père'), |
|
224 |
('TEL2PERE_LR_IN', 'tel2_pere_liste_rouge', unicode_and_strip, 'téléphone 2 liste rouge père'), |
|
225 |
('TEL_LR_IN', 'tel_liste_rourge', unicode_and_strip, 'téléphone liste rouge'), |
|
226 |
('TEL2_LR_IN', 'tel2_liste_rouge', unicode_and_strip, 'téléphone 2 liste rouge'), |
|
227 |
('NOMMERE_CH', 'nom_mere', unicode_and_strip, 'madame'), |
|
228 |
('PRENOMMERE_CH', 'prenom_mere', unicode_and_strip, 'prénom madame'), |
|
229 |
('AUTOPARENTALEMERE_IN', 'autoparentale_mere', unicode_and_strip, 'autorisation parentale mère'), |
|
230 |
('DATENAISMERE_DA', 'date_naissance_mere', strip_and_date, 'date de naissance de la mère'), |
|
231 |
('DEPNAISMERE_EN', 'departement_naissance_mere', unicode_and_strip, 'département de naissance de la mère'), |
|
232 |
('LIEUNAISMERE_CH', 'lieu_naissance_mere', unicode_and_strip, 'lieu de naissance de la mère'), |
|
233 |
('RUEMERE_CH', 'rue_mere', unicode_and_strip, 'rue mère'), |
|
234 |
('REVMENSUELPERE_MO', 'revenu_mensuel_pere', unicode_and_strip, 'revenu mensuel du père'), |
|
235 |
('RUE2MERE_CH', 'rue2_mere', unicode_and_strip, 'rue 2 mère'), |
|
236 |
('RUE3MERE_CH', 'rue3_mere', unicode_and_strip, 'rue 3 mère'), |
|
237 |
('CODEPOSTALMERE_CH', 'code_postal_mere', unicode_and_strip, 'code postal de la mère'), |
|
238 |
('VILLEMERE_CH', 'ville_mere', unicode_and_strip, 'ville de la mère'), |
|
239 |
('REVMENSUELMERE_MO', 'revenu_mensuel_mere', unicode_and_strip, 'revenu mensuel mère'), |
|
240 |
('REVANNUELPERE_MO', 'revenu_annuel_pere', unicode_and_strip, 'revenu annuel père'), |
|
241 |
('REVANNUELMERE_MO', 'revenu_annuel_mere', unicode_and_strip, 'revenu annuel mère'), |
|
242 |
('TELEPHONEMERE_CH', 'telephone_mere', unicode_and_strip, 'téléphone mère'), |
|
243 |
('TELEPHONE2MERE_CH', 'telephone2_mere', unicode_and_strip, 'téléphone 2 mère'), |
|
244 |
('TELMERE_LR_IN', 'telephone_mere_liste_rouge', unicode_and_strip, 'téléphone liste rouge mère'), |
|
245 |
('TEL2MERE_LR_IN', 'telephone2_mere_liste_rouge', unicode_and_strip, 'téléphone 2 liste rouge mère'), |
|
246 |
('TELECOPIEPERE_CH', 'telecopie_pere', unicode_and_strip, 'télécopie du père'), |
|
247 |
('TELECOPIE2PERE_CH', 'telecopie2_pere', unicode_and_strip, 'télécopie 2 du père'), |
|
248 |
('TELECOPIEMERE_CH', 'telecopie_mere', unicode_and_strip, 'télécopie de la mère'), |
|
249 |
('TELECOPIE2MERE_CH', 'telecopie2_mere', unicode_and_strip, 'télécopie 2 de la mère'), |
|
250 |
('PROFPERE_CH', 'profession_pere', unicode_and_strip, 'profession du père'), |
|
251 |
('PROFMERE_CH', 'profession_mere', unicode_and_strip, 'profession de la mère'), |
|
252 |
('LIEUTRAVPERE_CH', 'lieu_travail_pere', unicode_and_strip, 'lieu de travail du père'), |
|
253 |
('LIEUTRAVMERE_CH', 'lieu_travail_mere', unicode_and_strip, 'lieu de travail de la mère'), |
|
254 |
('RUETRAVPERE_CH', 'rue_travail_pere', unicode_and_strip, 'rue travail père'), |
|
255 |
('RUE2TRAVPERE_CH', 'rue2_travail_pere', unicode_and_strip, 'rue 2 travail père'), |
|
256 |
('RUE3TRAVPERE_CH', 'rue3_travail_pere', unicode_and_strip, 'rue 3 travail père'), |
|
257 |
('CPTRAVPERE_CH', 'code_postal_travail_pere', unicode_and_strip, 'code postal travail père'), |
|
258 |
('VILLETRAVPERE_CH', 'ville_travail_pere', unicode_and_strip, 'ville travail père'), |
|
259 |
('RUETRAVMERE_CH', 'rue_travail_mere', unicode_and_strip, 'rue travail mère'), |
|
260 |
('RUE2TRAVMERE_CH', 'rue2_travail_mere', unicode_and_strip, 'rue 2 travail mère'), |
|
261 |
('RUE3TRAVMERE_CH', 'rue3_travail_mere', unicode_and_strip, 'rue 3 travail mère'), |
|
262 |
('CPTRAVMERE_CH', 'code_postal_travail_mere', unicode_and_strip, 'code postal travail mère'), |
|
263 |
('VILLETRAVMERE_CH', 'ville_travail_mere', unicode_and_strip, 'ville travail mère'), |
|
264 |
('TELPROFPERE_CH', 'telephone_travail_pere', unicode_and_strip, 'téléphone professionnel père'), |
|
265 |
('TEL2PROFPERE_CH', 'telephone2_travail_pere', unicode_and_strip, 'téléphone 2 professionnel père'), |
|
266 |
('TELMOBILPERE_CH', 'telephone_mobile_pere', unicode_and_strip, 'téléphone mobile'), |
|
267 |
('TELPROFMERE_CH', 'telephone_travail_mere', unicode_and_strip, 'téléphone travail mère'), |
|
268 |
('TEL2PROFMERE_CH', 'telephone2_travail_mere', unicode_and_strip, 'téléphone 2 travail mère'), |
|
269 |
('TELMOBILMERE_CH', 'telephone_mobile_mere', unicode_and_strip, 'téléphone mobile mère'), |
|
270 |
('TOTALDU_MO', 'total_du', unicode_and_strip, 'total dû'), |
|
271 |
('TOTALREGLE_MO', 'total_regle', unicode_and_strip, 'total réglé'), |
|
272 |
('NUMCENTRESS_CH', 'num_centre_securite_sociale', unicode_and_strip, 'n° centre sécurité sociale'), |
|
273 |
('NOMCENTRESS_CH', 'nom_centre_securite_sociale', unicode_and_strip, 'nom centre sécurité sociale'), |
|
274 |
('NUMASSURANCE_CH', 'num_assurance', unicode_and_strip, 'n° assurance'), |
|
275 |
('NOMASSURANCE_CH', 'nom_assurance', unicode_and_strip, 'nom assurance'), |
|
276 |
('RIVOLI_EN', 'code_rivoli', unicode_and_strip, 'identifiant code rivoli'), |
|
277 |
('NUMCOMPTE_CH', 'numero_compte_comptable', unicode_and_strip, 'n° compte comptable'), |
|
278 |
('EMAILPERE_CH', 'email_pere', unicode_and_strip, 'email du père'), |
|
279 |
('EMAILMERE_CH', 'email_mere', unicode_and_strip, 'email de la mère'), |
|
280 |
('NUMALLOCATAIRE_CH', 'numero_allocataire', unicode_and_strip, 'n° allocataire'), |
|
281 |
('COMMENTAIRE_ME', 'commentaire', unicode_and_strip, 'commentaires / notes'), |
|
282 |
('IDCSPPERE', 'identifiant_csp_pere', unicode_and_strip, 'référence identifiant csp'), |
|
283 |
('IDCSPMERE', 'identifiant_csp_mere', unicode_and_strip, 'référence identifiant csp'), |
|
284 |
('IDSECTEURS', 'identifiant_secteurs', unicode_and_strip, 'référence identifiant secteurs'), |
|
285 |
('IDZONES', 'identifiant_zones', unicode_and_strip, 'référence identifiant zones'), |
|
286 |
('IDRUES', 'identifiant_rues', unicode_and_strip, 'référence identifiant rues'), |
|
287 |
('IDVILLES', 'identifiant_villes', unicode_and_strip, 'référence identifiant villes'), |
|
288 |
('IDREGIMES', 'identifiant_regimes', unicode_and_strip, 'référence identifiant regimes'), |
|
289 |
('IDSITUATIONFAMILLE', 'identifiant_situation_famille', unicode_and_strip, 'référence identifiant situationfamille'), |
|
290 |
('NUMSECUPERE_CH', 'num_securite_sociale_pere', unicode_and_strip, 'n° secu père'), |
|
291 |
('NUMSECUMERE_CH', 'num_securite_sociale_mere', unicode_and_strip, 'n° secu mère'), |
|
292 |
('NATIONPERE_CH', 'nation_pere', unicode_and_strip, 'nationalité père'), |
|
293 |
('NATIONMERE_CH', 'nation_mere', unicode_and_strip, 'nationalité mère'), |
|
294 |
('NOMJEUNEFILLE_CH', 'nom_jeune_fille', unicode_and_strip, 'nom jeune fille'), |
|
295 |
('IDCAFS', 'idcafs', unicode_and_strip, 'référence identifiant cafs'), |
|
296 |
('CHAMPLIBRE1_CH', 'champ_libre1', unicode_and_strip, 'valeur champ libre 1'), |
|
297 |
('CHAMPLIBRE2_CH', 'champ_libre2', unicode_and_strip, 'valeur champ libre 2'), |
|
298 |
('CHAMPCALCULE1_CH', 'champ_calcule1', unicode_and_strip, 'valeur champ calculé 1'), |
|
299 |
('CHAMPCALCULE2_CH', 'champ_calcule2', unicode_and_strip, 'valeur champ calculé 2'), |
|
300 |
('IDTABLELIBRE1', 'id_table_libre1', unicode_and_strip, 'idtablelibre1'), |
|
301 |
('IDTABLELIBRE3', 'id_table_libre3', unicode_and_strip, 'idtablelibre3'), |
|
302 |
('IDTABLELIBRE2', 'id_table_libre2', unicode_and_strip, 'idtablelibre2'), |
|
303 |
('IDTABLELIBRE4', 'id_table_libre4', unicode_and_strip, 'idtablelibre4'), |
|
304 |
('NOMURSSAF_CH', 'nom_urssaf', unicode_and_strip, 'nom urssaf'), |
|
305 |
('NUMURSSAF_CH', 'num_urssaf', unicode_and_strip, 'n° urssaf'), |
|
306 |
('IDPROFPERE', 'identifiant_profession_pere', unicode_and_strip, 'référence identifiant profession'), |
|
307 |
('IDPROFMERE', 'identifiant_profession_mere', unicode_and_strip, 'référence identifiant profession'), |
|
308 |
('ALLOCATAIRE_CH', 'allocataire', unicode_and_strip, 'allocataire père ou mère (p,m)'), |
|
309 |
# ('PHOTOPERE_CH', 'photo_pere', unicode_and_strip, 'photographie père'), |
|
310 |
# ('PHOTOMERE_CH', 'photo_mere', unicode_and_strip, 'photographie mère'), |
|
311 |
('NUMRUE_CH', 'numero_rue', unicode_and_strip, 'numéro de rue'), |
|
312 |
('NUMRUEPERE_CH', 'numero_rue_pere', unicode_and_strip, 'numéro de rue père'), |
|
313 |
('NUMRUEMERE_CH', 'numero_rue_mere', unicode_and_strip, 'numéro de rue mère'), |
|
314 |
('IDPORTAIL_FAMILLES', 'identifiant_portail_familles', unicode_and_strip, 'identifiant de portail_familles'), |
|
315 |
('ECHEANCEASSURANCE_DA', 'echeance_assurance', unicode_and_strip, 'date echéance assurance'), |
|
316 |
('RM_MIKADO_MO', 'rm_mikado', unicode_and_strip, 'revenus mensuels mikado'), |
|
317 |
('RA_MIKADO_MO', 'ra_mikado', unicode_and_strip, 'revenus annuels mikado'), |
|
318 |
('QF_MIKADO_MO', 'qf_mikado', unicode_and_strip, 'quotient familial mikado'), |
|
319 |
('RM_DIABOLO_MO', 'rm_diabolo', unicode_and_strip, 'revenus mensuels diabolo'), |
|
320 |
('RA_DIABOLO_MO', 'ra_diabolo', unicode_and_strip, 'revenus annuels diabolo'), |
|
321 |
('QF_DIABOLO_MO', 'qf_diabolo', unicode_and_strip, 'quotient familial diabolo'), |
|
322 |
('RM_OLIGO_MO', 'rm_oligo', unicode_and_strip, 'revenus mensuels oligo'), |
|
323 |
('RA_OLIGO_MO', 'ra_oligo', unicode_and_strip, 'revenus annuels oligo'), |
|
324 |
('QF_OLIGO_MO', 'qf_oligo', unicode_and_strip, 'quotient familial oligo'), |
|
325 |
('APPLICATION_REV_MIKADO_DA', 'application_rev_mikado', unicode_and_strip, 'date d\'application des revenus de mikado'), |
|
326 |
('APPLICATION_REV_DIABOLO_DA', 'application_rev_diabolo', unicode_and_strip, 'date d\'application des revenus de diabolo'), |
|
327 |
('APPLICATION_REV_OLIGO_DA', 'application_rev_oligo', unicode_and_strip, 'date d\'application des revenus de oligo'), |
|
328 |
) |
|
329 | ||
330 |
def __init__(self, *args, **kwargs): |
|
331 |
self.children = [] |
|
332 |
super(Family, self).__init__(*args, **kwargs) |
|
333 | ||
334 |
def complete(self): |
|
335 |
k = [a for a,b,c,d in self.MORE_COLUMNS] |
|
336 |
list(self.client('LISTER_FAMILLES', args=(','.join(k), self.id), |
|
337 |
columns=self.MORE_COLUMNS, instances=(self,))) |
|
338 |
l = self.client.get_children(self.id).values() |
|
339 |
self.children = sorted(l, key=lambda c: c.id) |
|
340 |
return self |
|
341 | ||
342 |
@property |
|
343 |
def invoices(self): |
|
344 |
return [invoice for id, invoice in self.client.invoices.items() if invoice.id_famille == self.id] |
|
345 | ||
346 |
def add_child(self, child): |
|
347 |
if hasattr(self, 'id'): |
|
348 |
child.id_famille = self.id |
|
349 |
child.client = self.client |
|
350 |
self.children.append(child) |
|
351 | ||
352 |
def save(self): |
|
353 |
if hasattr(self, 'id'): |
|
354 |
self.client.update_family(self) |
|
355 |
else: |
|
356 |
self.code_interne = self.client.new_code_interne() |
|
357 |
self.id = self.client.add_family(self) |
|
358 |
for child in self.children: |
|
359 |
child.id_famille = self.id |
|
360 |
child.save() |
|
361 |
self.client.clear_cache() |
|
362 | ||
363 |
class Invoice(SimpleObject): |
|
364 |
COLUMNS = ( |
|
365 |
('', 'id_famille', int, ''), |
|
366 |
('', 'id', int, ''), |
|
367 |
('', 'numero', str, ''), |
|
368 |
('', 'debut_periode', parse_date, ''), |
|
369 |
('', 'fin_periode', parse_date, ''), |
|
370 |
('', 'creation', parse_date, ''), |
|
371 |
('', 'echeance', parse_date, ''), |
|
372 |
('', 'montant', Decimal, ''), |
|
373 |
('', 'reste_du', Decimal, ''), |
|
374 |
) |
|
375 |
_detail = {} |
|
376 | ||
377 |
def detail(self): |
|
378 |
if not self._detail: |
|
379 |
self.client.factures_detail([self]) |
|
380 |
return self._detail |
|
381 | ||
382 |
@property |
|
383 |
def family(self): |
|
384 |
return self.client.families[self.id_famille] |
|
385 | ||
386 |
def paid(self): |
|
387 |
return self.reste_du == Decimal(0) |
|
388 | ||
389 |
class DominoWs(object): |
|
390 |
'''Interface to the WebService exposed by Abelium Domino. |
|
391 | ||
392 |
It allows to retrieve family and invoices. |
|
393 | ||
394 |
Beware that it does a lot of caching to limit call to the webservice, so |
|
395 |
if you need fresh data, call clear_cache() |
|
396 | ||
397 |
All family are in the families dictionnary and all invoices in the |
|
398 |
invoices dictionnary. |
|
399 |
''' |
|
400 | ||
401 |
def __init__(self, url, domain, login, password, location=None, |
|
402 |
logger=logger): |
|
403 |
if not Client: |
|
404 |
raise ValueError('You need python suds') |
|
405 |
self.logger = logger |
|
406 |
self.logger.debug('creating DominoWs(%r, %r, %r, %r, location=%r)', |
|
407 |
url, domain, login, password, location) |
|
408 |
self.url = url |
|
409 |
self.domain = domain |
|
410 |
self.login = login |
|
411 |
self.password = password |
|
412 |
self.client = Client(url, location=location, timeout=60) |
|
413 |
self.client.options.cache.setduration(seconds=60) |
|
414 | ||
415 |
def clear_cache(self): |
|
416 |
'''Remove cached attributes from the instance.''' |
|
417 | ||
418 |
for key, value in self.__dict__.items(): |
|
419 |
if key.startswith('__') and key.endswith('_cache'): |
|
420 |
del self.__dict__[key] |
|
421 | ||
422 |
def call(self, function_name, *args): |
|
423 |
'''Call SOAP method named function_name passing args list as parameters. |
|
424 | ||
425 |
Any error is converted into the DominoException class.''' |
|
426 | ||
427 |
try: |
|
428 |
self.logger.debug(('soap call to %s(%s)' % (function_name, args)).encode('utf-8')) |
|
429 |
data = getattr(self.client.service, function_name)(self.domain, self.login, self.password, *args) |
|
430 |
self.logger.debug((u'result: %s' % data).encode('utf-8')) |
|
431 |
self.data = data |
|
432 |
except IOError as e: |
|
433 |
raise DominoException('Erreur IO', e) |
|
434 |
if data is None: |
|
435 |
data = '' |
|
436 |
if data.startswith('ERREUR'): |
|
437 |
raise DominoException(data[9:].encode('utf8')) |
|
438 |
return data |
|
439 | ||
440 |
def parse_tabular_data(self, data): |
|
441 |
'''Row are separated by carriage-return, ASCII #13, characters and columns by tabs. |
|
442 |
Empty lines (ignoring spaces) are ignored. |
|
443 |
''' |
|
444 | ||
445 |
rows = data.split(LINE_SEPARATOR) |
|
446 |
rows = [[cell.strip() for cell in row.split(COLUMN_SEPARATOR)] for row in rows if row.strip() != ''] |
|
447 |
return rows |
|
448 | ||
449 |
def __call__(self, function_name, cls=None, args=[], instances=None, columns=None): |
|
450 |
'''Call SOAP method named function_name, splitlines, map tab separated |
|
451 |
values to _map keys in a dictionnary, and use this dictionnary to |
|
452 |
initialize an object of class cls. |
|
453 | ||
454 |
- If instances is present, the given instances are updated with the |
|
455 |
returned content, in order, row by row. |
|
456 |
- If cls is not None and instances is None, a new instance of the class |
|
457 |
cls is instancied for every row and initialized with the content of |
|
458 |
the row. |
|
459 |
- If cls and instances are None, the raw data returned by the SOAP call |
|
460 |
is returned. |
|
461 |
''' |
|
462 | ||
463 |
data = self.call(function_name, *args) |
|
464 |
if cls or instances: |
|
465 |
rows = self.parse_tabular_data(data) |
|
466 |
kwargs = {} |
|
467 |
if instances: |
|
468 |
rows = zip(rows, instances) |
|
469 |
for row in rows: |
|
470 |
if instances: |
|
471 |
row, instance = row |
|
472 |
if not row[0]: |
|
473 |
continue |
|
474 |
for a, b in zip(columns or cls.COLUMNS, row): |
|
475 |
x, name, converter, desc = a |
|
476 |
kwargs[name] = converter(b.strip()) |
|
477 |
if instances: |
|
478 |
instance.__dict__.update(kwargs) |
|
479 |
yield instance |
|
480 |
else: |
|
481 |
yield cls(client=self, **kwargs) |
|
482 |
else: |
|
483 |
yield data |
|
484 | ||
485 |
def add_family(self, family): |
|
486 |
result = self.call('AJOUTER_FAMILLE', family.serialize()) |
|
487 |
return int(result.strip()) |
|
488 | ||
489 |
def update_family(self, family): |
|
490 |
if not hasattr(family, 'id'): |
|
491 |
raise DominoException('Family lacks an "id" attribute, it usually means that it is new.') |
|
492 |
result = self.call('MODIFIER_FAMILLE', unicode(family.id), family.serialize()) |
|
493 |
return result.strip() == 'OK' |
|
494 | ||
495 |
def add_child(self, child): |
|
496 |
result = self.call('AJOUTER_ENFANT', child.serialize()) |
|
497 |
return int(result.strip()) |
|
498 | ||
499 |
def update_child(self, child): |
|
500 |
if not hasattr(child, 'id'): |
|
501 |
raise DominoException('Family lacks an "id" attribute, it usually means that it is new.') |
|
502 |
result = self.call('MODIFIER_ENFANT', unicode(child.id), child.serialize()) |
|
503 |
return result.strip() == 'OK' |
|
504 | ||
505 |
@property |
|
506 |
@object_cached |
|
507 |
def families(self): |
|
508 |
'''Dictionary of all families indexed by their id. |
|
509 | ||
510 |
After the first use, the value is cached. Use clear_cache() to reset |
|
511 |
it. |
|
512 |
''' |
|
513 | ||
514 |
return self.get_families() |
|
515 | ||
516 |
def get_families(self, id_famille=0, full=False): |
|
517 |
'''Get families informations. |
|
518 |
There is no caching. |
|
519 | ||
520 |
id_famille - if not 0, the family with this id is retrieved. If 0 |
|
521 |
all families are retrieved. Default to 0. |
|
522 |
full - If True return all the columns of the family table. Default |
|
523 |
to False. |
|
524 |
''' |
|
525 |
columns = Family.MORE_COLUMNS if full else Family.COLUMNS |
|
526 |
families = self('LISTER_FAMILLES', |
|
527 |
Family, |
|
528 |
args=(','.join([x[0] for x in columns]), id_famille)) |
|
529 |
return dict([(int(x), x) for x in families]) |
|
530 | ||
531 |
def get_children(self, id_famille=0): |
|
532 |
columns = Child.COLUMNS |
|
533 |
if id_famille == 0: |
|
534 |
children = self('LISTER_ENFANTS', |
|
535 |
Child, |
|
536 |
args=((','.join([x[0] for x in columns])),)) |
|
537 |
else: |
|
538 |
children = self('LISTER_ENFANTS_FAMILLE', |
|
539 |
Child, |
|
540 |
args=(id_famille, (','.join([x[0] for x in columns])))) |
|
541 |
return dict([(int(x), x) for x in children]) |
|
542 | ||
543 |
def get_urgent_contacts(self, id_enfant): |
|
544 |
columns = UrgentContact.COLUMNS |
|
545 |
urgent_contacts = self('LISTER_PERSONNES_URGENCE', |
|
546 |
UrgentContact, |
|
547 |
args=((id_enfant, ','.join([x[0] for x in columns])))) |
|
548 |
return dict([(int(x), x) for x in urgent_contacts]) |
|
549 | ||
550 |
@property |
|
551 |
@object_cached |
|
552 |
def invoices(self): |
|
553 |
'''Dictionnary of all invoices indexed by their id. |
|
554 | ||
555 |
After the first use, the value is cached. Use clear_cache() to reset |
|
556 |
it. |
|
557 |
''' |
|
558 |
invoices = self.get_invoices() |
|
559 |
for invoice in invoices.values(): |
|
560 |
invoice.famille = self.families[invoice.id_famille] |
|
561 |
return invoices |
|
562 | ||
563 |
def new_code_interne(self): |
|
564 |
max_ci = 0 |
|
565 |
for family in self.families.values(): |
|
566 |
try: |
|
567 |
max_ci = max(max_ci, int(family.code_interne)) |
|
568 |
except: |
|
569 |
pass |
|
570 |
return '%05d' % (max_ci+1) |
|
571 | ||
572 |
def get_invoices(self, id_famille=0, state='TOUTES'): |
|
573 |
'''Get invoices informations. |
|
574 | ||
575 |
id_famille - If value is not 0, only invoice for the family with |
|
576 |
this id are retrieved. If value is 0, invoices for all families are |
|
577 |
retrieved. Default to 0. |
|
578 |
etat - state of the invoices to return, possible values are |
|
579 |
'SOLDEES', 'NON_SOLDEES', 'TOUTES'. |
|
580 |
''' |
|
581 |
invoices = self('LISTER_FACTURES_FAMILLE', Invoice, |
|
582 |
args=(id_famille, state)) |
|
583 |
invoices = list(invoices) |
|
584 |
for invoice in invoices: |
|
585 |
invoice.famille = self.families[invoice.id_famille] |
|
586 |
return dict(((int(x), x) for x in invoices)) |
|
587 | ||
588 |
FACTURE_DETAIL_HEADERS = ['designation', 'quantite', 'prix', 'montant'] |
|
589 |
def factures_detail(self, invoices): |
|
590 |
'''Retrieve details of some invoice''' |
|
591 |
data = self.call('DETAILLER_FACTURES', (''.join(("%s;" % int(x) for x in invoices)),)) |
|
592 |
try: |
|
593 |
tree = etree.fromstring(data.encode('utf8')) |
|
594 |
for invoice, facture_node in zip(invoices, tree.findall('facture')): |
|
595 |
rows = [] |
|
596 |
for ligne in facture_node.findall('detail_facture/ligne'): |
|
597 |
row = [] |
|
598 |
rows.append(row) |
|
599 |
for header in self.FACTURE_DETAIL_HEADERS: |
|
600 |
if header in ligne.attrib: |
|
601 |
row.append((header, ligne.attrib[header])) |
|
602 |
etablissement = facture_node.find('detail_etablissements/etablissement') |
|
603 |
if etablissement is not None: |
|
604 |
nom = etablissement.get('nom').strip() |
|
605 |
else: |
|
606 |
nom = '' |
|
607 |
d = { 'etablissement': nom, 'lignes': rows } |
|
608 |
invoice._detail = d |
|
609 |
except Exception as e: |
|
610 |
raise DominoException('Exception when retrieving invoice details', e) |
|
611 | ||
612 |
def get_family_by_mail(self, email): |
|
613 |
'''Return the first whose one email attribute matches the given email''' |
|
614 |
for famille in self.families.values(): |
|
615 |
if email in (famille.email_pere, famille.email_mere, |
|
616 |
famille.adresse_internet): |
|
617 |
return famille |
|
618 |
return None |
|
619 | ||
620 |
def get_family_by_code_interne(self, code_interne): |
|
621 |
'''Return the first whose one email attribute matches the given email''' |
|
622 |
for famille in self.families.values(): |
|
623 |
if getattr(famille, 'code_interne', None) == code_interne: |
|
624 |
return famille |
|
625 |
return None |
|
626 | ||
627 |
def pay_invoice(self, id_invoices, amount, other_information, date=None): |
|
628 |
'''Notify Domino of the payment of some invoices. |
|
629 | ||
630 |
id_invoices - integer if of the invoice or Invoice instances |
|
631 |
amount - amount as a Decimal object |
|
632 |
other_information - free content to attach to the payment, like a |
|
633 |
bank transaction number for correlation. |
|
634 |
date - date of the payment, must be a datetime object. If None, |
|
635 |
now() is used. Default to None. |
|
636 |
''' |
|
637 | ||
638 |
if not date: |
|
639 |
date = datetime.datetime.now() |
|
640 |
due = sum([self.invoices[int(id_invoice)].reste_du |
|
641 |
for id_invoice in id_invoices]) |
|
642 |
if Decimal(amount) == Decimal(due): |
|
643 |
return self('SOLDER_FACTURE', None, args=(str(amount), |
|
644 |
''.join([ '%s;' % int(x) for x in id_invoices]), |
|
645 |
date.strftime('%Y-%m-%d'), other_information)) |
|
646 |
else: |
|
647 |
raise DominoException('Amount due and paid do not match', { 'due': due, 'paid': amount}) |
auquotidien/modules/admin.py | ||
---|---|---|
18 | 18 |
from wcs.qommon.backoffice.menu import html_top |
19 | 19 | |
20 | 20 |
import re |
21 |
from .abelium_domino_ui import AbeliumDominoDirectory |
|
22 | 21 | |
23 | 22 | |
24 | 23 |
class PanelDirectory(Directory): |
25 |
_q_exports = ['', 'update', 'permissions', 'domino']
|
|
24 |
_q_exports = ['', 'update', 'permissions'] |
|
26 | 25 |
label = N_('Control Panel') |
27 | 26 | |
28 |
domino = AbeliumDominoDirectory() |
|
29 | ||
30 | 27 |
def _verify_mask(self, form): |
31 | 28 |
if form.is_submitted(): |
32 | 29 |
if not re.match("[0-9Xx]*$", form.get('mobile_mask') or ''): |
... | ... | |
76 | 73 |
r += htmltext('<h2>%s</h2>') % _('Extra Options') |
77 | 74 |
r += htmltext('<ul>') |
78 | 75 |
r += htmltext('<li><a href="aq/permissions">%s</a></li>') % _('Permissions') |
79 |
if get_publisher().has_site_option('domino'): |
|
80 |
r += htmltext('<li><a href="aq/domino">%s</a></li>') % _('Abelium Domino Integration') |
|
81 | 76 |
r += htmltext('</ul>') |
82 | 77 |
r += htmltext('</div>') |
83 | 78 |
r += htmltext('</div>') |
auquotidien/modules/connectors.py | ||
---|---|---|
1 |
from . import abelium_domino_workflow |
|
2 | 1 |
from . import clicrdv |
3 |
- |