0001-mail-feed-from-MaarchCourrier-22550.patch
debian/control | ||
---|---|---|
11 | 11 |
Depends: ${misc:Depends}, ${python:Depends}, |
12 | 12 |
python-django (>= 1.7), |
13 | 13 |
python-gadjo, |
14 |
python-requests, |
|
14 |
python-requests (>= 2.11),
|
|
15 | 15 |
python-django-haystack (>= 2.4.0), |
16 | 16 |
python-django-reversion (>= 2.0.12), |
17 | 17 |
python-django-taggit (>= 0.17.4), |
setup.py | ||
---|---|---|
106 | 106 |
'requests', |
107 | 107 |
'whoosh', |
108 | 108 |
'XStatic-Select2', |
109 |
'python-dateutil', |
|
109 | 110 |
], |
110 | 111 |
zip_safe=False, |
111 | 112 |
cmdclass={ |
tests/conftest.py | ||
---|---|---|
1 |
# welco - multichannel request processing |
|
2 |
# Copyright (C) 2015 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 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 Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import pytest |
|
18 |
import django_webtest |
|
19 | ||
20 | ||
21 |
@pytest.fixture |
|
22 |
def app(request): |
|
23 |
wtm = django_webtest.WebTestMixin() |
|
24 |
wtm._patch_settings() |
|
25 |
request.addfinalizer(wtm._unpatch_settings) |
|
26 |
return django_webtest.DjangoTestApp() |
|
27 | ||
28 | ||
29 |
@pytest.fixture |
|
30 |
def user(db): |
|
31 |
from django.contrib.auth.models import User |
|
32 | ||
33 |
user = User.objects.create(username='toto') |
|
34 |
user.set_password('toto') |
|
35 |
user.save() |
|
36 |
return user |
|
37 | ||
38 | ||
39 |
@pytest.fixture |
|
40 |
def mail_group(db, settings, user): |
|
41 |
from django.contrib.auth.models import Group |
|
42 | ||
43 |
# add mail group to default user |
|
44 |
group = Group.objects.create(name='mail') |
|
45 |
user.groups.add(group) |
|
46 | ||
47 |
# define authorization of mail group on mail channel |
|
48 |
channel_roles = getattr(settings, 'CHANNEL_ROLES', {}) |
|
49 |
mail_roles = channel_roles.setdefault('mail', []) |
|
50 |
mail_roles.append('mail') |
|
51 |
return group |
tests/test_source_maarch.py | ||
---|---|---|
1 |
# welco - multichannel request processing |
|
2 |
# Copyright (C) 2015 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 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 Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import json |
|
18 | ||
19 |
import pytest |
|
20 | ||
21 |
from httmock import urlmatch, HTTMock |
|
22 | ||
23 | ||
24 |
class BaseMock(object): |
|
25 |
def __init__(self, netloc): |
|
26 |
self.netloc = netloc |
|
27 |
self.clear() |
|
28 | ||
29 |
def clear(self): |
|
30 |
self.requests = [] |
|
31 |
self.responses = [] |
|
32 | ||
33 |
def next_response(self): |
|
34 |
response, self.responses = self.responses[0], self.responses[1:] |
|
35 |
return response |
|
36 | ||
37 |
@property |
|
38 |
def ctx_manager(self): |
|
39 |
'''Create an HTTMock context manager for all endpoints of a mocked Maarch instance''' |
|
40 |
endpoints = [] |
|
41 |
for attribute, value in self.__class__.__dict__.items(): |
|
42 |
if hasattr(value, 'path'): |
|
43 |
value = getattr(self, attribute) |
|
44 |
match_decorator = urlmatch(netloc=self.netloc, path=value.path) |
|
45 |
print value, self.netloc, '^/rest' + value.path |
|
46 |
endpoints.append(match_decorator(value)) |
|
47 |
return HTTMock(*endpoints) |
|
48 | ||
49 | ||
50 |
class MaarchMock(BaseMock): |
|
51 |
def list_endpoint(self, url, request): |
|
52 |
self.requests.append(('list_endpoint', url, request, json.loads(request.body))) |
|
53 |
return { |
|
54 |
'content': json.dumps(self.next_response()), |
|
55 |
'headers': { |
|
56 |
'content-type': 'application/json', |
|
57 |
}, |
|
58 |
} |
|
59 |
list_endpoint.path = '^/rest/res/list$' |
|
60 | ||
61 |
def update_external_infos(self, url, request): |
|
62 |
self.requests.append(('update_external_infos', url, request, json.loads(request.body))) |
|
63 |
return json.dumps({}) |
|
64 |
update_external_infos.path = '^/rest/res/externalInfos$' |
|
65 | ||
66 |
def update_status(self, url, request): |
|
67 |
self.requests.append(('update_status', url, request, json.loads(request.body))) |
|
68 |
return { |
|
69 |
'content': json.dumps(self.next_response()), |
|
70 |
'headers': { |
|
71 |
'content-type': 'application/json', |
|
72 |
}, |
|
73 |
} |
|
74 |
update_status.path = '^/rest/res/resource/status$' |
|
75 | ||
76 |
def post_courrier(self, url, request): |
|
77 |
self.requests.append(('post_courrier', url, request, json.loads(request.body))) |
|
78 |
post_courrier.path = '^/rest/res$' |
|
79 | ||
80 | ||
81 |
@pytest.fixture |
|
82 |
def maarch(settings, mail_group): |
|
83 |
# configure maarch server |
|
84 |
settings.MAARCH_FEED = { |
|
85 |
'ENABLE': True, |
|
86 |
'URL': 'http://maarch.example.net/', |
|
87 |
'USERNAME': 'admin', |
|
88 |
'PASSWORD': 'admin', |
|
89 |
} |
|
90 |
return MaarchMock('maarch.example.net') |
|
91 | ||
92 | ||
93 |
class WcsMock(BaseMock): |
|
94 |
def api_formdefs(self, url, request): |
|
95 |
return json.dumps({ |
|
96 |
'data': [{ |
|
97 |
'slug': 'slug1', |
|
98 |
'title': 'title1', |
|
99 |
}] |
|
100 |
}) |
|
101 |
api_formdefs.path = '^/api/formdefs/$' |
|
102 | ||
103 |
def json(self, url, request): |
|
104 |
return json.dumps({ |
|
105 |
'data': [{ |
|
106 |
'slug': 'slug1', |
|
107 |
'title': 'title1', |
|
108 |
'category': 'category1', |
|
109 |
}] |
|
110 |
}) |
|
111 |
json.path = '^/json$' |
|
112 | ||
113 |
def api_formdefs_slug1_schema(self, url, request): |
|
114 |
return json.dumps({ |
|
115 |
}) |
|
116 |
api_formdefs_slug1_schema.path = '^/api/formdefs/slug-1/schema$' |
|
117 | ||
118 |
def api_formdefs_slug1_submit(self, url, request): |
|
119 |
return json.dumps({ |
|
120 |
'err': 0, |
|
121 |
'data': { |
|
122 |
'id': 1, |
|
123 |
'backoffice_url': 'http://wcs.example.net/slug-1/1', |
|
124 |
}, |
|
125 |
}) |
|
126 |
api_formdefs_slug1_submit.path = '^/api/formdefs/slug-1/submit$' |
|
127 | ||
128 | ||
129 |
@pytest.fixture |
|
130 |
def wcs(settings): |
|
131 |
settings.KNOWN_SERVICES = { |
|
132 |
'wcs': { |
|
133 |
'demarches': { |
|
134 |
'url': 'http://wcs.example.net/', |
|
135 |
} |
|
136 |
} |
|
137 |
} |
|
138 |
return WcsMock('wcs.example.net') |
|
139 | ||
140 | ||
141 |
def test_utils(maarch): |
|
142 |
from welco.sources.mail.utils import get_maarch |
|
143 | ||
144 |
welco_maarch_obj = get_maarch() |
|
145 |
assert welco_maarch_obj.url == 'http://maarch.example.net/' |
|
146 |
assert welco_maarch_obj.username == 'admin' |
|
147 |
assert welco_maarch_obj.password == 'admin' |
|
148 |
assert welco_maarch_obj.grc_status == 'GRC' |
|
149 |
assert welco_maarch_obj.grc_received_status == 'GRC_TRT' |
|
150 |
assert welco_maarch_obj.grc_send_status == 'GRCSENT' |
|
151 |
assert welco_maarch_obj.grc_refused_status == 'GRCREFUSED' |
|
152 | ||
153 | ||
154 |
PDF_MOCK = '%PDF-1.4 ...' |
|
155 | ||
156 | ||
157 |
def test_feed(app, maarch, wcs, user): |
|
158 |
import base64 |
|
159 |
from django.core.management import call_command |
|
160 |
from django.contrib.contenttypes.models import ContentType |
|
161 |
from welco.sources.mail.models import Mail |
|
162 | ||
163 |
app.set_user(user.username) |
|
164 |
response = app.get('/').follow() |
|
165 |
# no mail |
|
166 |
assert len(response.pyquery('li[data-registered-mail-number]')) == 0 |
|
167 | ||
168 |
# feed mails from maarch |
|
169 |
with maarch.ctx_manager: |
|
170 |
# list request |
|
171 |
maarch.responses.append({ |
|
172 |
'resources': [ |
|
173 |
{ |
|
174 |
'res_id': 1, |
|
175 |
'fileBase64Content': base64.b64encode(PDF_MOCK), |
|
176 |
} |
|
177 |
], |
|
178 |
}) |
|
179 |
# update status request |
|
180 |
maarch.responses.append({}) |
|
181 |
# last list request |
|
182 |
maarch.responses.append({'resources': []}) |
|
183 |
call_command('feed_mail_maarch') |
|
184 |
assert len(maarch.requests) == 3 |
|
185 |
assert maarch.requests[0][3] == { |
|
186 |
'select': '*', |
|
187 |
'clause': "status='GRC'", |
|
188 |
'withFile': True, |
|
189 |
'orderBy': ['res_id'], |
|
190 |
'limit': 10, |
|
191 |
} |
|
192 |
assert maarch.requests[1][3] == { |
|
193 |
'resId': [1], |
|
194 |
'status': 'GRC_TRT', |
|
195 |
} |
|
196 |
assert maarch.requests[2][3] == { |
|
197 |
'select': '*', |
|
198 |
'clause': "status='GRC'", |
|
199 |
'withFile': True, |
|
200 |
'orderBy': ['res_id'], |
|
201 |
'limit': 10, |
|
202 |
} |
|
203 |
response = app.get('/').follow() |
|
204 | ||
205 |
# new mail is visible |
|
206 |
assert len(response.pyquery('li[data-registered-mail-number]')) == 1 |
|
207 |
assert len(response.pyquery('li[data-registered-mail-number=maarch-1]')) == 1 |
|
208 | ||
209 |
# start qualification |
|
210 |
maarch.clear() |
|
211 |
pk = Mail.objects.get().pk |
|
212 |
with wcs.ctx_manager, maarch.ctx_manager: |
|
213 |
source_type = str(ContentType.objects.get_for_model(Mail).pk), |
|
214 |
source_pk = str(pk) |
|
215 | ||
216 |
response = app.get('/ajax/qualification', params={ |
|
217 |
'source_type': source_type, |
|
218 |
'source_pk': source_pk, |
|
219 |
}) |
|
220 | ||
221 |
assert len(response.pyquery('a[data-association-pk]')) == 0 |
|
222 |
response = app.post('/ajax/qualification', params={ |
|
223 |
'source_type': source_type, |
|
224 |
'source_pk': str(pk), |
|
225 |
'formdef_reference': 'demarches:slug-1', |
|
226 |
}) |
|
227 | ||
228 |
# verify qualification was done |
|
229 |
assert len(response.pyquery('a[data-association-pk]')) == 1 |
|
230 |
association_pk = response.pyquery('a[data-association-pk]')[0].attrib['data-association-pk'] |
|
231 | ||
232 |
response = app.post('/ajax/create-formdata/%s' % association_pk) |
|
233 |
assert len(maarch.requests) == 1 |
|
234 |
assert maarch.requests[0][3] == { |
|
235 |
'status': 'GRCSENT', |
|
236 |
'externalInfos': [ |
|
237 |
{ |
|
238 |
'external_id': 1, |
|
239 |
'external_link': 'http://wcs.example.net/slug-1/1', |
|
240 |
'res_id': 1, |
|
241 |
} |
|
242 |
] |
|
243 |
} |
tests/test_source_phone.py | ||
---|---|---|
28 | 28 |
pytestmark = pytest.mark.django_db |
29 | 29 | |
30 | 30 | |
31 |
@pytest.fixture |
|
32 |
def user(): |
|
33 |
from django.contrib.auth.models import User |
|
34 | ||
35 |
user = User.objects.create(username='toto') |
|
36 |
user.set_password('toto') |
|
37 |
user.save() |
|
38 |
return user |
|
39 | ||
40 | ||
41 | 31 |
def test_call_start_stop(client): |
42 | 32 |
assert models.PhoneCall.objects.count() == 0 |
43 | 33 |
payload = { |
welco/sources/mail/__init__.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
import django.apps |
18 | 18 | |
19 | ||
19 | 20 |
class AppConfig(django.apps.AppConfig): |
20 | 21 |
name = 'welco.sources.mail' |
21 | 22 | |
... | ... | |
23 | 24 |
from . import urls |
24 | 25 |
return urls.urlpatterns |
25 | 26 | |
27 |
def ready(self): |
|
28 |
from welco.qualif.models import Association |
|
29 |
from django.db.models import signals |
|
30 | ||
31 |
signals.post_save.connect(self.association_post_save, |
|
32 |
sender=Association) |
|
33 | ||
34 |
def association_post_save(self, sender, instance, **kwargs): |
|
35 |
from .utils import get_maarch |
|
36 | ||
37 |
if not instance.formdata_id: |
|
38 |
return |
|
39 |
source = instance.source |
|
40 |
if not hasattr(source, 'registered_mail_number'): |
|
41 |
return |
|
42 |
registered_mail_number = source.registered_mail_number |
|
43 |
if not registered_mail_number.startswith('maarch-'): |
|
44 |
return |
|
45 |
maarch_pk = int(registered_mail_number.split('-', 1)[-1]) |
|
46 |
maarch = get_maarch() |
|
47 |
maarch.set_grc_sent_status( |
|
48 |
mail_pk=maarch_pk, |
|
49 |
formdata_id=instance.formdata_id, |
|
50 |
formdata_url_backoffice=instance.formdata_url_backoffice) |
|
51 | ||
26 | 52 |
default_app_config = 'welco.sources.mail.AppConfig' |
welco/sources/mail/maarch.py | ||
---|---|---|
1 |
# welco - multichannel request processing |
|
2 |
# Copyright (C) 2015 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 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 Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import urlparse |
|
18 |
import base64 |
|
19 | ||
20 |
from dateutil.parser import parse as parse_datetime |
|
21 | ||
22 |
import requests |
|
23 |
from requests.adapters import HTTPAdapter |
|
24 |
from requests.packages.urllib3.util.retry import Retry |
|
25 | ||
26 | ||
27 |
class MaarchError(Exception): |
|
28 |
pass |
|
29 | ||
30 | ||
31 |
class MaarchCourrier(object): |
|
32 |
url = None |
|
33 |
username = None |
|
34 |
password = None |
|
35 |
default_limit = 100 |
|
36 |
max_retries = 3 |
|
37 | ||
38 |
def __init__(self, url, username, password): |
|
39 |
self.url = url |
|
40 |
self.username = username |
|
41 |
self.password = password |
|
42 | ||
43 |
def __repr__(self): |
|
44 |
return '<MaarchCourrier url:%s>' % self.url |
|
45 | ||
46 |
class Courrier(object): |
|
47 |
content = None |
|
48 |
format = None |
|
49 |
status = None |
|
50 | ||
51 |
def __init__(self, maarch_courrier, **kwargs): |
|
52 |
self.maarch_courrier = maarch_courrier |
|
53 |
self.pk = kwargs.pop('res_id', None) |
|
54 |
# decode file content |
|
55 |
if 'fileBase64Content' in kwargs: |
|
56 |
kwargs['content'] = base64.b64decode(kwargs.pop('fileBase64Content')) |
|
57 |
# decode date fields |
|
58 |
for key in kwargs: |
|
59 |
if key.endswith('_date') and kwargs[key]: |
|
60 |
kwargs[key] = parse_datetime(kwargs[key]) |
|
61 |
self.__dict__.update(kwargs) |
|
62 | ||
63 |
def __repr__(self): |
|
64 |
descriptions = [] |
|
65 |
for key in ['pk', 'status']: |
|
66 |
if getattr(self, key, None): |
|
67 |
descriptions.append('%s:%s' % (key, getattr(self, key))) |
|
68 |
return '<Courrier %s>' % ' '.join(descriptions) |
|
69 | ||
70 |
@classmethod |
|
71 |
def new_with_file(cls, maarch_courrier, content, format, status, **kwargs): |
|
72 |
if hasattr(content, 'read'): |
|
73 |
content = content.read() |
|
74 |
else: |
|
75 |
content = content |
|
76 |
return cls(maarch_courrier, content=content, format=format, status=status, **kwargs) |
|
77 | ||
78 |
def post_serialize(self): |
|
79 |
payload = {} |
|
80 |
assert self.content |
|
81 |
assert self.status |
|
82 |
payload['encodedFile'] = base64.b64encode(self.content) |
|
83 |
payload['collId'] = 'letterbox_coll' |
|
84 |
payload['table'] = 'res_letterbox' |
|
85 |
payload['fileFormat'] = self.fileFormat |
|
86 |
payload['data'] = d = [] |
|
87 |
excluded_keys = ['content', 'format', 'status', 'maarch_courrier', 'pk'] |
|
88 |
data = {key: self.__dict__[key] for key in self.__dict__ if key not in excluded_keys} |
|
89 |
if data: |
|
90 |
for key, value in data.iteritems(): |
|
91 |
if isinstance(value, basestring): |
|
92 |
d.append({'column': key, 'value': value, 'type': 'string'}) |
|
93 |
elif isinstance(value, int): |
|
94 |
d.append({'column': key, 'value': str(value), 'type': 'int'}) |
|
95 |
else: |
|
96 |
raise NotImplementedError |
|
97 |
payload['status'] = self.status |
|
98 |
return payload |
|
99 | ||
100 |
def get_serialize(self): |
|
101 |
d = {'res_id': self.pk} |
|
102 |
for key in self.__dict__: |
|
103 |
if key in ['pk', 'maarch_courrier']: |
|
104 |
continue |
|
105 |
value = getattr(self, key) |
|
106 |
if key == 'content': |
|
107 |
value = base64.b64encode(value) |
|
108 |
key = 'fileBase64Content' |
|
109 |
if key.endswith('_date'): |
|
110 |
value = value.isoformat() |
|
111 |
d[key] = value |
|
112 |
return d |
|
113 | ||
114 |
def new_courrier_with_file(self, content, format, status, **kwargs): |
|
115 |
return self.Courrier.new_with_file(self, content, format, status, **kwargs) |
|
116 | ||
117 |
@property |
|
118 |
def session(self): |
|
119 |
s = requests.Session() |
|
120 |
if self.username and self.password: |
|
121 |
s.auth = (self.username, self.password) |
|
122 |
retry = Retry( |
|
123 |
total=self.max_retries, |
|
124 |
read=self.max_retries, |
|
125 |
connect=self.max_retries, |
|
126 |
backoff_factor=0.5, |
|
127 |
status_forcelist=(500, 502, 504) |
|
128 |
) |
|
129 |
adapter = HTTPAdapter(max_retries=retry) |
|
130 |
s.mount('http://', adapter) |
|
131 |
s.mount('https://', adapter) |
|
132 |
return s |
|
133 | ||
134 |
def post_json(self, url, payload, verb='post'): |
|
135 |
try: |
|
136 |
method = getattr(self.session, verb) |
|
137 |
response = method(url, json=payload) |
|
138 |
except requests.RequestException as e: |
|
139 |
raise MaarchError('HTTP request to maarch failed', e, payload) |
|
140 |
try: |
|
141 |
response.raise_for_status() |
|
142 |
except requests.RequestException as e: |
|
143 |
raise MaarchError('HTTP request to maarch failed', e, payload, repr(response.content[:1000])) |
|
144 |
try: |
|
145 |
response_payload = response.json() |
|
146 |
except ValueError: |
|
147 |
raise MaarchError('maarch returned non-JSON data', repr(response.content[:1000]), payload) |
|
148 |
return response_payload |
|
149 | ||
150 |
def put_json(self, url, payload): |
|
151 |
return self.post_json(url, payload, verb='put') |
|
152 | ||
153 |
@property |
|
154 |
def list_url(self): |
|
155 |
return urlparse.urljoin(self.url, 'rest/res/list') |
|
156 | ||
157 |
@property |
|
158 |
def update_external_infos_url(self): |
|
159 |
return urlparse.urljoin(self.url, 'rest/res/externalInfos') |
|
160 | ||
161 |
@property |
|
162 |
def update_status_url(self): |
|
163 |
return urlparse.urljoin(self.url, 'rest/res/resource/status') |
|
164 | ||
165 |
@property |
|
166 |
def post_courrier_url(self): |
|
167 |
return urlparse.urljoin(self.url, 'rest/res') |
|
168 | ||
169 |
def get_courriers(self, clause, fields=None, limit=None, include_file=False, order_by=None): |
|
170 |
if fields: |
|
171 |
# res_id is mandatory |
|
172 |
fields = set(fields) |
|
173 |
fields.add('res_id') |
|
174 |
fields = ','.join(fields) if fields else '*' |
|
175 |
limit = limit or self.default_limit |
|
176 |
order_by = order_by or [] |
|
177 |
response = self.post_json(self.list_url, { |
|
178 |
'select': fields, |
|
179 |
'clause': clause, |
|
180 |
'limit': limit, |
|
181 |
'withFile': include_file, |
|
182 |
'orderBy': order_by, |
|
183 |
}) |
|
184 |
if not hasattr(response.get('resources'), 'append'): |
|
185 |
raise MaarchError('missing resources field or bad type', response) |
|
186 |
return [self.Courrier(self, **resource) for resource in response['resources']] |
|
187 | ||
188 |
def update_external_infos(self, courriers, status): |
|
189 |
if not courriers: |
|
190 |
return |
|
191 |
external_infos = [] |
|
192 |
payload = { |
|
193 |
'externalInfos': external_infos, |
|
194 |
'status': status, |
|
195 |
} |
|
196 |
for courrier in courriers: |
|
197 |
assert courrier.pk, 'courrier must already exist in Maarch and have a pk' |
|
198 |
external_info = {'res_id': courrier.pk} |
|
199 |
if getattr(courrier, 'external_id', None): |
|
200 |
external_info['external_id'] = courrier.external_id |
|
201 |
if getattr(courrier, 'external_link', None): |
|
202 |
external_info['external_link'] = courrier.external_link |
|
203 |
external_infos.append(external_info) |
|
204 |
response = self.put_json(self.update_external_infos_url, payload) |
|
205 |
if 'errors' in response: |
|
206 |
raise MaarchError('update_external_infos failed with errors', response['errors'], response) |
|
207 | ||
208 |
def update_status(self, courriers, status, history_message=None): |
|
209 |
if not courriers: |
|
210 |
return |
|
211 |
res_ids = [] |
|
212 |
for courrier in courriers: |
|
213 |
assert courrier.pk |
|
214 |
res_ids.append(courrier.pk) |
|
215 |
payload = { |
|
216 |
'status': status, |
|
217 |
'resId': res_ids, |
|
218 |
} |
|
219 |
if history_message: |
|
220 |
payload['historyMessage'] = history_message |
|
221 |
response = self.put_json(self.update_status_url, payload) |
|
222 | ||
223 |
if 'errors' in response: |
|
224 |
raise MaarchError('update_status failed with errors', response['errors'], response) |
|
225 | ||
226 |
def post_courrier(self, courrier): |
|
227 |
response = self.post_json(self.post_courrier_url, courrier.post_serialize()) |
|
228 |
if 'errors' in response: |
|
229 |
raise MaarchError('update_external_infos failed with errors', response['errors'], response) |
|
230 |
if 'resId' not in response: |
|
231 |
raise MaarchError('update_external_infos failed with errors, missing resId', response) |
|
232 |
courrier.pk = response['resId'] |
|
233 |
return courrier |
welco/sources/mail/management/commands/feed_mail_maarch.py | ||
---|---|---|
1 |
# welco - multichannel request processing |
|
2 |
# Copyright (C) 2015 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 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 Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from optparse import make_option |
|
18 |
import os |
|
19 | ||
20 |
from django.core.files.base import ContentFile |
|
21 |
from django.core.management.base import BaseCommand, CommandError |
|
22 |
from django.conf import settings |
|
23 |
from django.db import transaction |
|
24 | ||
25 |
from ...models import Mail |
|
26 |
from ...utils import get_maarch |
|
27 | ||
28 |
class Command(BaseCommand): |
|
29 |
"""Inject mail coming from Maarch into welco. |
|
30 | ||
31 |
Only mail with a status "GRC" are injected, |
|
32 |
After injection, their status is immediately changed to "GRC_TRT". |
|
33 |
After injection in w.c.s., their status is changed to "GRCSENT" and an |
|
34 |
id and an URL of the request in w.c.s. is attached to the mail in |
|
35 |
Maarch. |
|
36 |
""" |
|
37 | ||
38 |
def handle(self, *args, **kwargs): |
|
39 |
verbosity = kwargs['verbosity'] |
|
40 |
maarch = get_maarch() |
|
41 | ||
42 |
maarch_mails = maarch.get_mails() |
|
43 |
count = 0 |
|
44 |
while maarch_mails: |
|
45 |
with transaction.atomic(): |
|
46 |
for maarch_mail in maarch_mails: |
|
47 |
Mail.objects.create( |
|
48 |
content=ContentFile(maarch_mail.content, name='maarch-%s' % maarch_mail.pk), |
|
49 |
registered_mail_number='maarch-%s' % str(maarch_mail.pk), # res_id |
|
50 |
) |
|
51 |
# update maarch inside transaction, if it fails all imports will be |
|
52 |
# rollbacked |
|
53 |
maarch.set_grc_received_status(maarch_mails) |
|
54 |
count += len(maarch_mails) |
|
55 |
maarch_mails = maarch.get_mails() |
|
56 |
if verbosity > 1: |
|
57 |
self.stdout.write('Injected %d mails from %s.' % (count, maarch.url)) |
welco/sources/mail/utils.py | ||
---|---|---|
1 |
# welco - multichannel request processing |
|
2 |
# Copyright (C) 2015 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 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 Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from django.conf import settings |
|
18 | ||
19 |
from .maarch import MaarchCourrier |
|
20 | ||
21 | ||
22 |
class WelcoMaarchCourrier(MaarchCourrier): |
|
23 |
def __init__(self, url, username, password, grc_status, |
|
24 |
grc_received_status, grc_send_status, grc_refused_status, |
|
25 |
batch_size=10): |
|
26 |
super(WelcoMaarchCourrier, self).__init__(url, username, password) |
|
27 |
self.grc_status = grc_status |
|
28 |
self.grc_received_status = grc_received_status |
|
29 |
self.grc_send_status = grc_send_status |
|
30 |
self.grc_refused_status = grc_refused_status |
|
31 |
self.batch_size = batch_size |
|
32 | ||
33 |
def get_mails(self): |
|
34 |
return self.get_courriers( |
|
35 |
clause="status='%s'" % self.grc_status, |
|
36 |
include_file=True, |
|
37 |
order_by=['res_id'], |
|
38 |
limit=self.batch_size) |
|
39 | ||
40 |
def get_mail(self, mail_id): |
|
41 |
return self.get_courriers(clause="res_id=%s" % mail_id)[0] |
|
42 | ||
43 |
def set_grc_received_status(self, mails): |
|
44 |
self.update_status(mails, self.grc_received_status) |
|
45 | ||
46 |
def set_grc_sent_status(self, mail_pk, formdata_id, formdata_url_backoffice): |
|
47 |
mail = self.Courrier(self, pk=mail_pk) |
|
48 |
mail.external_id = formdata_id |
|
49 |
mail.external_link = formdata_url_backoffice |
|
50 |
self.update_external_infos([mail], self.grc_send_status) |
|
51 | ||
52 |
def set_grc_refused_status(self, mail_pk): |
|
53 |
mail = self.Courrier(self, pk=mail_pk) |
|
54 |
self.update_status([mail], self.grc_refused_status) |
|
55 | ||
56 | ||
57 |
def get_maarch(): |
|
58 |
config = getattr(settings, 'MAARCH_FEED', {}) |
|
59 |
if not config.get('ENABLE'): |
|
60 |
return |
|
61 |
url = config['URL'] |
|
62 |
username = config['USERNAME'] |
|
63 |
password = config['PASSWORD'] |
|
64 |
return WelcoMaarchCourrier( |
|
65 |
url=url, |
|
66 |
username=username, |
|
67 |
password=password, |
|
68 |
grc_status=config.get('STATUS_GRC', 'GRC'), |
|
69 |
grc_received_status=config.get('STATUS_RECEIVED', 'GRC_TRT'), |
|
70 |
grc_send_status=config.get('STATUS_SEND', 'GRCSENT'), |
|
71 |
grc_refused_status=config.get('STATUS_REFUSED', 'GRCREFUSED')) |
|
72 |
welco/sources/mail/views.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
import json |
18 |
import logging |
|
18 | 19 | |
19 | 20 |
from django import template |
20 | 21 |
from django.contrib.auth.decorators import login_required |
... | ... | |
26 | 27 |
from django.utils.translation import ugettext_lazy as _ |
27 | 28 |
from django.views.decorators.csrf import csrf_exempt |
28 | 29 |
from django.views.generic import TemplateView |
30 |
from django.db.transaction import atomic |
|
29 | 31 | |
30 | 32 |
from welco.utils import response_for_json |
31 | 33 | |
32 | 34 |
from .models import Mail |
33 | 35 |
from .forms import MailQualificationForm |
36 |
from .utils import get_maarch |
|
37 | ||
38 | ||
39 |
logger = logging.getLogger(__name__) |
|
34 | 40 | |
35 | 41 |
def viewer(request, *args, **kwargs): |
36 | 42 |
if not 'file' in request.GET: |
... | ... | |
124 | 130 |
@login_required |
125 | 131 |
@csrf_exempt |
126 | 132 |
def reject(request, *args, **kwargs): |
127 |
Mail.objects.filter(id=request.POST['source_pk']).delete() |
|
133 |
maarch = get_maarch() |
|
134 |
mail = Mail.objects.filter(id=request.POST['source_pk']).first() |
|
135 |
if mail: |
|
136 |
try: |
|
137 |
with atomic(): |
|
138 |
if maarch and mail.registered_mail_number and mail.registered_mail_number.startswith('maarch-'): |
|
139 |
mail_pk = mail.registered_mail_number.split('-', 1)[1] |
|
140 |
maarch.set_grc_refused_status(mail_pk) |
|
141 |
mail.delete() |
|
142 |
except Exception: |
|
143 |
logger.exception('rejection request to maarch failed') |
|
144 |
messages.error(request, _('Rejection request to Maarch failed')) |
|
128 | 145 |
return HttpResponse() |
129 | 146 | |
130 | 147 | |
131 |
- |