trivial: apply black

This commit is contained in:
Frédéric Péters 2021-01-11 20:10:12 +01:00
parent 54aae08cfe
commit ce7f2dd500
44 changed files with 571 additions and 454 deletions

6
debian/settings.py vendored
View File

@ -16,15 +16,15 @@
DEBUG = False DEBUG = False
TEMPLATE_DEBUG = False TEMPLATE_DEBUG = False
#ADMINS = ( # ADMINS = (
# # ('User 1', 'watchdog@example.net'), # # ('User 1', 'watchdog@example.net'),
# # ('User 2', 'janitor@example.net'), # # ('User 2', 'janitor@example.net'),
#) # )
# ALLOWED_HOSTS must be correct in production! # ALLOWED_HOSTS must be correct in production!
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts # See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
ALLOWED_HOSTS = [ ALLOWED_HOSTS = [
'*', '*',
] ]
# Databases # Databases

View File

@ -25,16 +25,18 @@ class eo_sdist(sdist):
def get_version(): def get_version():
'''Use the VERSION, if absent generates a version with git describe, if not """Use the VERSION, if absent generates a version with git describe, if not
tag exists, take 0.0- and add the length of the commit log. tag exists, take 0.0- and add the length of the commit log.
''' """
if os.path.exists('VERSION'): if os.path.exists('VERSION'):
with open('VERSION', 'r') as v: with open('VERSION', 'r') as v:
return v.read() return v.read()
if os.path.exists('.git'): if os.path.exists('.git'):
p = subprocess.Popen( p = subprocess.Popen(
['git', 'describe', '--dirty=.dirty', '--match=v*'], ['git', 'describe', '--dirty=.dirty', '--match=v*'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
result = p.communicate()[0] result = p.communicate()[0]
if p.returncode == 0: if p.returncode == 0:
result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v
@ -45,9 +47,7 @@ def get_version():
version = result version = result
return version return version
else: else:
return '0.0.post%s' % len( return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines())
subprocess.check_output(
['git', 'rev-list', 'HEAD']).splitlines())
return '0.0' return '0.0'
@ -65,6 +65,7 @@ class compile_translations(Command):
curdir = os.getcwd() curdir = os.getcwd()
try: try:
from django.core.management import call_command from django.core.management import call_command
for path, dirs, files in os.walk('welco'): for path, dirs, files in os.walk('welco'):
if 'locale' not in dirs: if 'locale' not in dirs:
continue continue
@ -108,7 +109,8 @@ setup(
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2',
], ],
install_requires=['django>=1.11,<2.3', install_requires=[
'django>=1.11,<2.3',
'gadjo', 'gadjo',
'django-ckeditor<4.5.4', 'django-ckeditor<4.5.4',
'django-haystack<2.8', 'django-haystack<2.8',
@ -118,7 +120,7 @@ setup(
'whoosh', 'whoosh',
'XStatic-Select2', 'XStatic-Select2',
'python-dateutil', 'python-dateutil',
], ],
zip_safe=False, zip_safe=False,
cmdclass={ cmdclass={
'build': build, 'build': build,

View File

@ -29,14 +29,9 @@ def test_get_contacts_zone_view(app, db):
resp = app.get('/ajax/contacts', status=200) resp = app.get('/ajax/contacts', status=200)
assert resp.html.find('button')['data-url'] == '/contacts/add/' assert resp.html.find('button')['data-url'] == '/contacts/add/'
mail = Mail.objects.create( mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt'), contact_id='42')
content=ContentFile('foo', name='bar.txt'),
contact_id='42')
source_type = ContentType.objects.get_for_model(Mail).pk source_type = ContentType.objects.get_for_model(Mail).pk
resp = app.get( resp = app.get('/ajax/contacts', params={'source_type': source_type, 'source_pk': mail.pk}, status=200)
'/ajax/contacts',
params={'source_type': source_type, 'source_pk': mail.pk},
status=200)
assert resp.html.find('a').text == '...' assert resp.html.find('a').text == '...'
assert resp.html.find('a')['data-page-slug'] == '42' assert resp.html.find('a')['data-page-slug'] == '42'
@ -46,9 +41,8 @@ def test_post_contacts_zone_view(app, db):
assert not mail.contact_id assert not mail.contact_id
source_type = ContentType.objects.get_for_model(Mail).pk source_type = ContentType.objects.get_for_model(Mail).pk
resp = app.post( resp = app.post(
'/ajax/contacts', '/ajax/contacts', params={'source_type': source_type, 'source_pk': mail.pk, 'user_id': 42}, status=200
params={'source_type': source_type, 'source_pk': mail.pk, 'user_id': 42}, )
status=200)
assert resp.text == 'ok' assert resp.text == 'ok'
assert Mail.objects.get(id=mail.pk).contact_id == '42' assert Mail.objects.get(id=mail.pk).contact_id == '42'
@ -90,20 +84,24 @@ def test_search_json_view(settings, app, user, mail_group):
def response(url, request): def response(url, request):
headers = {'content-type': 'application/json'} headers = {'content-type': 'application/json'}
content = { content = {
'data': [{ 'data': [
'user_display_name': 'John Doe', {
'user_email': 'john@example.net', 'user_display_name': 'John Doe',
'user_var_phone': '0123456789', 'user_email': 'john@example.net',
'user_var_mobile': '0612345789', 'user_var_phone': '0123456789',
'user_id': '42', 'user_var_mobile': '0612345789',
'user_roles': [{ 'user_id': '42',
'name': 'Agent', 'user_roles': [
'text': 'Agent', {
'slug': 'agent', 'name': 'Agent',
'id': '8d73434814484aa0b8555ac9c68a9300' 'text': 'Agent',
}], 'slug': 'agent',
}], 'id': '8d73434814484aa0b8555ac9c68a9300',
'err': 0 }
],
}
],
'err': 0,
} }
return httmock.response(200, content, headers) return httmock.response(200, content, headers)
@ -123,6 +121,7 @@ def test_contact_detail_fragment_view(settings, app, db):
} }
} }
} }
@httmock.urlmatch(netloc='wcs.example.net', path='/api/users/42/', method='GET') @httmock.urlmatch(netloc='wcs.example.net', path='/api/users/42/', method='GET')
def response(url, request): def response(url, request):
headers = {'content-type': 'application/json'} headers = {'content-type': 'application/json'}
@ -132,12 +131,9 @@ def test_contact_detail_fragment_view(settings, app, db):
'user_var_phone': '0123456789', 'user_var_phone': '0123456789',
'user_var_mobile': '0612345789', 'user_var_mobile': '0612345789',
'user_id': '42', 'user_id': '42',
'user_roles': [{ 'user_roles': [
'name': 'Agent', {'name': 'Agent', 'text': 'Agent', 'slug': 'agent', 'id': '8d73434814484aa0b8555ac9c68a9300'}
'text': 'Agent', ],
'slug': 'agent',
'id': '8d73434814484aa0b8555ac9c68a9300'
}],
} }
return httmock.response(200, content, headers) return httmock.response(200, content, headers)
@ -148,15 +144,12 @@ def test_contact_detail_fragment_view(settings, app, db):
assert resp.html.find('li').text == 'Phone: 0123456789' assert resp.html.find('li').text == 'Phone: 0123456789'
# unused 'is_pinned_user' context # unused 'is_pinned_user' context
mail = Mail.objects.create( mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt'), contact_id='42')
content=ContentFile('foo', name='bar.txt'),
contact_id='42')
source_type = ContentType.objects.get_for_model(Mail).pk source_type = ContentType.objects.get_for_model(Mail).pk
with httmock.HTTMock(response): with httmock.HTTMock(response):
resp = app.get( resp = app.get(
'/ajax/contacts/42/', '/ajax/contacts/42/', params={'source_type': source_type, 'source_pk': mail.pk}, status=200
params={'source_type': source_type, 'source_pk': mail.pk}, )
status=200)
assert resp.html.find('h3').text == 'John Doe' assert resp.html.find('h3').text == 'John Doe'
@ -183,7 +176,7 @@ def test_post_contact_add_view(mocked_sleep, settings, app, db):
'orig': 'http://welco.example.net/', 'orig': 'http://welco.example.net/',
'secret': 'xxx', 'secret': 'xxx',
} }
} },
} }
# normal case # normal case
@ -210,7 +203,8 @@ def test_post_contact_add_view(mocked_sleep, settings, app, db):
'first_name': 'John', 'first_name': 'John',
'last_name': 'Doe', 'last_name': 'Doe',
}, },
status=200) status=200,
)
assert resp.content_type == 'application/json' assert resp.content_type == 'application/json'
assert resp.json['data']['user_id'] == '43' assert resp.json['data']['user_id'] == '43'

View File

@ -46,10 +46,7 @@ def test_get_feeder_view(app, user):
def test_post_feeder_view(app, user): def test_post_feeder_view(app, user):
app.set_user(user.username) app.set_user(user.username)
resp = app.post( resp = app.post('/mail/feeder/', params={'mail': Upload('filename.txt', b'contents')}, status=302)
'/mail/feeder/',
params={'mail': Upload('filename.txt', b'contents')},
status=302)
assert resp.location == '/mail/feeder/' assert resp.location == '/mail/feeder/'
resp = resp.follow() resp = resp.follow()
assert resp.html.find('li', {'class': 'info'}).text == '1 files uploaded successfully.' assert resp.html.find('li', {'class': 'info'}).text == '1 files uploaded successfully.'
@ -63,27 +60,28 @@ def test_qualification_save_view(settings, app, db):
} }
} }
} }
mail = Mail.objects.create( mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt'), subject='spam')
content=ContentFile('foo', name='bar.txt'),
subject='spam')
assert not mail.contact_id assert not mail.contact_id
source_type = ContentType.objects.get_for_model(Mail).pk source_type = ContentType.objects.get_for_model(Mail).pk
resp = app.post( resp = app.post(
'/ajax/qualification-mail-save', '/ajax/qualification-mail-save',
params={'source_type': source_type, 'source_pk': mail.pk, 'subject': 'eggs'}, params={'source_type': source_type, 'source_pk': mail.pk, 'subject': 'eggs'},
status=302) status=302,
assert resp.location == '/ajax/qualification?source_type=%s&source_pk=%s' % ( )
source_type, mail.pk) assert resp.location == '/ajax/qualification?source_type=%s&source_pk=%s' % (source_type, mail.pk)
@httmock.urlmatch(netloc='wcs.example.net', path='/api/formdefs/', method='GET') @httmock.urlmatch(netloc='wcs.example.net', path='/api/formdefs/', method='GET')
def response_get(url, request): def response_get(url, request):
headers = {'content-type': 'application/json'} headers = {'content-type': 'application/json'}
content = { content = {
"err": 0, "err": 0,
"data": [{ "data": [
"title": "Foo", {
"slug": "foo", "title": "Foo",
}]} "slug": "foo",
}
],
}
return httmock.response(200, content, headers) return httmock.response(200, content, headers)
with httmock.HTTMock(response_get): with httmock.HTTMock(response_get):
@ -97,9 +95,7 @@ def test_edit_note_view(app, user):
assert resp.location.startswith('/login/?next=') assert resp.location.startswith('/login/?next=')
app.set_user(user.username) app.set_user(user.username)
mail = Mail.objects.create( mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt'), note='spam')
content=ContentFile('foo', name='bar.txt'),
note='spam')
resp = app.get('/ajax/mail/edit-note/', params={'mail': mail.pk}, status=200) resp = app.get('/ajax/mail/edit-note/', params={'mail': mail.pk}, status=200)
assert resp.html.find('h2').text == 'Note' assert resp.html.find('h2').text == 'Note'
assert resp.html.find('textarea', {'name': 'note'}).text == 'spam' assert resp.html.find('textarea', {'name': 'note'}).text == 'spam'
@ -123,7 +119,7 @@ def test_note_view(app, user):
def test_reject_view(settings, app, user): def test_reject_view(settings, app, user):
settings.MAARCH_FEED= { settings.MAARCH_FEED = {
'URL': 'http://maarch.example.net', 'URL': 'http://maarch.example.net',
'ENABLE': True, 'ENABLE': True,
'USERNAME': 'xxx', 'USERNAME': 'xxx',
@ -134,9 +130,7 @@ def test_reject_view(settings, app, user):
assert resp.location.startswith('/login/?next=') assert resp.location.startswith('/login/?next=')
app.set_user(user.username) app.set_user(user.username)
mail = Mail.objects.create( mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt'), external_id='maarch-42')
content=ContentFile('foo', name='bar.txt'),
external_id='maarch-42')
@httmock.urlmatch(netloc='maarch.example.net', path='/rest/res/resource/status', method='PUT') @httmock.urlmatch(netloc='maarch.example.net', path='/rest/res/resource/status', method='PUT')
def response_ok(url, request): def response_ok(url, request):
@ -150,9 +144,7 @@ def test_reject_view(settings, app, user):
assert Mail.objects.count() == 0 assert Mail.objects.count() == 0
# errors # errors
mail = Mail.objects.create( mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt'), external_id='maarch-42')
content=ContentFile('foo', name='bar.txt'),
external_id='maarch-42')
@httmock.urlmatch(netloc='maarch.example.net', path='/rest/res/resource/status', method='PUT') @httmock.urlmatch(netloc='maarch.example.net', path='/rest/res/resource/status', method='PUT')
def response_error1(url, request): def response_error1(url, request):

View File

@ -112,15 +112,11 @@ def test_wcs_summary_view(app, mail_group, user):
mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt')) mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt'))
source_type = ContentType.objects.get_for_model(Mail).pk source_type = ContentType.objects.get_for_model(Mail).pk
resp = app.get( resp = app.get('/ajax/summary/%s/%s/?callback=spam' % (source_type, mail.pk), status=302)
'/ajax/summary/%s/%s/?callback=spam' % (source_type, mail.pk),
status=302)
assert resp.location.startswith('/login/?next=') assert resp.location.startswith('/login/?next=')
app.set_user(user.username) app.set_user(user.username)
resp = app.get( resp = app.get('/ajax/summary/%s/%s/?callback=spam' % (source_type, mail.pk), status=200)
'/ajax/summary/%s/%s/?callback=spam' % (source_type, mail.pk),
status=200)
assert resp.content_type == 'application/javascript' assert resp.content_type == 'application/javascript'
assert 'bar' in resp.text assert 'bar' in resp.text
assert resp.text.startswith('spam({') assert resp.text.startswith('spam({')
@ -130,8 +126,8 @@ def test_remove_association_view(app, mail_group, user):
mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt')) mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt'))
source_type = ContentType.objects.get_for_model(Mail).pk source_type = ContentType.objects.get_for_model(Mail).pk
association = Association.objects.create( association = Association.objects.create(
source_type=ContentType.objects.get(id=source_type), source_type=ContentType.objects.get(id=source_type), source_pk=mail.pk
source_pk=mail.pk) )
assert Association.objects.filter(id=association.pk).count() == 1 assert Association.objects.filter(id=association.pk).count() == 1
resp = app.get('/ajax/remove-association/%s' % association.pk, status=302) resp = app.get('/ajax/remove-association/%s' % association.pk, status=302)
@ -154,8 +150,8 @@ def test_create_formdata_view(settings, app, mail_group, user):
mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt')) mail = Mail.objects.create(content=ContentFile('foo', name='bar.txt'))
source_type = ContentType.objects.get_for_model(Mail).pk source_type = ContentType.objects.get_for_model(Mail).pk
association = Association.objects.create( association = Association.objects.create(
source_type=ContentType.objects.get(id=source_type), source_type=ContentType.objects.get(id=source_type), source_pk=mail.pk
source_pk=mail.pk) )
resp = app.get('/ajax/create-formdata/%s' % association.pk, status=302) resp = app.get('/ajax/create-formdata/%s' % association.pk, status=302)
assert resp.location.startswith('/login/?next=') assert resp.location.startswith('/login/?next=')
@ -185,7 +181,8 @@ def test_create_formdata_view(settings, app, mail_group, user):
'data': { 'data': {
'id': 42, 'id': 42,
'backoffice_url': 'http://example.net', 'backoffice_url': 'http://example.net',
}} },
}
return httmock.response(200, content, headers) return httmock.response(200, content, headers)
with httmock.HTTMock(response_get, response_post): with httmock.HTTMock(response_get, response_post):
@ -204,8 +201,7 @@ def test_menu_json_view(app, user, mail_group, phone_group, counter_group, kb_gr
app.set_user(user.username) app.set_user(user.username)
resp = app.get('/menu.json', status=200) resp = app.get('/menu.json', status=200)
assert resp.content_type == 'application/json' assert resp.content_type == 'application/json'
assert sorted([x['label'] for x in resp.json]) == [ assert sorted([x['label'] for x in resp.json]) == ['Call Center', 'Counter', 'Knowledge Base', 'Mails']
'Call Center', 'Counter', 'Knowledge Base', 'Mails']
resp = app.get('/menu.json?callback=foo', status=200) resp = app.get('/menu.json?callback=foo', status=200)
assert resp.content_type == 'application/javascript' assert resp.content_type == 'application/javascript'

View File

@ -23,27 +23,25 @@ from welco.forms import QualificationForm
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
KNOWN_SERVICES = { KNOWN_SERVICES = {'wcs': {'eservices': {'url': 'http://localhost/', 'title': 'Eservices', 'orig': 'welco'}}}
'wcs': {
'eservices': {
'url': 'http://localhost/',
'title': 'Eservices',
'orig': 'welco'
}
}
}
@mock.patch('welco.utils.requests.get') @mock.patch('welco.utils.requests.get')
def test_get_qualification(mocked_get, client): def test_get_qualification(mocked_get, client):
with override_settings(KNOWN_SERVICES=KNOWN_SERVICES): with override_settings(KNOWN_SERVICES=KNOWN_SERVICES):
forms = mock.Mock() forms = mock.Mock()
forms.json.return_value = {'data': [{'category': 'Test', forms.json.return_value = {
'authentication_required': False, 'data': [
'description': '', {
'title': 'Test form', 'category': 'Test',
'slug': 'test-form'}], 'authentication_required': False,
'err': 0} 'description': '',
'title': 'Test form',
'slug': 'test-form',
}
],
'err': 0,
}
mocked_get.return_value = forms mocked_get.return_value = forms
user = mock.Mock() user = mock.Mock()

View File

@ -59,11 +59,13 @@ class MaarchMock(BaseMock):
}, },
'status_code': 200, 'status_code': 200,
} }
list_endpoint.path = '^/rest/res/list$' list_endpoint.path = '^/rest/res/list$'
def update_external_infos(self, url, request): def update_external_infos(self, url, request):
self.requests.append(('update_external_infos', url, request, json.loads(force_text(request.body)))) self.requests.append(('update_external_infos', url, request, json.loads(force_text(request.body))))
return json.dumps({}) return json.dumps({})
update_external_infos.path = '^/rest/res/externalInfos$' update_external_infos.path = '^/rest/res/externalInfos$'
def update_status(self, url, request): def update_status(self, url, request):
@ -75,10 +77,12 @@ class MaarchMock(BaseMock):
}, },
'status_code': 200, 'status_code': 200,
} }
update_status.path = '^/rest/res/resource/status$' update_status.path = '^/rest/res/resource/status$'
def post_courrier(self, url, request): def post_courrier(self, url, request):
self.requests.append(('post_courrier', url, request, json.loads(force_text(request.body)))) self.requests.append(('post_courrier', url, request, json.loads(force_text(request.body))))
post_courrier.path = '^/rest/res$' post_courrier.path = '^/rest/res$'
@ -96,37 +100,50 @@ def maarch(settings, mail_group):
class WcsMock(BaseMock): class WcsMock(BaseMock):
def api_formdefs(self, url, request): def api_formdefs(self, url, request):
return json.dumps({ return json.dumps(
'data': [{ {
'slug': 'slug1', 'data': [
'title': 'title1', {
}] 'slug': 'slug1',
}) 'title': 'title1',
}
]
}
)
api_formdefs.path = '^/api/formdefs/$' api_formdefs.path = '^/api/formdefs/$'
def json(self, url, request): def json(self, url, request):
return json.dumps({ return json.dumps(
'data': [{ {
'slug': 'slug1', 'data': [
'title': 'title1', {
'category': 'category1', 'slug': 'slug1',
}] 'title': 'title1',
}) 'category': 'category1',
}
]
}
)
json.path = '^/json$' json.path = '^/json$'
def api_formdefs_slug1_schema(self, url, request): def api_formdefs_slug1_schema(self, url, request):
return json.dumps({ return json.dumps({})
})
api_formdefs_slug1_schema.path = '^/api/formdefs/slug-1/schema$' api_formdefs_slug1_schema.path = '^/api/formdefs/slug-1/schema$'
def api_formdefs_slug1_submit(self, url, request): def api_formdefs_slug1_submit(self, url, request):
return json.dumps({ return json.dumps(
'err': 0, {
'data': { 'err': 0,
'id': 1, 'data': {
'backoffice_url': 'http://wcs.example.net/slug-1/1', 'id': 1,
}, 'backoffice_url': 'http://wcs.example.net/slug-1/1',
}) },
}
)
api_formdefs_slug1_submit.path = '^/api/formdefs/slug-1/submit$' api_formdefs_slug1_submit.path = '^/api/formdefs/slug-1/submit$'
@ -172,14 +189,16 @@ def test_feed(settings, app, maarch, wcs, user):
# feed mails from maarch # feed mails from maarch
with maarch.ctx_manager: with maarch.ctx_manager:
# list request # list request
maarch.responses.append({ maarch.responses.append(
'resources': [ {
{ 'resources': [
'res_id': 1, {
'fileBase64Content': force_text(base64.b64encode(PDF_MOCK)), 'res_id': 1,
} 'fileBase64Content': force_text(base64.b64encode(PDF_MOCK)),
], }
}) ],
}
)
# update status request # update status request
maarch.responses.append({}) maarch.responses.append({})
# last list request # last list request
@ -214,20 +233,26 @@ def test_feed(settings, app, maarch, wcs, user):
maarch.clear() maarch.clear()
pk = Mail.objects.get().pk pk = Mail.objects.get().pk
with wcs.ctx_manager, maarch.ctx_manager: with wcs.ctx_manager, maarch.ctx_manager:
source_type = str(ContentType.objects.get_for_model(Mail).pk), source_type = (str(ContentType.objects.get_for_model(Mail).pk),)
source_pk = str(pk) source_pk = str(pk)
response = app.get('/ajax/qualification', params={ response = app.get(
'source_type': source_type, '/ajax/qualification',
'source_pk': source_pk, params={
}) 'source_type': source_type,
'source_pk': source_pk,
},
)
assert len(response.pyquery('a[data-association-pk]')) == 0 assert len(response.pyquery('a[data-association-pk]')) == 0
response = app.post('/ajax/qualification', params={ response = app.post(
'source_type': source_type, '/ajax/qualification',
'source_pk': str(pk), params={
'formdef_reference': 'demarches:slug-1', 'source_type': source_type,
}) 'source_pk': str(pk),
'formdef_reference': 'demarches:slug-1',
},
)
# verify qualification was done # verify qualification was done
assert len(response.pyquery('a[data-association-pk]')) == 1 assert len(response.pyquery('a[data-association-pk]')) == 1
@ -243,7 +268,7 @@ def test_feed(settings, app, maarch, wcs, user):
'external_link': 'http://wcs.example.net/slug-1/1', 'external_link': 'http://wcs.example.net/slug-1/1',
'res_id': 1, 'res_id': 1,
} }
] ],
} }
# verify we can answer # verify we can answer
@ -260,24 +285,24 @@ def test_feed(settings, app, maarch, wcs, user):
assert response.json['err'] == 1 assert response.json['err'] == 1
# verify error when maarch feed is not configured # verify error when maarch feed is not configured
settings.MAARCH_FEED['ENABLE'] = False settings.MAARCH_FEED['ENABLE'] = False
response = app.post_json('/api/mail/response/', response = app.post_json(
params={'mail_id': 'maarch-1', 'content': 'coucou'}, '/api/mail/response/', params={'mail_id': 'maarch-1', 'content': 'coucou'}, status=200
status=200) )
assert response.json['err'] == 1 assert response.json['err'] == 1
assert response.json['err_desc'] == 'maarch is unconfigured' assert response.json['err_desc'] == 'maarch is unconfigured'
settings.MAARCH_FEED['ENABLE'] = True settings.MAARCH_FEED['ENABLE'] = True
# verify error when mail_id is unknown # verify error when mail_id is unknown
response = app.post_json('/api/mail/response/', response = app.post_json(
params={'mail_id': 'maarch-231', 'content': 'coucou'}, '/api/mail/response/', params={'mail_id': 'maarch-231', 'content': 'coucou'}, status=404
status=404) )
assert response.json['err'] == 1 assert response.json['err'] == 1
# successfull call # successfull call
maarch.responses.append({}) maarch.responses.append({})
with maarch.ctx_manager: with maarch.ctx_manager:
response = app.post_json('/api/mail/response/', response = app.post_json(
params={'mail_id': 'maarch-1', 'content': 'coucou'}, '/api/mail/response/', params={'mail_id': 'maarch-1', 'content': 'coucou'}, status=200
status=200) )
assert maarch.requests[0][3] == { assert maarch.requests[0][3] == {
'historyMessage': 'coucou', 'historyMessage': 'coucou',
'resId': [1], 'resId': [1],

View File

@ -38,95 +38,104 @@ def test_call_start_stop(client):
'callee': '102', 'callee': '102',
'data': { 'data': {
'user': 'boby.lapointe', 'user': 'boby.lapointe',
} },
} }
response = client.post(reverse('phone-call-event'), json.dumps(payload), response = client.post(reverse('phone-call-event'), json.dumps(payload), content_type='application/json')
content_type='application/json')
assert response.status_code == 200 assert response.status_code == 200
assert response['content-type'] == 'application/json' assert response['content-type'] == 'application/json'
assert response.json() == {'err': 0} assert response.json() == {'err': 0}
assert models.PhoneCall.objects.count() == 1 assert models.PhoneCall.objects.count() == 1
assert models.PhoneCall.objects.filter( assert (
caller='0033699999999', models.PhoneCall.objects.filter(
callee='102', caller='0033699999999', callee='102', data=json.dumps(payload['data']), stop__isnull=True
data=json.dumps(payload['data']), stop__isnull=True).count() == 1 ).count()
== 1
)
# new start event # new start event
response = client.post(reverse('phone-call-event'), json.dumps(payload), response = client.post(reverse('phone-call-event'), json.dumps(payload), content_type='application/json')
content_type='application/json')
assert response.status_code == 200 assert response.status_code == 200
assert response['content-type'] == 'application/json' assert response['content-type'] == 'application/json'
assert response.json() == {'err': 0} assert response.json() == {'err': 0}
assert models.PhoneCall.objects.count() == 2 assert models.PhoneCall.objects.count() == 2
assert models.PhoneCall.objects.filter( assert (
caller='0033699999999', models.PhoneCall.objects.filter(
callee='102', caller='0033699999999', callee='102', data=json.dumps(payload['data']), stop__isnull=True
data=json.dumps(payload['data']), stop__isnull=True).count() == 1 ).count()
== 1
)
# first call has been closed # first call has been closed
assert models.PhoneCall.objects.filter( assert (
caller='0033699999999', models.PhoneCall.objects.filter(
callee='102', caller='0033699999999', callee='102', data=json.dumps(payload['data']), stop__isnull=False
data=json.dumps(payload['data']), stop__isnull=False).count() == 1 ).count()
== 1
)
payload['event'] = 'stop' payload['event'] = 'stop'
response = client.post(reverse('phone-call-event'), json.dumps(payload), response = client.post(reverse('phone-call-event'), json.dumps(payload), content_type='application/json')
content_type='application/json')
assert response.status_code == 200 assert response.status_code == 200
assert response['content-type'] == 'application/json' assert response['content-type'] == 'application/json'
assert response.json() == {'err': 0} assert response.json() == {'err': 0}
assert models.PhoneCall.objects.count() == 2 assert models.PhoneCall.objects.count() == 2
assert models.PhoneCall.objects.filter( assert (
caller='0033699999999', models.PhoneCall.objects.filter(
callee='102', caller='0033699999999', callee='102', data=json.dumps(payload['data']), stop__isnull=False
data=json.dumps(payload['data']), stop__isnull=False).count() == 2 ).count()
== 2
)
# stop is idempotent # stop is idempotent
response = client.post(reverse('phone-call-event'), json.dumps(payload), response = client.post(reverse('phone-call-event'), json.dumps(payload), content_type='application/json')
content_type='application/json')
assert response.status_code == 200 assert response.status_code == 200
assert response['content-type'] == 'application/json' assert response['content-type'] == 'application/json'
assert response.json() == {'err': 0} assert response.json() == {'err': 0}
assert models.PhoneCall.objects.count() == 2 assert models.PhoneCall.objects.count() == 2
assert models.PhoneCall.objects.filter( assert (
caller='0033699999999', models.PhoneCall.objects.filter(
callee='102', caller='0033699999999', callee='102', data=json.dumps(payload['data']), stop__isnull=False
data=json.dumps(payload['data']), stop__isnull=False).count() == 2 ).count()
== 2
)
def test_one_call_per_callee(user, client): def test_one_call_per_callee(user, client):
assert models.PhoneCall.objects.count() == 0 assert models.PhoneCall.objects.count() == 0
payload = {'event': 'start', 'caller': '0033699999999', 'callee': '102'} payload = {'event': 'start', 'caller': '0033699999999', 'callee': '102'}
response = client.post(reverse('phone-call-event'), json.dumps(payload), response = client.post(reverse('phone-call-event'), json.dumps(payload), content_type='application/json')
content_type='application/json')
assert response.status_code == 200 assert response.status_code == 200
assert models.PhoneCall.objects.filter(callee='102', stop__isnull=True).count() == 1 # active assert models.PhoneCall.objects.filter(callee='102', stop__isnull=True).count() == 1 # active
assert models.PhoneCall.objects.filter(callee='102', stop__isnull=False).count() == 0 # inactive assert models.PhoneCall.objects.filter(callee='102', stop__isnull=False).count() == 0 # inactive
# new caller, same callee: stops the last call, start a new one # new caller, same callee: stops the last call, start a new one
payload['caller'] = '00337123456789' payload['caller'] = '00337123456789'
response = client.post(reverse('phone-call-event'), json.dumps(payload), response = client.post(reverse('phone-call-event'), json.dumps(payload), content_type='application/json')
content_type='application/json')
assert response.status_code == 200 assert response.status_code == 200
assert models.PhoneCall.objects.count() == 2 assert models.PhoneCall.objects.count() == 2
assert models.PhoneCall.objects.filter( assert (
caller='00337123456789', callee='102', stop__isnull=True).count() == 1 models.PhoneCall.objects.filter(caller='00337123456789', callee='102', stop__isnull=True).count() == 1
assert models.PhoneCall.objects.filter( )
caller='0033699999999', callee='102', stop__isnull=False).count() == 1 assert (
models.PhoneCall.objects.filter(caller='0033699999999', callee='102', stop__isnull=False).count() == 1
)
with override_settings(PHONE_ONE_CALL_PER_CALLEE=False): with override_settings(PHONE_ONE_CALL_PER_CALLEE=False):
# accept multiple call: start a new one, don't stop anything # accept multiple call: start a new one, don't stop anything
payload['caller'] = '00221774261500' payload['caller'] = '00221774261500'
response = client.post(reverse('phone-call-event'), json.dumps(payload), response = client.post(
content_type='application/json') reverse('phone-call-event'), json.dumps(payload), content_type='application/json'
)
assert response.status_code == 200 assert response.status_code == 200
assert models.PhoneCall.objects.count() == 3 assert models.PhoneCall.objects.count() == 3
assert models.PhoneCall.objects.filter(callee='102', stop__isnull=True).count() == 2 assert models.PhoneCall.objects.filter(callee='102', stop__isnull=True).count() == 2
assert models.PhoneCall.objects.filter(callee='102', stop__isnull=False).count() == 1 assert models.PhoneCall.objects.filter(callee='102', stop__isnull=False).count() == 1
# same caller: stop his last call, add a new one # same caller: stop his last call, add a new one
response = client.post(reverse('phone-call-event'), json.dumps(payload), response = client.post(
content_type='application/json') reverse('phone-call-event'), json.dumps(payload), content_type='application/json'
)
assert response.status_code == 200 assert response.status_code == 200
assert models.PhoneCall.objects.count() == 4 assert models.PhoneCall.objects.count() == 4
assert models.PhoneCall.objects.filter(callee='102', stop__isnull=True).count() == 2 assert models.PhoneCall.objects.filter(callee='102', stop__isnull=True).count() == 2
assert models.PhoneCall.objects.filter(callee='102', stop__isnull=False).count() == 2 assert models.PhoneCall.objects.filter(callee='102', stop__isnull=False).count() == 2
def test_current_calls(user, client): def test_current_calls(user, client):
# create some calls # create some calls
for number in range(0, 10): for number in range(0, 10):
@ -136,10 +145,11 @@ def test_current_calls(user, client):
'callee': '1%02d' % number, 'callee': '1%02d' % number,
'data': { 'data': {
'user': 'boby.lapointe', 'user': 'boby.lapointe',
} },
} }
response = client.post(reverse('phone-call-event'), json.dumps(payload), response = client.post(
content_type='application/json') reverse('phone-call-event'), json.dumps(payload), content_type='application/json'
)
assert response.status_code == 200 assert response.status_code == 200
assert response['content-type'] == 'application/json' assert response['content-type'] == 'application/json'
assert response.json() == {'err': 0} assert response.json() == {'err': 0}
@ -199,22 +209,20 @@ def test_take_release_line(user, client):
payload = { payload = {
'callee': '102', 'callee': '102',
} }
response = client.post(reverse('phone-take-line'), json.dumps(payload), response = client.post(reverse('phone-take-line'), json.dumps(payload), content_type='application/json')
content_type='application/json')
assert response.status_code == 200 assert response.status_code == 200
assert response['content-type'] == 'application/json' assert response['content-type'] == 'application/json'
assert response.json() == {'err': 0} assert response.json() == {'err': 0}
assert models.PhoneLine.objects.count() == 1 assert models.PhoneLine.objects.count() == 1
assert models.PhoneLine.objects.filter( assert models.PhoneLine.objects.filter(users=user, callee='102').count() == 1
users=user, callee='102').count() == 1 response = client.post(
response = client.post(reverse('phone-release-line'), json.dumps(payload), reverse('phone-release-line'), json.dumps(payload), content_type='application/json'
content_type='application/json') )
assert response.status_code == 200 assert response.status_code == 200
assert response['content-type'] == 'application/json' assert response['content-type'] == 'application/json'
assert response.json() == {'err': 0} assert response.json() == {'err': 0}
assert models.PhoneLine.objects.count() == 1 assert models.PhoneLine.objects.count() == 1
assert models.PhoneLine.objects.filter( assert models.PhoneLine.objects.filter(users=user, callee='102').count() == 0
users=user, callee='102').count() == 0
def test_phone_zone(user, client): def test_phone_zone(user, client):
@ -230,15 +238,16 @@ def test_phone_zone(user, client):
assert 'You do not have a phoneline configured' not in force_text(response.content) assert 'You do not have a phoneline configured' not in force_text(response.content)
assert '<li>102' in force_text(response.content) assert '<li>102' in force_text(response.content)
assert 'data-callee="102"' in force_text(response.content) assert 'data-callee="102"' in force_text(response.content)
currents = re.search('<div id="source-mainarea" ' currents = re.search(
'data-current-calls="/api/phone/current-calls/">' '<div id="source-mainarea" ' 'data-current-calls="/api/phone/current-calls/">' '(.*?)</div>',
'(.*?)</div>', force_text(response.content), flags=re.DOTALL) force_text(response.content),
flags=re.DOTALL,
)
assert currents.group(1).strip() == '' assert currents.group(1).strip() == ''
# create a call # create a call
payload = {'event': 'start', 'caller': '003369999999', 'callee': '102'} payload = {'event': 'start', 'caller': '003369999999', 'callee': '102'}
response = client.post(reverse('phone-call-event'), json.dumps(payload), response = client.post(reverse('phone-call-event'), json.dumps(payload), content_type='application/json')
content_type='application/json')
assert response.status_code == 200 assert response.status_code == 200
response = client.get(reverse('phone-zone')) response = client.get(reverse('phone-zone'))
assert response.status_code == 200 assert response.status_code == 200
@ -269,8 +278,7 @@ def test_call_expiration(user, client):
assert models.PhoneCall.objects.count() == 0 assert models.PhoneCall.objects.count() == 0
# create a call # create a call
payload = {'event': 'start', 'caller': '003369999999', 'callee': '102'} payload = {'event': 'start', 'caller': '003369999999', 'callee': '102'}
response = client.post(reverse('phone-call-event'), json.dumps(payload), response = client.post(reverse('phone-call-event'), json.dumps(payload), content_type='application/json')
content_type='application/json')
assert response.status_code == 200 assert response.status_code == 200
assert models.PhoneCall.objects.filter(stop__isnull=True).count() == 1 assert models.PhoneCall.objects.filter(stop__isnull=True).count() == 1
@ -284,15 +292,14 @@ def test_call_expiration(user, client):
assert len(payload['data']['calls']) == 1 assert len(payload['data']['calls']) == 1
# start call 10 minutes ago # start call 10 minutes ago
models.PhoneCall.objects.filter(stop__isnull=True).update( models.PhoneCall.objects.filter(stop__isnull=True).update(start=now() - timedelta(minutes=10))
start=now()-timedelta(minutes=10))
# get list of calls without expiration # get list of calls without expiration
response = client.get(reverse('phone-current-calls')) response = client.get(reverse('phone-current-calls'))
assert response.status_code == 200 assert response.status_code == 200
payload = response.json() payload = response.json()
assert payload['err'] == 0 assert payload['err'] == 0
assert len(payload['data']['calls']) == 1 # still here assert len(payload['data']['calls']) == 1 # still here
# get list of calls with an expiration of 2 minutes (< 10 minutes) # get list of calls with an expiration of 2 minutes (< 10 minutes)
with override_settings(PHONE_MAX_CALL_DURATION=2): with override_settings(PHONE_MAX_CALL_DURATION=2):
@ -300,7 +307,7 @@ def test_call_expiration(user, client):
assert response.status_code == 200 assert response.status_code == 200
payload = response.json() payload = response.json()
assert payload['err'] == 0 assert payload['err'] == 0
assert len(payload['data']['calls']) == 0 # call is expired assert len(payload['data']['calls']) == 0 # call is expired
assert models.PhoneCall.objects.filter(stop__isnull=True).count() == 0 # active calls assert models.PhoneCall.objects.filter(stop__isnull=True).count() == 0 # active calls
assert models.PhoneCall.objects.filter(stop__isnull=False).count() == 1 # stopped calls assert models.PhoneCall.objects.filter(stop__isnull=False).count() == 1 # stopped calls

View File

@ -17,6 +17,7 @@
from django.apps import apps from django.apps import apps
from django.conf.urls import include, url from django.conf.urls import include, url
def register_urls(urlpatterns): def register_urls(urlpatterns):
pre_urls = [] pre_urls = []
post_urls = [] post_urls = []

View File

@ -24,14 +24,15 @@ DEFAULT_TITLE_CHOICES = (
(pgettext_lazy('title', 'Mr'), pgettext_lazy('title', 'Mr')), (pgettext_lazy('title', 'Mr'), pgettext_lazy('title', 'Mr')),
) )
class ContactAddForm(forms.Form): class ContactAddForm(forms.Form):
title = forms.CharField(label=_('Title'), title = forms.CharField(
required=False, label=_('Title'), required=False, widget=forms.Select(choices=DEFAULT_TITLE_CHOICES)
widget=forms.Select(choices=DEFAULT_TITLE_CHOICES)) )
first_name = forms.CharField(label=_('First Name'), required=False) first_name = forms.CharField(label=_('First Name'), required=False)
last_name = forms.CharField(label=_('Last Name'), last_name = forms.CharField(
required=True, label=_('Last Name'), required=True, widget=forms.TextInput(attrs={'required': 'required'})
widget=forms.TextInput(attrs={'required': 'required'})) )
email = forms.CharField(label=_('Email'), required=False) email = forms.CharField(label=_('Email'), required=False)
address = forms.CharField(label=_('Address'), required=False) address = forms.CharField(label=_('Address'), required=False)
zipcode = forms.CharField(label=_('Zip Code'), required=False) zipcode = forms.CharField(label=_('Zip Code'), required=False)

View File

@ -33,6 +33,7 @@ from welco.utils import get_wcs_data, sign_url
from .forms import ContactAddForm from .forms import ContactAddForm
class HomeZone(object): class HomeZone(object):
def __init__(self, request): def __init__(self, request):
self.request = request self.request = request
@ -49,21 +50,20 @@ class ContactsZone(TemplateView):
context = super(ContactsZone, self).get_context_data(**kwargs) context = super(ContactsZone, self).get_context_data(**kwargs)
context['source_pk'] = self.request.GET.get('source_pk') context['source_pk'] = self.request.GET.get('source_pk')
if 'source_pk' in self.request.GET: if 'source_pk' in self.request.GET:
source_class = ContentType.objects.get( source_class = ContentType.objects.get(id=self.request.GET['source_type']).model_class()
id=self.request.GET['source_type']).model_class()
source_object = source_class.objects.get(id=self.request.GET['source_pk']) source_object = source_class.objects.get(id=self.request.GET['source_pk'])
context['contact_user_id'] = source_object.contact_id context['contact_user_id'] = source_object.contact_id
return context return context
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if 'user_id' in request.POST: if 'user_id' in request.POST:
source_class = ContentType.objects.get( source_class = ContentType.objects.get(id=self.request.POST['source_type']).model_class()
id=self.request.POST['source_type']).model_class()
source_object = source_class.objects.get(id=self.request.POST['source_pk']) source_object = source_class.objects.get(id=self.request.POST['source_pk'])
source_object.contact_id = request.POST['user_id'] source_object.contact_id = request.POST['user_id']
source_object.save() source_object.save()
return HttpResponse('ok') return HttpResponse('ok')
zone = csrf_exempt(ContactsZone.as_view()) zone = csrf_exempt(ContactsZone.as_view())
@ -83,8 +83,12 @@ def search_json(request):
raise Exception('error %r' % result) raise Exception('error %r' % result)
for user in result.get('data'): for user in result.get('data'):
user['title'] = user['user_display_name'] user['title'] = user['user_display_name']
more = [user.get('user_var_address'), user.get('user_var_phone'), more = [
user.get('user_var_mobile'), user.get('user_var_email')] user.get('user_var_address'),
user.get('user_var_phone'),
user.get('user_var_mobile'),
user.get('user_var_email'),
]
user['more'] = ' / '.join([x for x in more if x]) user['more'] = ' / '.join([x for x in more if x])
if user.get('user_roles'): if user.get('user_roles'):
user['roles'] = ' / '.join([r['text'] for r in user['user_roles']]) user['roles'] = ' / '.join([r['text'] for r in user['user_roles']])
@ -109,13 +113,13 @@ class ContactDetailFragmentView(TemplateView):
context['user_id'] = user_id context['user_id'] = user_id
if 'source_pk' in self.request.GET: if 'source_pk' in self.request.GET:
source_class = ContentType.objects.get( source_class = ContentType.objects.get(id=self.request.GET['source_type']).model_class()
id=self.request.GET['source_type']).model_class()
source_object = source_class.objects.get(id=self.request.GET['source_pk']) source_object = source_class.objects.get(id=self.request.GET['source_pk'])
context['is_pinned_user'] = bool(source_object.contact_id == user_id) context['is_pinned_user'] = bool(source_object.contact_id == user_id)
return context return context
contact_detail_fragment = ContactDetailFragmentView.as_view() contact_detail_fragment = ContactDetailFragmentView.as_view()
@ -144,9 +148,8 @@ class ContactAdd(FormView):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.info('POST to authentic (%r)', json.dumps(msg)) logger.info('POST to authentic (%r)', json.dumps(msg))
authentic_response = requests.post( authentic_response = requests.post(
signed_url, signed_url, data=json.dumps(msg), headers={'Content-type': 'application/json'}
data=json.dumps(msg), )
headers={'Content-type': 'application/json'})
logger.info('Got authentic response (%r)', authentic_response.text) logger.info('Got authentic response (%r)', authentic_response.text)
user_uuid = authentic_response.json().get('uuid') user_uuid = authentic_response.json().get('uuid')
@ -165,4 +168,5 @@ class ContactAdd(FormView):
json.dump(result, response, indent=2) json.dump(result, response, indent=2)
return response return response
contact_add = csrf_exempt(ContactAdd.as_view()) contact_add = csrf_exempt(ContactAdd.as_view())

View File

@ -16,6 +16,7 @@
from django.apps import AppConfig from django.apps import AppConfig
class KbAppConfig(AppConfig): class KbAppConfig(AppConfig):
name = 'welco.kb' name = 'welco.kb'

View File

@ -19,6 +19,7 @@ from django.utils.text import slugify
from .models import Page from .models import Page
class PageForm(forms.ModelForm): class PageForm(forms.ModelForm):
class Meta: class Meta:
model = Page model = Page

View File

@ -7,14 +7,16 @@ import ckeditor.fields
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Page', name='Page',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('title', models.CharField(max_length=200, verbose_name='Title')), ('title', models.CharField(max_length=200, verbose_name='Title')),
('slug', models.SlugField(verbose_name='Slug')), ('slug', models.SlugField(verbose_name='Slug')),
('content', ckeditor.fields.RichTextField(verbose_name='Text')), ('content', ckeditor.fields.RichTextField(verbose_name='Text')),

View File

@ -16,7 +16,13 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='page', model_name='page',
name='tags', name='tags',
field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Keywords'), field=taggit.managers.TaggableManager(
to='taggit.Tag',
through='taggit.TaggedItem',
blank=True,
help_text='A comma-separated list of tags.',
verbose_name='Keywords',
),
preserve_default=True, preserve_default=True,
), ),
] ]

View File

@ -26,8 +26,7 @@ class Page(models.Model):
title = models.CharField(_('Title'), max_length=200) title = models.CharField(_('Title'), max_length=200)
slug = models.SlugField(_('Slug')) slug = models.SlugField(_('Slug'))
content = RichTextField(_('Text')) content = RichTextField(_('Text'))
tags = TaggableManager(_('Keywords'), blank=True, tags = TaggableManager(_('Keywords'), blank=True, help_text=_('A comma-separated list of tags.'))
help_text=_('A comma-separated list of tags.'))
class Meta: class Meta:
ordering = ['title'] ordering = ['title']

View File

@ -21,6 +21,7 @@ from haystack import indexes
from .models import Page from .models import Page
class PageIndex(indexes.SearchIndex, indexes.Indexable): class PageIndex(indexes.SearchIndex, indexes.Indexable):
title = indexes.CharField(model_attr='title', boost=3) title = indexes.CharField(model_attr='title', boost=3)
text = indexes.CharField(document=True) text = indexes.CharField(document=True)

View File

@ -25,8 +25,7 @@ from django.db.models import Count
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.template import RequestContext from django.template import RequestContext
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.generic import (DetailView, CreateView, UpdateView, from django.views.generic import DetailView, CreateView, UpdateView, ListView, DeleteView, TemplateView
ListView, DeleteView, TemplateView)
from haystack.forms import SearchForm from haystack.forms import SearchForm
from haystack.generic_views import SearchView from haystack.generic_views import SearchView
@ -36,15 +35,17 @@ from taggit.models import Tag
from .models import Page from .models import Page
from .forms import PageForm from .forms import PageForm
def check_user_perms(user, access=False): def check_user_perms(user, access=False):
allowed_roles = settings.KB_MANAGE_ROLES[:] allowed_roles = settings.KB_MANAGE_ROLES[:]
if access: if access:
allowed_roles.extend(settings.KB_ACCESS_ROLES) allowed_roles.extend(settings.KB_ACCESS_ROLES)
if settings.KB_ROLE: if settings.KB_ROLE:
allowed_roles.append(settings.KB_ROLE) # legacy allowed_roles.append(settings.KB_ROLE) # legacy
user_groups = set([x.name for x in user.groups.all()]) user_groups = set([x.name for x in user.groups.all()])
return user_groups.intersection(allowed_roles) return user_groups.intersection(allowed_roles)
def check_request_perms(request, access=False): def check_request_perms(request, access=False):
if not check_user_perms(request.user, access=access): if not check_user_perms(request.user, access=access):
raise PermissionDenied() raise PermissionDenied()
@ -63,6 +64,7 @@ class PageListView(ListView):
context['can_manage'] = check_user_perms(self.request.user) context['can_manage'] = check_user_perms(self.request.user)
return context return context
page_list = login_required(PageListView.as_view()) page_list = login_required(PageListView.as_view())
@ -74,6 +76,7 @@ class PageAddView(CreateView):
check_request_perms(request) check_request_perms(request)
return super(PageAddView, self).dispatch(request, *args, **kwargs) return super(PageAddView, self).dispatch(request, *args, **kwargs)
page_add = login_required(PageAddView.as_view()) page_add = login_required(PageAddView.as_view())
@ -85,6 +88,7 @@ class PageEditView(UpdateView):
check_request_perms(request) check_request_perms(request)
return super(PageEditView, self).dispatch(request, *args, **kwargs) return super(PageEditView, self).dispatch(request, *args, **kwargs)
page_edit = login_required(PageEditView.as_view()) page_edit = login_required(PageEditView.as_view())
@ -108,6 +112,7 @@ class PageDetailFragmentView(DetailView):
model = Page model = Page
template_name = 'kb/page_detail_fragment.html' template_name = 'kb/page_detail_fragment.html'
page_detail_fragment = PageDetailFragmentView.as_view() page_detail_fragment = PageDetailFragmentView.as_view()
@ -119,6 +124,7 @@ class PageDeleteView(DeleteView):
check_request_perms(request) check_request_perms(request)
return super(PageDeleteView, self).dispatch(request, *args, **kwargs) return super(PageDeleteView, self).dispatch(request, *args, **kwargs)
page_delete = login_required(PageDeleteView.as_view()) page_delete = login_required(PageDeleteView.as_view())
@ -130,6 +136,7 @@ class PageSearchView(SearchView):
check_request_perms(request, access=True) check_request_perms(request, access=True)
return super(PageSearchView, self).dispatch(request, *args, **kwargs) return super(PageSearchView, self).dispatch(request, *args, **kwargs)
page_search = login_required(PageSearchView.as_view()) page_search = login_required(PageSearchView.as_view())
@ -140,8 +147,9 @@ class KbZone(TemplateView):
context = super(KbZone, self).get_context_data(**kwargs) context = super(KbZone, self).get_context_data(**kwargs)
context['source_pk'] = self.request.GET.get('source_pk') context['source_pk'] = self.request.GET.get('source_pk')
context['form'] = SearchForm() context['form'] = SearchForm()
context['tags'] = Tag.objects.all().annotate( context['tags'] = (
num_times=Count('taggit_taggeditem_items')).filter(num_times__gt=0) Tag.objects.all().annotate(num_times=Count('taggit_taggeditem_items')).filter(num_times__gt=0)
)
num_times = context['tags'].values_list('num_times', flat=True) num_times = context['tags'].values_list('num_times', flat=True)
if not num_times: if not num_times:
num_times = [0] num_times = [0]
@ -163,6 +171,7 @@ class KbZone(TemplateView):
tag.font_size = 'x-large' tag.font_size = 'x-large'
return context return context
zone = csrf_exempt(KbZone.as_view()) zone = csrf_exempt(KbZone.as_view())

View File

@ -24,6 +24,7 @@ from django.utils.translation import get_language
import ckeditor.widgets import ckeditor.widgets
def ckeditor_render(self, name, value, attrs=None): def ckeditor_render(self, name, value, attrs=None):
if value is None: if value is None:
value = '' value = ''
@ -40,14 +41,22 @@ def ckeditor_render(self, name, value, attrs=None):
self.config['language'] = get_language() self.config['language'] = get_language()
# Force to text to evaluate possible lazy objects # Force to text to evaluate possible lazy objects
external_plugin_resources = [[force_text(a), force_text(b), force_text(c)] for a, b, c in self.external_plugin_resources] external_plugin_resources = [
[force_text(a), force_text(b), force_text(c)] for a, b, c in self.external_plugin_resources
]
return mark_safe(
render_to_string(
'ckeditor/widget.html',
{
'final_attrs': flatatt(final_attrs),
'value': conditional_escape(force_text(value)),
'id': final_attrs['id'],
'config': ckeditor.widgets.json_encode(self.config),
'external_plugin_resources': ckeditor.widgets.json_encode(external_plugin_resources),
},
)
)
return mark_safe(render_to_string('ckeditor/widget.html', {
'final_attrs': flatatt(final_attrs),
'value': conditional_escape(force_text(value)),
'id': final_attrs['id'],
'config': ckeditor.widgets.json_encode(self.config),
'external_plugin_resources' : ckeditor.widgets.json_encode(external_plugin_resources)
}))
ckeditor.widgets.CKEditorWidget.render = ckeditor_render ckeditor.widgets.CKEditorWidget.render = ckeditor_render

View File

@ -14,31 +14,37 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Association', name='Association',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('source_pk', models.PositiveIntegerField()), ('source_pk', models.PositiveIntegerField()),
], ],
options={ options={},
},
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name='FormdataReference', name='FormdataReference',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('reference', models.CharField(max_length=250)), ('reference', models.CharField(max_length=250)),
], ],
options={ options={},
},
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name='FormdefReference', name='FormdefReference',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('reference', models.CharField(max_length=250)), ('reference', models.CharField(max_length=250)),
], ],
options={ options={},
},
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.AddField( migrations.AddField(

View File

@ -38,10 +38,12 @@ class Association(models.Model):
if self.source.contact_id: if self.source.contact_id:
context['user_id'] = self.source.contact_id context['user_id'] = self.source.contact_id
context['summary_url'] = request.build_absolute_uri( context['summary_url'] = request.build_absolute_uri(
reverse('wcs-summary', kwargs={'source_type': self.source_type_id, reverse('wcs-summary', kwargs={'source_type': self.source_type_id, 'source_pk': self.source_pk})
'source_pk': self.source_pk})) )
context.update(self.source.get_source_context(request)) context.update(self.source.get_source_context(request))
self.formdata_id, self.formdata_url_backoffice = push_wcs_formdata(request, self.formdef_reference, context) self.formdata_id, self.formdata_url_backoffice = push_wcs_formdata(
request, self.formdef_reference, context
)
self.save() self.save()
@property @property

View File

@ -90,7 +90,7 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
LOCALE_PATHS = (os.path.join(BASE_DIR, 'welco', 'locale'), ) LOCALE_PATHS = (os.path.join(BASE_DIR, 'welco', 'locale'),)
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/ # https://docs.djangoproject.com/en/1.7/howto/static-files/
@ -99,9 +99,7 @@ STATIC_URL = '/static/'
STATICFILES_FINDERS = tuple(global_settings.STATICFILES_FINDERS) + ('gadjo.finders.XStaticFinder',) STATICFILES_FINDERS = tuple(global_settings.STATICFILES_FINDERS) + ('gadjo.finders.XStaticFinder',)
STATICFILES_DIRS = ( STATICFILES_DIRS = (os.path.join(BASE_DIR, 'welco', 'static'),)
os.path.join(BASE_DIR, 'welco', 'static'),
)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
@ -131,12 +129,18 @@ CKEDITOR_UPLOAD_PATH = 'uploads/'
CKEDITOR_CONFIGS = { CKEDITOR_CONFIGS = {
'default': { 'default': {
'toolbar_Own': [['Source', 'Format', '-', 'Bold', 'Italic'], 'toolbar_Own': [
['NumberedList', 'BulletedList'], ['Source', 'Format', '-', 'Bold', 'Italic'],
['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'], ['NumberedList', 'BulletedList'],
['Link', 'Unlink'], ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
['Image',], ['Link', 'Unlink'],
['RemoveFormat',]], [
'Image',
],
[
'RemoveFormat',
],
],
'toolbar': 'Own', 'toolbar': 'Own',
}, },
} }
@ -190,7 +194,7 @@ CHANNEL_ROLES = {
} }
# role allowed to manage knowledge base # role allowed to manage knowledge base
KB_ROLE = None # deprecated KB_ROLE = None # deprecated
KB_MANAGE_ROLES = [] KB_MANAGE_ROLES = []
# roles allowed to visit knowledge base # roles allowed to visit knowledge base
@ -200,9 +204,7 @@ KB_ACCESS_ROLES = []
SCREEN_PANELS = ['contacts', 'qualif'] SCREEN_PANELS = ['contacts', 'qualif']
# useful links for counter # useful links for counter
COUNTER_LINKS = [ COUNTER_LINKS = [{'label': 'Wikipedia', 'url': 'https://fr.wikipedia.org'}]
{'label': 'Wikipedia', 'url': 'https://fr.wikipedia.org'}
]
# phone system # phone system
PHONE_ONE_CALL_PER_CALLEE = True PHONE_ONE_CALL_PER_CALLEE = True
@ -214,7 +216,8 @@ PHONE_AUTOTAKE_MELLON_USERNAME = False
REST_FRAMEWORK = {} REST_FRAMEWORK = {}
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = ['rest_framework.authentication.BasicAuthentication'] REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = ['rest_framework.authentication.BasicAuthentication']
local_settings_file = os.environ.get('WELCO_SETTINGS_FILE', local_settings_file = os.environ.get(
os.path.join(os.path.dirname(__file__), 'local_settings.py')) 'WELCO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')
)
if os.path.exists(local_settings_file): if os.path.exists(local_settings_file):
exec(open(local_settings_file).read()) exec(open(local_settings_file).read())

View File

@ -6,14 +6,16 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='CounterPresence', name='CounterPresence',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('status', models.CharField(max_length=50, verbose_name='Status', blank=True)), ('status', models.CharField(max_length=50, verbose_name='Status', blank=True)),
('contact_id', models.CharField(max_length=50, null=True)), ('contact_id', models.CharField(max_length=50, null=True)),
('creation_timestamp', models.DateTimeField(auto_now_add=True)), ('creation_timestamp', models.DateTimeField(auto_now_add=True)),

View File

@ -20,16 +20,15 @@ from django.utils.translation import ugettext_lazy as _
from welco.qualif.models import Association from welco.qualif.models import Association
class CounterPresence(models.Model):
class CounterPresence(models.Model):
class Meta: class Meta:
verbose_name = _('Counter Presence') verbose_name = _('Counter Presence')
# common to all source types: # common to all source types:
status = models.CharField(_('Status'), blank=True, max_length=50) status = models.CharField(_('Status'), blank=True, max_length=50)
contact_id = models.CharField(max_length=50, null=True) contact_id = models.CharField(max_length=50, null=True)
associations = GenericRelation(Association, associations = GenericRelation(Association, content_type_field='source_type', object_id_field='source_pk')
content_type_field='source_type', object_id_field='source_pk')
creation_timestamp = models.DateTimeField(auto_now_add=True) creation_timestamp = models.DateTimeField(auto_now_add=True)
last_update_timestamp = models.DateTimeField(auto_now=True) last_update_timestamp = models.DateTimeField(auto_now=True)

View File

@ -54,6 +54,7 @@ class CounterZone(TemplateView):
context['useful_links'] = settings.COUNTER_LINKS context['useful_links'] = settings.COUNTER_LINKS
return context return context
zone = csrf_exempt(CounterZone.as_view()) zone = csrf_exempt(CounterZone.as_view())

View File

@ -22,14 +22,14 @@ class AppConfig(django.apps.AppConfig):
def get_before_urls(self): def get_before_urls(self):
from . import urls from . import urls
return urls.urlpatterns return urls.urlpatterns
def ready(self): def ready(self):
from welco.qualif.models import Association from welco.qualif.models import Association
from django.db.models import signals from django.db.models import signals
signals.post_save.connect(self.association_post_save, signals.post_save.connect(self.association_post_save, sender=Association)
sender=Association)
def association_post_save(self, sender, instance, **kwargs): def association_post_save(self, sender, instance, **kwargs):
from .utils import get_maarch from .utils import get_maarch
@ -47,6 +47,8 @@ class AppConfig(django.apps.AppConfig):
maarch.set_grc_sent_status( maarch.set_grc_sent_status(
mail_pk=maarch_pk, mail_pk=maarch_pk,
formdata_id=instance.formdata_id, formdata_id=instance.formdata_id,
formdata_url_backoffice=instance.formdata_url_backoffice) formdata_url_backoffice=instance.formdata_url_backoffice,
)
default_app_config = 'welco.sources.mail.AppConfig' default_app_config = 'welco.sources.mail.AppConfig'

View File

@ -18,6 +18,7 @@ from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
class MailQualificationForm(forms.Form): class MailQualificationForm(forms.Form):
post_date = forms.DateTimeField(label=_('Post Date (*)'), required=False) post_date = forms.DateTimeField(label=_('Post Date (*)'), required=False)
registered_mail_number = forms.CharField(label=_('Registered Mail Number'), required=False) registered_mail_number = forms.CharField(label=_('Registered Mail Number'), required=False)

View File

@ -125,7 +125,7 @@ class MaarchCourrier(object):
read=self.max_retries, read=self.max_retries,
connect=self.max_retries, connect=self.max_retries,
backoff_factor=0.5, backoff_factor=0.5,
status_forcelist=(500, 502, 504) status_forcelist=(500, 502, 504),
) )
adapter = HTTPAdapter(max_retries=retry) adapter = HTTPAdapter(max_retries=retry)
s.mount('http://', adapter) s.mount('http://', adapter)
@ -175,13 +175,16 @@ class MaarchCourrier(object):
fields = ','.join(fields) if fields else '*' fields = ','.join(fields) if fields else '*'
limit = limit or self.default_limit limit = limit or self.default_limit
order_by = order_by or [] order_by = order_by or []
response = self.post_json(self.list_url, { response = self.post_json(
'select': fields, self.list_url,
'clause': clause, {
'limit': limit, 'select': fields,
'withFile': include_file, 'clause': clause,
'orderBy': order_by, 'limit': limit,
}) 'withFile': include_file,
'orderBy': order_by,
},
)
if not hasattr(response.get('resources'), 'append'): if not hasattr(response.get('resources'), 'append'):
raise MaarchError('missing resources field or bad type', response) raise MaarchError('missing resources field or bad type', response)
return [self.Courrier(self, **resource) for resource in response['resources']] return [self.Courrier(self, **resource) for resource in response['resources']]

View File

@ -22,10 +22,10 @@ from django.core.management.base import BaseCommand, CommandError
from ...models import Mail from ...models import Mail
class Command(BaseCommand): class Command(BaseCommand):
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument( parser.add_argument('--category', metavar='CATEGORY', default=None)
'--category', metavar='CATEGORY', default=None)
parser.add_argument('filenames', metavar='FILENAME', nargs='+') parser.add_argument('filenames', metavar='FILENAME', nargs='+')
def handle(self, filenames, *args, **kwargs): def handle(self, filenames, *args, **kwargs):

View File

@ -25,14 +25,15 @@ from django.db import transaction
from ...models import Mail from ...models import Mail
from ...utils import get_maarch from ...utils import get_maarch
class Command(BaseCommand): class Command(BaseCommand):
"""Inject mail coming from Maarch into welco. """Inject mail coming from Maarch into welco.
Only mail with a status "GRC" are injected, Only mail with a status "GRC" are injected,
After injection, their status is immediately changed to "GRC_TRT". After injection, their status is immediately changed to "GRC_TRT".
After injection in w.c.s., their status is changed to "GRCSENT" and an After injection in w.c.s., their status is changed to "GRCSENT" and an
id and an URL of the request in w.c.s. is attached to the mail in id and an URL of the request in w.c.s. is attached to the mail in
Maarch. Maarch.
""" """
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):

View File

@ -6,14 +6,16 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Mail', name='Mail',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('content', models.FileField(upload_to=b'', verbose_name='Content')), ('content', models.FileField(upload_to=b'', verbose_name='Content')),
('triaged', models.BooleanField(default=False)), ('triaged', models.BooleanField(default=False)),
('creation_timestamp', models.DateTimeField(auto_now_add=True)), ('creation_timestamp', models.DateTimeField(auto_now_add=True)),

View File

@ -18,7 +18,6 @@ class Migration(migrations.Migration):
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='mail', name='mail',
options={'ordering': ['post_date', 'creation_timestamp'], options={'ordering': ['post_date', 'creation_timestamp'], 'verbose_name': 'Mail'},
'verbose_name': 'Mail'},
), ),
] ]

View File

@ -32,15 +32,13 @@ from welco.utils import get_wcs_data
class Mail(models.Model): class Mail(models.Model):
class Meta: class Meta:
verbose_name = _('Mail') verbose_name = _('Mail')
ordering = ['post_date', 'creation_timestamp'] ordering = ['post_date', 'creation_timestamp']
content = models.FileField(_('Content')) content = models.FileField(_('Content'))
post_date = models.DateField(_('Post Date'), null=True) post_date = models.DateField(_('Post Date'), null=True)
registered_mail_number = models.CharField(_('Registered Mail Number'), registered_mail_number = models.CharField(_('Registered Mail Number'), null=True, max_length=50)
null=True, max_length=50)
note = models.TextField(_('Note'), null=True) note = models.TextField(_('Note'), null=True)
external_id = models.CharField(_('External Id'), null=True, max_length=32) external_id = models.CharField(_('External Id'), null=True, max_length=32)
@ -52,8 +50,7 @@ class Mail(models.Model):
# common to all source types: # common to all source types:
status = models.CharField(_('Status'), blank=True, max_length=50) status = models.CharField(_('Status'), blank=True, max_length=50)
contact_id = models.CharField(max_length=50, null=True) contact_id = models.CharField(max_length=50, null=True)
associations = GenericRelation(Association, associations = GenericRelation(Association, content_type_field='source_type', object_id_field='source_pk')
content_type_field='source_type', object_id_field='source_pk')
creation_timestamp = models.DateTimeField(auto_now_add=True) creation_timestamp = models.DateTimeField(auto_now_add=True)
last_update_timestamp = models.DateTimeField(auto_now=True) last_update_timestamp = models.DateTimeField(auto_now=True)
@ -61,6 +58,7 @@ class Mail(models.Model):
@classmethod @classmethod
def get_qualification_form_class(cls): def get_qualification_form_class(cls):
from .forms import MailQualificationForm from .forms import MailQualificationForm
return MailQualificationForm return MailQualificationForm
def get_qualification_form(self): def get_qualification_form(self):
@ -111,5 +109,13 @@ class Mail(models.Model):
def create_thumbnail(sender, instance, created, **kwargs): def create_thumbnail(sender, instance, created, **kwargs):
if not created: if not created:
return return
subprocess.call(['gm', 'convert', '-geometry', '200x', subprocess.call(
instance.content.file.name, instance.content.file.name + '.png']) [
'gm',
'convert',
'-geometry',
'200x',
instance.content.file.name,
instance.content.file.name + '.png',
]
)

View File

@ -16,8 +16,7 @@
from django.conf.urls import url from django.conf.urls import url
from .views import (viewer, feeder, qualification_save, edit_note, note, from .views import viewer, feeder, qualification_save, edit_note, note, reject, mail_count, mail_response
reject, mail_count, mail_response)
urlpatterns = [ urlpatterns = [
url('viewer/$', viewer, name='mail-viewer'), url('viewer/$', viewer, name='mail-viewer'),

View File

@ -20,9 +20,18 @@ from .maarch import MaarchCourrier, MaarchError
class WelcoMaarchCourrier(MaarchCourrier): class WelcoMaarchCourrier(MaarchCourrier):
def __init__(self, url, username, password, grc_status, def __init__(
grc_received_status, grc_send_status, grc_refused_status, self,
grc_response_status, batch_size=10): url,
username,
password,
grc_status,
grc_received_status,
grc_send_status,
grc_refused_status,
grc_response_status,
batch_size=10,
):
super(WelcoMaarchCourrier, self).__init__(url, username, password) super(WelcoMaarchCourrier, self).__init__(url, username, password)
self.grc_status = grc_status self.grc_status = grc_status
self.grc_received_status = grc_received_status self.grc_received_status = grc_received_status
@ -36,7 +45,8 @@ class WelcoMaarchCourrier(MaarchCourrier):
clause="status='%s'" % self.grc_status, clause="status='%s'" % self.grc_status,
include_file=True, include_file=True,
order_by=['res_id'], order_by=['res_id'],
limit=self.batch_size) limit=self.batch_size,
)
def get_mail(self, mail_id): def get_mail(self, mail_id):
return self.get_courriers(clause="res_id=%s" % mail_id)[0] return self.get_courriers(clause="res_id=%s" % mail_id)[0]
@ -74,5 +84,5 @@ def get_maarch():
grc_received_status=config.get('STATUS_RECEIVED', 'GRC_TRT'), grc_received_status=config.get('STATUS_RECEIVED', 'GRC_TRT'),
grc_send_status=config.get('STATUS_SEND', 'GRCSENT'), grc_send_status=config.get('STATUS_SEND', 'GRCSENT'),
grc_refused_status=config.get('STATUS_REFUSED', 'GRCREFUSED'), grc_refused_status=config.get('STATUS_REFUSED', 'GRCREFUSED'),
grc_response_status=config.get('STATUS_RESPONSE', 'GRC_RESPONSE')) grc_response_status=config.get('STATUS_RESPONSE', 'GRC_RESPONSE'),
)

View File

@ -42,11 +42,11 @@ from .utils import get_maarch, MaarchError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def viewer(request, *args, **kwargs): def viewer(request, *args, **kwargs):
if not 'file' in request.GET: if not 'file' in request.GET:
return HttpResponseRedirect('?file=') return HttpResponseRedirect('?file=')
body = template.loader.get_template('welco/mail_viewer.html').render( body = template.loader.get_template('welco/mail_viewer.html').render(request=request)
request=request)
return HttpResponse(body) return HttpResponse(body)
@ -57,12 +57,13 @@ class Feeder(TemplateView):
for upload in request.FILES.getlist('mail'): for upload in request.FILES.getlist('mail'):
mail = Mail(content=upload) mail = Mail(content=upload)
mail.save() mail.save()
messages.info(request, _('%d files uploaded successfully.') % messages.info(request, _('%d files uploaded successfully.') % len(request.FILES.getlist('mail')))
len(request.FILES.getlist('mail')))
return HttpResponseRedirect(reverse('mail-feeder')) return HttpResponseRedirect(reverse('mail-feeder'))
feeder = login_required(csrf_exempt(Feeder.as_view())) feeder = login_required(csrf_exempt(Feeder.as_view()))
class Home(object): class Home(object):
source_key = 'mail' source_key = 'mail'
display_filter = True display_filter = True
@ -101,9 +102,10 @@ def qualification_save(request, *args, **kwargs):
mail.reference = form.cleaned_data['reference'] mail.reference = form.cleaned_data['reference']
mail.subject = form.cleaned_data['subject'] mail.subject = form.cleaned_data['subject']
mail.save() mail.save()
return HttpResponseRedirect(reverse('qualif-zone') + return HttpResponseRedirect(
'?source_type=%s&source_pk=%s' % (request.POST['source_type'], reverse('qualif-zone')
request.POST['source_pk'])) + '?source_type=%s&source_pk=%s' % (request.POST['source_type'], request.POST['source_pk'])
)
class EditNote(TemplateView): class EditNote(TemplateView):
@ -120,6 +122,7 @@ class EditNote(TemplateView):
mail.save() mail.save()
return HttpResponse(json.dumps({'result': 'ok'})) return HttpResponse(json.dumps({'result': 'ok'}))
edit_note = login_required(csrf_exempt(EditNote.as_view())) edit_note = login_required(csrf_exempt(EditNote.as_view()))
@ -192,4 +195,5 @@ class MailResponseAPIView(GenericAPIView):
return Response({'err': 1, 'err_desc': str(e)}) return Response({'err': 1, 'err_desc': str(e)})
return Response({'err': 0}) return Response({'err': 0})
mail_response = MailResponseAPIView.as_view() mail_response = MailResponseAPIView.as_view()

View File

@ -6,14 +6,16 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='PhoneCall', name='PhoneCall',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('number', models.CharField(max_length=20, verbose_name='Number')), ('number', models.CharField(max_length=20, verbose_name='Number')),
('creation_timestamp', models.DateTimeField(auto_now_add=True)), ('creation_timestamp', models.DateTimeField(auto_now_add=True)),
('last_update_timestamp', models.DateTimeField(auto_now=True)), ('last_update_timestamp', models.DateTimeField(auto_now=True)),

View File

@ -19,7 +19,10 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='PhoneLine', name='PhoneLine',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('callee', models.CharField(unique=True, max_length=20, verbose_name='Callee')), ('callee', models.CharField(unique=True, max_length=20, verbose_name='Callee')),
('users', models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name='User')), ('users', models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name='User')),
], ],

View File

@ -25,8 +25,8 @@ from django.utils.timezone import now, timedelta
from welco.qualif.models import Association from welco.qualif.models import Association
class PhoneCall(models.Model):
class PhoneCall(models.Model):
class Meta: class Meta:
verbose_name = _('Phone Call') verbose_name = _('Phone Call')
@ -39,8 +39,7 @@ class PhoneCall(models.Model):
# common to all source types: # common to all source types:
status = models.CharField(_('Status'), blank=True, max_length=50) status = models.CharField(_('Status'), blank=True, max_length=50)
contact_id = models.CharField(max_length=50, null=True) contact_id = models.CharField(max_length=50, null=True)
associations = GenericRelation(Association, associations = GenericRelation(Association, content_type_field='source_type', object_id_field='source_pk')
content_type_field='source_type', object_id_field='source_pk')
creation_timestamp = models.DateTimeField(auto_now_add=True) creation_timestamp = models.DateTimeField(auto_now_add=True)
last_update_timestamp = models.DateTimeField(auto_now=True) last_update_timestamp = models.DateTimeField(auto_now=True)
@ -61,14 +60,13 @@ class PhoneCall(models.Model):
if settings.PHONE_MAX_CALL_DURATION: if settings.PHONE_MAX_CALL_DURATION:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
start_after = now() - timedelta(minutes=settings.PHONE_MAX_CALL_DURATION) start_after = now() - timedelta(minutes=settings.PHONE_MAX_CALL_DURATION)
for call in cls.objects.filter(callee__in=PhoneLine.get_callees(user), for call in cls.objects.filter(
stop__isnull=True, callee__in=PhoneLine.get_callees(user), stop__isnull=True, start__lt=start_after
start__lt=start_after): ):
logger.info('stop expired call from %s to %s', call.caller, call.callee) logger.info('stop expired call from %s to %s', call.caller, call.callee)
call.stop = now() call.stop = now()
call.save() call.save()
return cls.objects.filter(callee__in=PhoneLine.get_callees(user), return cls.objects.filter(callee__in=PhoneLine.get_callees(user), stop__isnull=True).order_by('start')
stop__isnull=True).order_by('start')
@classmethod @classmethod
def get_all_callees(cls): def get_all_callees(cls):
@ -80,15 +78,15 @@ class PhoneCall(models.Model):
} }
def previous_calls(self): def previous_calls(self):
return PhoneCall.objects.filter(caller=self.caller).exclude( return PhoneCall.objects.filter(caller=self.caller).exclude(id=self.id).order_by('-start')[:5]
id=self.id).order_by('-start')[:5]
@property @property
def duration(self): def duration(self):
if not self.stop: if not self.stop:
return 'n.a.' return 'n.a.'
seconds = (self.stop - self.start).seconds seconds = (self.stop - self.start).seconds
return '%02d:%02d' % (seconds//60, seconds%60) return '%02d:%02d' % (seconds // 60, seconds % 60)
class PhoneLine(models.Model): class PhoneLine(models.Model):
callee = models.CharField(_('Callee'), unique=True, max_length=80) callee = models.CharField(_('Callee'), unique=True, max_length=80)

View File

@ -57,7 +57,7 @@ class PhoneZone(TemplateView):
username = self.request.session.get('mellon_session', {}).get('username') username = self.request.session.get('mellon_session', {}).get('username')
if username: if username:
# user is from SSO, username is a phone line (callee), create a link to it # user is from SSO, username is a phone line (callee), create a link to it
username = username[0].split('@', 1)[0][:80] # remove realm username = username[0].split('@', 1)[0][:80] # remove realm
if username: if username:
PhoneLine.take(callee=username, user=self.request.user) PhoneLine.take(callee=username, user=self.request.user)
context = super(PhoneZone, self).get_context_data(**kwargs) context = super(PhoneZone, self).get_context_data(**kwargs)
@ -66,50 +66,51 @@ class PhoneZone(TemplateView):
context['phonecalls'] = PhoneCall.get_current_calls(self.request.user) context['phonecalls'] = PhoneCall.get_current_calls(self.request.user)
return context return context
zone = csrf_exempt(PhoneZone.as_view())
zone = csrf_exempt(PhoneZone.as_view())
@csrf_exempt @csrf_exempt
def call_event(request): def call_event(request):
'''Log a new call start or stop, input is JSON: """Log a new call start or stop, input is JSON:
{ {
'event': 'start' or 'stop', 'event': 'start' or 'stop',
'caller': '003399999999', 'caller': '003399999999',
'callee': '102', 'callee': '102',
'data': { 'data': {
'user': 'zozo', 'user': 'zozo',
}, },
} }
''' """
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try: try:
payload = json.loads(force_text(request.body)) payload = json.loads(force_text(request.body))
assert isinstance(payload, dict), 'payload is not a JSON object' assert isinstance(payload, dict), 'payload is not a JSON object'
assert set(payload.keys()) <= set(['event', 'caller', 'callee', 'data']), \ assert set(payload.keys()) <= set(
'payload keys must be "event", "caller", "callee" and optionnaly "data"' ['event', 'caller', 'callee', 'data']
assert set(['event', 'caller', 'callee']) <= set(payload.keys()), \ ), 'payload keys must be "event", "caller", "callee" and optionnaly "data"'
'payload keys must be "event", "caller", "callee" and optionnaly "data"' assert set(['event', 'caller', 'callee']) <= set(
payload.keys()
), 'payload keys must be "event", "caller", "callee" and optionnaly "data"'
assert payload['event'] in ('start', 'stop'), 'event must be "start" or "stop"' assert payload['event'] in ('start', 'stop'), 'event must be "start" or "stop"'
assert isinstance(payload['caller'], six.string_types), 'caller must be a string' assert isinstance(payload['caller'], six.string_types), 'caller must be a string'
assert isinstance(payload['callee'], six.string_types), 'callee must be a string' assert isinstance(payload['callee'], six.string_types), 'callee must be a string'
if 'data' in payload: if 'data' in payload:
assert isinstance(payload['data'], dict), 'data must be a JSON object' assert isinstance(payload['data'], dict), 'data must be a JSON object'
except (TypeError, ValueError, AssertionError) as e: except (TypeError, ValueError, AssertionError) as e:
return HttpResponseBadRequest(json.dumps({'err': 1, 'msg': return HttpResponseBadRequest(
force_text(e)}), json.dumps({'err': 1, 'msg': force_text(e)}), content_type='application/json'
content_type='application/json') )
# janitoring: stop active calls to the callee # janitoring: stop active calls to the callee
if settings.PHONE_ONE_CALL_PER_CALLEE: if settings.PHONE_ONE_CALL_PER_CALLEE:
logger.info('stop all calls to %s', payload['callee']) logger.info('stop all calls to %s', payload['callee'])
PhoneCall.objects.filter(callee=payload['callee'], PhoneCall.objects.filter(callee=payload['callee'], stop__isnull=True).update(stop=now())
stop__isnull=True).update(stop=now())
else: else:
logger.info('stop call from %s to %s', payload['caller'], payload['callee']) logger.info('stop call from %s to %s', payload['caller'], payload['callee'])
PhoneCall.objects.filter(caller=payload['caller'], PhoneCall.objects.filter(
callee=payload['callee'], caller=payload['caller'], callee=payload['callee'], stop__isnull=True
stop__isnull=True).update(stop=now()) ).update(stop=now())
if payload['event'] == 'start': if payload['event'] == 'start':
# start a new call # start a new call
kwargs = { kwargs = {
@ -129,40 +130,39 @@ def active_call(request, *args, **kwargs):
result = { result = {
'caller': call.caller, 'caller': call.caller,
'callee': call.callee, 'callee': call.callee,
'active': not(bool(call.stop)), 'active': not (bool(call.stop)),
'start_timestamp': call.start.strftime('%Y-%m-%dT%H:%M:%S'), 'start_timestamp': call.start.strftime('%Y-%m-%dT%H:%M:%S'),
} }
return HttpResponse(json.dumps(result, indent=2), return HttpResponse(json.dumps(result, indent=2), content_type='application/json')
content_type='application/json')
@login_required @login_required
def current_calls(request): def current_calls(request):
'''Returns the list of current calls for current user as JSON: """Returns the list of current calls for current user as JSON:
{ {
'err': 0, 'err': 0,
'data': { 'data': {
'calls': [ 'calls': [
{ {
'caller': '00334545445', 'caller': '00334545445',
'callee': '102', 'callee': '102',
'data': { ... }, 'data': { ... },
}, },
... ...
], ],
'lines': [ 'lines': [
'102', '102',
], ],
'all-lines': [ 'all-lines': [
'102', '102',
], ],
} }
} }
lines are number the user is currently watching, all-lines is all lines are number the user is currently watching, all-lines is all
registered numbers. registered numbers.
''' """
all_callees = PhoneCall.get_all_callees() all_callees = PhoneCall.get_all_callees()
callees = PhoneLine.get_callees(request.user) callees = PhoneLine.get_callees(request.user)
phonecalls = PhoneCall.get_current_calls(request.user) phonecalls = PhoneCall.get_current_calls(request.user)
@ -177,11 +177,13 @@ def current_calls(request):
}, },
} }
for call in phonecalls: for call in phonecalls:
calls.append({ calls.append(
'caller': call.caller, {
'callee': call.callee, 'caller': call.caller,
'start': call.start.isoformat('T').split('.')[0], 'callee': call.callee,
}) 'start': call.start.isoformat('T').split('.')[0],
}
)
if call.data: if call.data:
calls[-1]['data'] = json.loads(call.data) calls[-1]['data'] = json.loads(call.data)
response = HttpResponse(content_type='application/json') response = HttpResponse(content_type='application/json')
@ -192,19 +194,19 @@ def current_calls(request):
@csrf_exempt @csrf_exempt
@login_required @login_required
def take_line(request): def take_line(request):
'''Take a line, input is JSON: """Take a line, input is JSON:
{ 'callee': '003369999999' } { 'callee': '003369999999' }
''' """
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try: try:
payload = json.loads(force_text(request.body)) payload = json.loads(force_text(request.body))
assert isinstance(payload, dict), 'payload is not a JSON object' assert isinstance(payload, dict), 'payload is not a JSON object'
assert list(payload.keys()) == ['callee'], 'payload must have only one key: callee' assert list(payload.keys()) == ['callee'], 'payload must have only one key: callee'
except (TypeError, ValueError, AssertionError) as e: except (TypeError, ValueError, AssertionError) as e:
return HttpResponseBadRequest(json.dumps({'err': 1, 'msg': return HttpResponseBadRequest(
force_text(e)}), json.dumps({'err': 1, 'msg': force_text(e)}), content_type='application/json'
content_type='application/json') )
PhoneLine.take(payload['callee'], request.user) PhoneLine.take(payload['callee'], request.user)
logger.info(u'user %s took line %s', request.user, payload['callee']) logger.info(u'user %s took line %s', request.user, payload['callee'])
return HttpResponse(json.dumps({'err': 0}), content_type='application/json') return HttpResponse(json.dumps({'err': 0}), content_type='application/json')
@ -213,19 +215,19 @@ def take_line(request):
@csrf_exempt @csrf_exempt
@login_required @login_required
def release_line(request): def release_line(request):
'''Release a line, input is JSON: """Release a line, input is JSON:
{ 'callee': '003369999999' } { 'callee': '003369999999' }
''' """
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try: try:
payload = json.loads(force_text(request.body)) payload = json.loads(force_text(request.body))
assert isinstance(payload, dict), 'payload is not a JSON object' assert isinstance(payload, dict), 'payload is not a JSON object'
assert list(payload.keys()) == ['callee'], 'payload must have only one key: callee' assert list(payload.keys()) == ['callee'], 'payload must have only one key: callee'
except (TypeError, ValueError, AssertionError) as e: except (TypeError, ValueError, AssertionError) as e:
return HttpResponseBadRequest(json.dumps({'err': 1, 'msg': return HttpResponseBadRequest(
force_text(e)}), json.dumps({'err': 1, 'msg': force_text(e)}), content_type='application/json'
content_type='application/json') )
PhoneLine.release(payload['callee'], request.user) PhoneLine.release(payload['callee'], request.user)
logger.info(u'user %s released line %s', request.user, payload['callee']) logger.info(u'user %s released line %s', request.user, payload['callee'])
return HttpResponse(json.dumps({'err': 0}), content_type='application/json') return HttpResponse(json.dumps({'err': 0}), content_type='application/json')

View File

@ -39,11 +39,12 @@ urlpatterns = [
url(r'^', include('welco.sources.phone.urls')), url(r'^', include('welco.sources.phone.urls')),
url(r'^', include('welco.sources.counter.urls')), url(r'^', include('welco.sources.counter.urls')),
url(r'^ajax/qualification$', welco.views.qualification, name='qualif-zone'), url(r'^ajax/qualification$', welco.views.qualification, name='qualif-zone'),
url(r'^ajax/remove-association/(?P<pk>\w+)$', url(
welco.views.remove_association, name='ajax-remove-association'), r'^ajax/remove-association/(?P<pk>\w+)$',
url(r'^ajax/create-formdata/(?P<pk>\w+)$', welco.views.remove_association,
welco.views.create_formdata, name='ajax-create-formdata'), name='ajax-remove-association',
),
url(r'^ajax/create-formdata/(?P<pk>\w+)$', welco.views.create_formdata, name='ajax-create-formdata'),
url(r'^ajax/kb$', welco.kb.views.zone, name='kb-zone'), url(r'^ajax/kb$', welco.kb.views.zone, name='kb-zone'),
url(r'^kb/$', welco.kb.views.page_list, name='kb-home'), url(r'^kb/$', welco.kb.views.page_list, name='kb-home'),
url(r'^kb/add/$', welco.kb.views.page_add, name='kb-page-add'), url(r'^kb/add/$', welco.kb.views.page_add, name='kb-page-add'),
@ -53,23 +54,27 @@ urlpatterns = [
url(r'^ajax/kb/(?P<slug>[\w-]+)/$', welco.kb.views.page_detail_fragment, name='kb-page-fragment'), url(r'^ajax/kb/(?P<slug>[\w-]+)/$', welco.kb.views.page_detail_fragment, name='kb-page-fragment'),
url(r'^kb/(?P<slug>[\w-]+)/edit$', welco.kb.views.page_edit, name='kb-page-edit'), url(r'^kb/(?P<slug>[\w-]+)/edit$', welco.kb.views.page_edit, name='kb-page-edit'),
url(r'^kb/(?P<slug>[\w-]+)/delete$', welco.kb.views.page_delete, name='kb-page-delete'), url(r'^kb/(?P<slug>[\w-]+)/delete$', welco.kb.views.page_delete, name='kb-page-delete'),
url(r'^ajax/contacts$', welco.contacts.views.zone, name='contacts-zone'), url(r'^ajax/contacts$', welco.contacts.views.zone, name='contacts-zone'),
url(r'^contacts/search/json/$', welco.contacts.views.search_json, name='contacts-search-json'), url(r'^contacts/search/json/$', welco.contacts.views.search_json, name='contacts-search-json'),
url(r'^ajax/contacts/(?P<slug>[\w-]+)/$', url(
welco.contacts.views.contact_detail_fragment, name='contact-page-fragment'), r'^ajax/contacts/(?P<slug>[\w-]+)/$',
welco.contacts.views.contact_detail_fragment,
name='contact-page-fragment',
),
url(r'^contacts/add/$', welco.contacts.views.contact_add, name='contacts-add'), url(r'^contacts/add/$', welco.contacts.views.contact_add, name='contacts-add'),
url(
url(r'^ajax/summary/(?P<source_type>\w+)/(?P<source_pk>\w+)/$', r'^ajax/summary/(?P<source_type>\w+)/(?P<source_pk>\w+)/$',
welco.views.wcs_summary, name='wcs-summary'), welco.views.wcs_summary,
name='wcs-summary',
),
url(r'^admin/', admin.site.urls), url(r'^admin/', admin.site.urls),
url(r'^logout/$', welco.views.logout, name='auth_logout'), url(r'^logout/$', welco.views.logout, name='auth_logout'),
url(r'^login/$', welco.views.login, name='auth_login'), url(r'^login/$', welco.views.login, name='auth_login'),
url(r'^menu.json$', welco.views.menu_json, name='menu_json'), url(r'^menu.json$', welco.views.menu_json, name='menu_json'),
url(r'^ckeditor/upload/', kb_manager_required(ckeditor_views.upload), name='ckeditor_upload'), url(r'^ckeditor/upload/', kb_manager_required(ckeditor_views.upload), name='ckeditor_upload'),
url(r'^ckeditor/browse/', never_cache(kb_manager_required(ckeditor_views.browse)), name='ckeditor_browse'), url(
r'^ckeditor/browse/', never_cache(kb_manager_required(ckeditor_views.browse)), name='ckeditor_browse'
),
] ]
if 'mellon' in settings.INSTALLED_APPS: if 'mellon' in settings.INSTALLED_APPS:

View File

@ -36,6 +36,7 @@ def sign_url(url, key, algo='sha256', timestamp=None, nonce=None):
new_query = sign_query(parsed.query, key, algo, timestamp, nonce) new_query = sign_query(parsed.query, key, algo, timestamp, nonce)
return urlparse.urlunparse(parsed[:4] + (new_query,) + parsed[5:]) return urlparse.urlunparse(parsed[:4] + (new_query,) + parsed[5:])
def sign_query(query, key, algo='sha256', timestamp=None, nonce=None): def sign_query(query, key, algo='sha256', timestamp=None, nonce=None):
if timestamp is None: if timestamp is None:
timestamp = datetime.datetime.utcnow() timestamp = datetime.datetime.utcnow()
@ -45,22 +46,22 @@ def sign_query(query, key, algo='sha256', timestamp=None, nonce=None):
new_query = query new_query = query
if new_query: if new_query:
new_query += '&' new_query += '&'
new_query += urlencode(( new_query += urlencode((('algo', algo), ('timestamp', timestamp), ('nonce', nonce)))
('algo', algo),
('timestamp', timestamp),
('nonce', nonce)))
signature = base64.b64encode(sign_string(new_query, key, algo=algo)) signature = base64.b64encode(sign_string(new_query, key, algo=algo))
new_query += '&signature=' + quote(signature) new_query += '&signature=' + quote(signature)
return new_query return new_query
def sign_string(s, key, algo='sha256', timedelta=30): def sign_string(s, key, algo='sha256', timedelta=30):
digestmod = getattr(hashlib, algo) digestmod = getattr(hashlib, algo)
hash = hmac.HMAC(smart_bytes(key), digestmod=digestmod, msg=smart_bytes(s)) hash = hmac.HMAC(smart_bytes(key), digestmod=digestmod, msg=smart_bytes(s))
return hash.digest() return hash.digest()
def get_wcs_services(): def get_wcs_services():
return settings.KNOWN_SERVICES.get('wcs') return settings.KNOWN_SERVICES.get('wcs')
def get_wcs_json(wcs_url, path, wcs_site, params={}): def get_wcs_json(wcs_url, path, wcs_site, params={}):
if not wcs_url.endswith('/'): if not wcs_url.endswith('/'):
wcs_url += '/' wcs_url += '/'
@ -70,13 +71,13 @@ def get_wcs_json(wcs_url, path, wcs_site, params={}):
response_json = cache.get(url) response_json = cache.get(url)
if response_json is None: if response_json is None:
signed_url = sign_url(url, wcs_site.get('secret')) signed_url = sign_url(url, wcs_site.get('secret'))
response_json = requests.get(signed_url, headers={'accept': 'application/json'}, response_json = requests.get(signed_url, headers={'accept': 'application/json'}, timeout=10).json()
timeout=10).json()
if not isinstance(response_json, dict): if not isinstance(response_json, dict):
response_json = {'data': response_json} response_json = {'data': response_json}
cache.set(url, response_json) cache.set(url, response_json)
return response_json return response_json
def get_wcs_options(url, condition=None, params={}): def get_wcs_options(url, condition=None, params={}):
categories = {} categories = {}
for wcs_key, wcs_site in get_wcs_services().items(): for wcs_key, wcs_site in get_wcs_services().items():
@ -103,6 +104,7 @@ def get_wcs_options(url, condition=None, params={}):
options.append((category, sorted(categories[category], key=lambda x: x[1]))) options.append((category, sorted(categories[category], key=lambda x: x[1])))
return options return options
def get_wcs_formdef_details(formdef_reference): def get_wcs_formdef_details(formdef_reference):
wcs_key, form_slug = formdef_reference.split(':') wcs_key, form_slug = formdef_reference.split(':')
wcs_site = get_wcs_services()[wcs_key] wcs_site = get_wcs_services()[wcs_key]
@ -113,6 +115,7 @@ def get_wcs_formdef_details(formdef_reference):
return form return form
return None return None
def push_wcs_formdata(request, formdef_reference, context=None): def push_wcs_formdata(request, formdef_reference, context=None):
wcs_key, form_slug = formdef_reference.split(':') wcs_key, form_slug = formdef_reference.split(':')
wcs_site = get_wcs_services()[wcs_key] wcs_site = get_wcs_services()[wcs_key]
@ -121,7 +124,7 @@ def push_wcs_formdata(request, formdef_reference, context=None):
wcs_site_url += '/' wcs_site_url += '/'
url = wcs_site_url + 'api/formdefs/%s/schema' % form_slug url = wcs_site_url + 'api/formdefs/%s/schema' % form_slug
response = requests.get(url) response = requests.get(url)
create_draft = not(bool('welco-direct' in (response.json().get('keywords') or ''))) create_draft = not (bool('welco-direct' in (response.json().get('keywords') or '')))
url = wcs_site_url + 'api/formdefs/%s/submit?' % form_slug url = wcs_site_url + 'api/formdefs/%s/submit?' % form_slug
data = { data = {
@ -139,8 +142,7 @@ def push_wcs_formdata(request, formdef_reference, context=None):
url = sign_url(url, wcs_site.get('secret')) url = sign_url(url, wcs_site.get('secret'))
response = requests.post(url, data=json.dumps(data), response = requests.post(url, data=json.dumps(data), headers={'Content-type': 'application/json'})
headers={'Content-type': 'application/json'})
if response.json().get('err') != 0: if response.json().get('err') != 0:
raise Exception('error %r' % response.content) raise Exception('error %r' % response.content)
data = response.json()['data'] data = response.json()['data']
@ -169,6 +171,7 @@ def get_wcs_data(endpoint, params=None):
json_response = {'data': json_response} json_response = {'data': json_response}
return json_response return json_response
def response_for_json(request, data): def response_for_json(request, data):
json_str = json.dumps(data) json_str = json.dumps(data)
for variable in ('jsonpCallback', 'callback'): for variable in ('jsonpCallback', 'callback'):

View File

@ -88,19 +88,22 @@ class Qualification(TemplateView):
context['source_pk'] = self.request.GET['source_pk'] context['source_pk'] = self.request.GET['source_pk']
if self.request.GET.get('source_pk'): if self.request.GET.get('source_pk'):
context['associations'] = Association.objects.filter( context['associations'] = Association.objects.filter(
source_type=ContentType.objects.get(id=self.request.GET['source_type']), source_type=ContentType.objects.get(id=self.request.GET['source_type']),
source_pk=self.request.GET['source_pk']).order_by('id') source_pk=self.request.GET['source_pk'],
).order_by('id')
return context return context
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
association = Association( association = Association(
source_type=ContentType.objects.get(id=request.POST['source_type']), source_type=ContentType.objects.get(id=request.POST['source_type']),
source_pk=request.POST['source_pk']) source_pk=request.POST['source_pk'],
)
association.formdef_reference = request.POST['formdef_reference'] association.formdef_reference = request.POST['formdef_reference']
association.save() association.save()
request.GET = request.POST request.GET = request.POST
return self.get(request) return self.get(request)
qualification = csrf_exempt(Qualification.as_view()) qualification = csrf_exempt(Qualification.as_view())
@ -117,8 +120,7 @@ class ChannelHome(TemplateView):
if not self.check_user_ok(): if not self.check_user_ok():
raise PermissionDenied() raise PermissionDenied()
context = super(ChannelHome, self).get_context_data(**kwargs) context = super(ChannelHome, self).get_context_data(**kwargs)
context['panels'] = [ context['panels'] = [{'key': x, 'zone_url': x + '-zone'} for x in settings.SCREEN_PANELS]
{'key': x, 'zone_url': x + '-zone'} for x in settings.SCREEN_PANELS]
context['source'] = self.source_klass(self.request, **kwargs) context['source'] = self.source_klass(self.request, **kwargs)
context['kb'] = KbHomeZone(self.request) context['kb'] = KbHomeZone(self.request)
context['contacts'] = ContactsHomeZone(self.request) context['contacts'] = ContactsHomeZone(self.request)
@ -141,20 +143,25 @@ def home(request):
return HttpResponseRedirect('%s/' % channel) return HttpResponseRedirect('%s/' % channel)
raise PermissionDenied() raise PermissionDenied()
class HomePhone(ChannelHome): class HomePhone(ChannelHome):
source_klass = PhoneHome source_klass = PhoneHome
home_phone = login_required(HomePhone.as_view()) home_phone = login_required(HomePhone.as_view())
class HomeMail(ChannelHome): class HomeMail(ChannelHome):
source_klass = MailHome source_klass = MailHome
home_mail = login_required(HomeMail.as_view()) home_mail = login_required(HomeMail.as_view())
class HomeCounter(ChannelHome): class HomeCounter(ChannelHome):
source_klass = CounterHome source_klass = CounterHome
home_counter = login_required(HomeCounter.as_view()) home_counter = login_required(HomeCounter.as_view())
@ -174,11 +181,13 @@ def wcs_summary(request, *args, **kwargs):
break break
return HttpResponse(json_str, content_type='application/javascript') return HttpResponse(json_str, content_type='application/javascript')
@login_required @login_required
def remove_association(request, *args, **kwargs): def remove_association(request, *args, **kwargs):
Association.objects.filter(id=kwargs.get('pk')).delete() Association.objects.filter(id=kwargs.get('pk')).delete()
return HttpResponseRedirect(resolve_url('home')) return HttpResponseRedirect(resolve_url('home'))
@login_required @login_required
@csrf_exempt @csrf_exempt
def create_formdata(request, *args, **kwargs): def create_formdata(request, *args, **kwargs):
@ -196,6 +205,7 @@ def create_formdata(request, *args, **kwargs):
json.dump({'result': 'ok', 'url': qualif.formdata_url}, response) json.dump({'result': 'ok', 'url': qualif.formdata_url}, response)
return response return response
@login_required @login_required
def menu_json(request): def menu_json(request):
response = HttpResponse(content_type='application/json') response = HttpResponse(content_type='application/json')
@ -209,17 +219,21 @@ def menu_json(request):
for channel in settings.CHANNEL_ROLES: for channel in settings.CHANNEL_ROLES:
channel_groups = set(settings.CHANNEL_ROLES[channel]) channel_groups = set(settings.CHANNEL_ROLES[channel])
if user_groups.intersection(channel_groups): if user_groups.intersection(channel_groups):
menu.append({ menu.append(
'label': force_text(labels.get(channel)), {
'slug': channel, 'label': force_text(labels.get(channel)),
'url': request.build_absolute_uri(reverse('home-%s' % channel)), 'slug': channel,
}) 'url': request.build_absolute_uri(reverse('home-%s' % channel)),
}
)
if check_kb_user_perms(request.user, access=True): if check_kb_user_perms(request.user, access=True):
menu.append({ menu.append(
'label': force_text(_('Knowledge Base')), {
'slug': 'book', 'label': force_text(_('Knowledge Base')),
'url': request.build_absolute_uri(reverse('kb-home')) 'slug': 'book',
}) 'url': request.build_absolute_uri(reverse('kb-home')),
}
)
json_str = json.dumps(menu) json_str = json.dumps(menu)
for variable in ('jsonpCallback', 'callback'): for variable in ('jsonpCallback', 'callback'):
if variable in request.GET: if variable in request.GET:

View File

@ -8,7 +8,9 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
""" """
import os import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "welco.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "welco.settings")
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
application = get_wsgi_application() application = get_wsgi_application()