0004-implement-telphony-models-and-web-services-fixes-878.patch
tests/test_source_phone.py | ||
---|---|---|
1 |
# welco - multichannel request processing |
|
2 |
# Copyright (C) 2015 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import json |
|
18 | ||
19 |
import pytest |
|
20 | ||
21 |
from django.core.urlresolvers import reverse |
|
22 | ||
23 |
from welco.sources.phone import models |
|
24 | ||
25 |
pytestmark = pytest.mark.django_db |
|
26 | ||
27 | ||
28 |
@pytest.fixture |
|
29 |
def user(): |
|
30 |
from django.contrib.auth.models import User |
|
31 | ||
32 |
user = User.objects.create(username='toto') |
|
33 |
user.set_password('toto') |
|
34 |
user.save() |
|
35 |
return user |
|
36 | ||
37 | ||
38 |
def test_call_start_stop(client): |
|
39 |
assert models.PhoneCall.objects.count() == 0 |
|
40 |
payload = { |
|
41 |
'event': 'start', |
|
42 |
'caller': '0033699999999', |
|
43 |
'callee': '102', |
|
44 |
'data': { |
|
45 |
'user': 'boby.lapointe', |
|
46 |
} |
|
47 |
} |
|
48 |
response = client.post(reverse('phone-call-event'), json.dumps(payload), |
|
49 |
content_type='application/json') |
|
50 |
assert response.status_code == 200 |
|
51 |
assert response['content-type'] == 'application/json' |
|
52 |
assert json.loads(response.content) == {'err': 0} |
|
53 |
assert models.PhoneCall.objects.count() == 1 |
|
54 |
assert models.PhoneCall.objects.filter( |
|
55 |
caller='0033699999999', |
|
56 |
callee='102', |
|
57 |
data=json.dumps(payload['data']), stop__isnull=True).count() == 1 |
|
58 |
# new start event |
|
59 |
response = client.post(reverse('phone-call-event'), json.dumps(payload), |
|
60 |
content_type='application/json') |
|
61 |
assert response.status_code == 200 |
|
62 |
assert response['content-type'] == 'application/json' |
|
63 |
assert json.loads(response.content) == {'err': 0} |
|
64 |
assert models.PhoneCall.objects.count() == 2 |
|
65 |
assert models.PhoneCall.objects.filter( |
|
66 |
caller='0033699999999', |
|
67 |
callee='102', |
|
68 |
data=json.dumps(payload['data']), stop__isnull=True).count() == 1 |
|
69 |
# first call has been closed |
|
70 |
assert models.PhoneCall.objects.filter( |
|
71 |
caller='0033699999999', |
|
72 |
callee='102', |
|
73 |
data=json.dumps(payload['data']), stop__isnull=False).count() == 1 |
|
74 |
payload['event'] = 'stop' |
|
75 |
response = client.post(reverse('phone-call-event'), json.dumps(payload), |
|
76 |
content_type='application/json') |
|
77 |
assert response.status_code == 200 |
|
78 |
assert response['content-type'] == 'application/json' |
|
79 |
assert json.loads(response.content) == {'err': 0} |
|
80 |
assert models.PhoneCall.objects.count() == 2 |
|
81 |
assert models.PhoneCall.objects.filter( |
|
82 |
caller='0033699999999', |
|
83 |
callee='102', |
|
84 |
data=json.dumps(payload['data']), stop__isnull=False).count() == 2 |
|
85 |
# stop is idempotent |
|
86 |
response = client.post(reverse('phone-call-event'), json.dumps(payload), |
|
87 |
content_type='application/json') |
|
88 |
assert response.status_code == 200 |
|
89 |
assert response['content-type'] == 'application/json' |
|
90 |
assert json.loads(response.content) == {'err': 0} |
|
91 |
assert models.PhoneCall.objects.count() == 2 |
|
92 |
assert models.PhoneCall.objects.filter( |
|
93 |
caller='0033699999999', |
|
94 |
callee='102', |
|
95 |
data=json.dumps(payload['data']), stop__isnull=False).count() == 2 |
|
96 | ||
97 | ||
98 |
def test_current_calls(user, client): |
|
99 |
# create some calls |
|
100 |
for number in range(0, 10): |
|
101 |
payload = { |
|
102 |
'event': 'start', |
|
103 |
'caller': '00336999999%02d' % number, |
|
104 |
'callee': '1%02d' % number, |
|
105 |
'data': { |
|
106 |
'user': 'boby.lapointe', |
|
107 |
} |
|
108 |
} |
|
109 |
response = client.post(reverse('phone-call-event'), json.dumps(payload), |
|
110 |
content_type='application/json') |
|
111 |
assert response.status_code == 200 |
|
112 |
assert response['content-type'] == 'application/json' |
|
113 |
assert json.loads(response.content) == {'err': 0} |
|
114 | ||
115 |
# register user to some lines |
|
116 |
# then remove from some |
|
117 |
for number in range(0, 10): |
|
118 |
models.PhoneLine.take(callee='1%02d' % number, user=user) |
|
119 |
for number in range(5, 10): |
|
120 |
models.PhoneLine.release(callee='1%02d' % number, user=user) |
|
121 |
client.login(username='toto', password='toto') |
|
122 |
response = client.get(reverse('phone-current-calls')) |
|
123 |
assert response.status_code == 200 |
|
124 |
assert response['content-type'] == 'application/json' |
|
125 |
payload = json.loads(response.content) |
|
126 |
assert isinstance(payload, dict) |
|
127 |
assert set(payload.keys()) == set(['err', 'data']) |
|
128 |
assert payload['err'] == 0 |
|
129 |
data = payload['data'] |
|
130 |
assert set(data.keys()) == set(['calls', 'lines', 'all-lines']) |
|
131 |
assert isinstance(data['calls'], list) |
|
132 |
assert isinstance(data['lines'], list) |
|
133 |
assert isinstance(data['all-lines'], list) |
|
134 |
assert len(data['calls']) == 5 |
|
135 |
assert len(data['lines']) == 5 |
|
136 |
assert len(data['all-lines']) == 10 |
|
137 |
for call in data['calls']: |
|
138 |
assert set(call.keys()) <= set(['caller', 'callee', 'start', 'data']) |
|
139 |
assert isinstance(call['caller'], unicode) |
|
140 |
assert isinstance(call['callee'], unicode) |
|
141 |
assert isinstance(call['start'], unicode) |
|
142 |
if 'data' in call: |
|
143 |
assert isinstance(call['data'], dict) |
|
144 |
assert len([call for call in data['lines'] if isinstance(call, unicode)]) == 5 |
|
145 |
assert len([call for call in data['all-lines'] if isinstance(call, unicode)]) == 10 |
|
146 | ||
147 |
# unregister user to all remaining lines |
|
148 |
for number in range(0, 5): |
|
149 |
models.PhoneLine.release(callee='1%02d' % number, user=user) |
|
150 |
response = client.get(reverse('phone-current-calls')) |
|
151 |
assert response.status_code == 200 |
|
152 |
assert response['content-type'] == 'application/json' |
|
153 |
payload = json.loads(response.content) |
|
154 |
assert isinstance(payload, dict) |
|
155 |
assert set(payload.keys()) == set(['err', 'data']) |
|
156 |
assert payload['err'] == 0 |
|
157 |
assert set(payload['data'].keys()) == set(['calls', 'lines', 'all-lines']) |
|
158 |
assert len(payload['data']['calls']) == 0 |
|
159 |
assert len(payload['data']['lines']) == 0 |
|
160 |
assert len(payload['data']['all-lines']) == 10 |
|
161 | ||
162 | ||
163 |
def test_take_release_line(user, client): |
|
164 |
client.login(username='toto', password='toto') |
|
165 | ||
166 |
assert models.PhoneLine.objects.count() == 0 |
|
167 |
payload = { |
|
168 |
'callee': '102', |
|
169 |
} |
|
170 |
response = client.post(reverse('phone-take-line'), json.dumps(payload), |
|
171 |
content_type='application/json') |
|
172 |
assert response.status_code == 200 |
|
173 |
assert response['content-type'] == 'application/json' |
|
174 |
assert json.loads(response.content) == {'err': 0} |
|
175 |
assert models.PhoneLine.objects.count() == 1 |
|
176 |
assert models.PhoneLine.objects.filter( |
|
177 |
users=user, callee='102').count() == 1 |
|
178 |
response = client.post(reverse('phone-release-line'), json.dumps(payload), |
|
179 |
content_type='application/json') |
|
180 |
assert response.status_code == 200 |
|
181 |
assert response['content-type'] == 'application/json' |
|
182 |
assert json.loads(response.content) == {'err': 0} |
|
183 |
assert models.PhoneLine.objects.count() == 1 |
|
184 |
assert models.PhoneLine.objects.filter( |
|
185 |
users=user, callee='102').count() == 0 |
welco/sources/phone/migrations/0001_initial.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.db import migrations, models |
|
5 | ||
6 | ||
7 |
class Migration(migrations.Migration): |
|
8 | ||
9 |
dependencies = [ |
|
10 |
] |
|
11 | ||
12 |
operations = [ |
|
13 |
migrations.CreateModel( |
|
14 |
name='PhoneCall', |
|
15 |
fields=[ |
|
16 |
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
|
17 |
('number', models.CharField(max_length=20, verbose_name='Number')), |
|
18 |
('creation_timestamp', models.DateTimeField(auto_now_add=True)), |
|
19 |
('last_update_timestamp', models.DateTimeField(auto_now=True)), |
|
20 |
], |
|
21 |
options={ |
|
22 |
'verbose_name': 'Phone Call', |
|
23 |
}, |
|
24 |
), |
|
25 |
] |
welco/sources/phone/migrations/0002_auto_20151028_1635.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.db import migrations, models |
|
5 |
from django.utils.timezone import utc |
|
6 |
from django.utils.timezone import now |
|
7 |
import datetime |
|
8 |
from django.conf import settings |
|
9 | ||
10 | ||
11 |
class Migration(migrations.Migration): |
|
12 | ||
13 |
dependencies = [ |
|
14 |
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
|
15 |
('phone', '0001_initial'), |
|
16 |
] |
|
17 | ||
18 |
operations = [ |
|
19 |
migrations.CreateModel( |
|
20 |
name='PhoneLine', |
|
21 |
fields=[ |
|
22 |
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
|
23 |
('callee', models.CharField(unique=True, max_length=20, verbose_name='Callee')), |
|
24 |
('users', models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name='User')), |
|
25 |
], |
|
26 |
), |
|
27 |
migrations.RemoveField( |
|
28 |
model_name='phonecall', |
|
29 |
name='creation_timestamp', |
|
30 |
), |
|
31 |
migrations.RemoveField( |
|
32 |
model_name='phonecall', |
|
33 |
name='last_update_timestamp', |
|
34 |
), |
|
35 |
migrations.RemoveField( |
|
36 |
model_name='phonecall', |
|
37 |
name='number', |
|
38 |
), |
|
39 |
migrations.AddField( |
|
40 |
model_name='phonecall', |
|
41 |
name='callee', |
|
42 |
field=models.CharField(default='0', max_length=20, verbose_name='Callee'), |
|
43 |
preserve_default=False, |
|
44 |
), |
|
45 |
migrations.AddField( |
|
46 |
model_name='phonecall', |
|
47 |
name='caller', |
|
48 |
field=models.CharField(default='0', max_length=20, verbose_name='Caller'), |
|
49 |
preserve_default=False, |
|
50 |
), |
|
51 |
migrations.AddField( |
|
52 |
model_name='phonecall', |
|
53 |
name='data', |
|
54 |
field=models.TextField(verbose_name='Data', blank=True), |
|
55 |
), |
|
56 |
migrations.AddField( |
|
57 |
model_name='phonecall', |
|
58 |
name='start', |
|
59 |
field=models.DateTimeField(default=now, verbose_name='Start', auto_now_add=True), |
|
60 |
preserve_default=False, |
|
61 |
), |
|
62 |
migrations.AddField( |
|
63 |
model_name='phonecall', |
|
64 |
name='stop', |
|
65 |
field=models.DateTimeField(null=True, verbose_name='Stop', blank=True), |
|
66 |
), |
|
67 |
] |
welco/sources/phone/models.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import subprocess |
|
18 | ||
19 | 17 |
from django.core.urlresolvers import reverse |
20 | 18 |
from django.db import models |
21 |
from django.db.models.signals import post_save |
|
22 |
from django.dispatch import receiver |
|
23 | 19 |
from django.utils.translation import ugettext_lazy as _ |
24 | 20 | |
21 | ||
25 | 22 |
class PhoneCall(models.Model): |
26 | 23 | |
27 | 24 |
class Meta: |
28 | 25 |
verbose_name = _('Phone Call') |
29 | 26 | |
30 |
number = models.CharField(_('Number'), max_length=20) |
|
31 | ||
32 |
creation_timestamp = models.DateTimeField(auto_now_add=True) |
|
33 |
last_update_timestamp = models.DateTimeField(auto_now=True) |
|
27 |
caller = models.CharField(_('Caller'), max_length=20) |
|
28 |
callee = models.CharField(_('Callee'), max_length=20) |
|
29 |
start = models.DateTimeField(_('Start'), auto_now_add=True) |
|
30 |
stop = models.DateTimeField(_('Stop'), null=True, blank=True) |
|
31 |
data = models.TextField(_('Data'), blank=True) |
|
34 | 32 | |
35 | 33 |
@classmethod |
36 | 34 |
def get_qualification_form_class(cls): |
... | ... | |
47 | 45 |
return { |
48 | 46 |
'channel': 'phone', |
49 | 47 |
} |
48 | ||
49 | ||
50 |
class PhoneLine(models.Model): |
|
51 |
callee = models.CharField(_('Callee'), unique=True, max_length=20) |
|
52 |
users = models.ManyToManyField('auth.User', verbose_name=_('User')) |
|
53 | ||
54 |
@classmethod |
|
55 |
def take(cls, callee, user): |
|
56 |
line, created = cls.objects.get_or_create(callee=callee) |
|
57 |
line.users.add(user) |
|
58 | ||
59 |
@classmethod |
|
60 |
def release(cls, callee, user): |
|
61 |
line, created = cls.objects.get_or_create(callee=callee) |
|
62 |
line.users.remove(user) |
welco/sources/phone/urls.py | ||
---|---|---|
1 |
# welco - multichannel request processing |
|
2 |
# Copyright (C) 2015 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from django.conf.urls import patterns, url |
|
18 | ||
19 |
from . import views |
|
20 | ||
21 |
urlpatterns = patterns('', |
|
22 |
url(r'^phone/call-event/$', views.call_event, |
|
23 |
name='phone-call-event'), |
|
24 |
url(r'^phone/current-calls/$', views.current_calls, |
|
25 |
name='phone-current-calls'), |
|
26 |
url(r'^phone/take-line/$', views.take_line, |
|
27 |
name='phone-take-line'), |
|
28 |
url(r'^phone/release-line/$', views.release_line, |
|
29 |
name='phone-release-line'),) |
welco/sources/phone/views.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import json |
|
18 |
import logging |
|
19 | ||
17 | 20 |
from django import template |
18 | 21 |
from django.contrib.contenttypes.models import ContentType |
19 | 22 |
from django.template import RequestContext |
23 |
from django.views.decorators.csrf import csrf_exempt |
|
24 |
from django.contrib.auth.decorators import login_required |
|
25 |
from django.http import HttpResponseBadRequest, HttpResponse |
|
26 |
from django.utils.timezone import now |
|
27 | ||
28 |
from .models import PhoneCall, PhoneLine |
|
20 | 29 | |
21 |
from .models import PhoneCall |
|
22 | 30 | |
23 | 31 |
class Home(object): |
24 | 32 |
source_key = 'phone' |
... | ... | |
31 | 39 |
context['source_type'] = ContentType.objects.get_for_model(PhoneCall) |
32 | 40 |
tmpl = template.loader.get_template('welco/phone_home.html') |
33 | 41 |
return tmpl.render(context) |
42 | ||
43 | ||
44 |
@csrf_exempt |
|
45 |
def call_event(request): |
|
46 |
'''Log a new call start or stop, input is JSON: |
|
47 | ||
48 |
{ |
|
49 |
'event': 'start' or 'stop', |
|
50 |
'caller': '003399999999', |
|
51 |
'callee': '102', |
|
52 |
'data': { |
|
53 |
'user': 'zozo', |
|
54 |
}, |
|
55 |
} |
|
56 |
''' |
|
57 |
logger = logging.getLogger(__name__) |
|
58 |
try: |
|
59 |
payload = json.loads(request.body) |
|
60 |
assert isinstance(payload, dict), 'payload is not a JSON object' |
|
61 |
assert set(payload.keys()) <= set(['event', 'caller', 'callee', 'data']), \ |
|
62 |
'payload keys must be "event", "caller", "callee" and optionnaly "data"' |
|
63 |
assert set(['event', 'caller', 'callee']) <= set(payload.keys()), \ |
|
64 |
'payload keys must be "event", "caller", "callee" and optionnaly "data"' |
|
65 |
assert payload['event'] in ('start', 'stop'), 'event must be "start" or "stop"' |
|
66 |
assert isinstance(payload['caller'], unicode), 'caller must be a string' |
|
67 |
assert isinstance(payload['callee'], unicode), 'callee must be a string' |
|
68 |
if 'data' in payload: |
|
69 |
assert isinstance(payload['data'], dict), 'data must be a JSON object' |
|
70 |
except (TypeError, ValueError, AssertionError), e: |
|
71 |
return HttpResponseBadRequest(json.dumps({'err': 1, 'msg': |
|
72 |
unicode(e)}), |
|
73 |
content_type='application/json') |
|
74 |
# terminate all existing calls between these two endpoints |
|
75 |
PhoneCall.objects.filter(caller=payload['caller'], |
|
76 |
callee=payload['callee'], stop__isnull=True) \ |
|
77 |
.update(stop=now()) |
|
78 |
if payload['event'] == 'start': |
|
79 |
# start a new call |
|
80 |
kwargs = { |
|
81 |
'caller': payload['caller'], |
|
82 |
'callee': payload['callee'], |
|
83 |
} |
|
84 |
if 'data' in payload: |
|
85 |
kwargs['data'] = json.dumps(payload['data']) |
|
86 |
PhoneCall.objects.create(**kwargs) |
|
87 |
logger.info('start call from %s to %s', payload['caller'], payload['callee']) |
|
88 |
else: |
|
89 |
logger.info('stop call from %s to %s', payload['caller'], payload['callee']) |
|
90 |
return HttpResponse(json.dumps({'err': 0}), content_type='application/json') |
|
91 | ||
92 | ||
93 |
@login_required |
|
94 |
def current_calls(request): |
|
95 |
'''Returns the list of current calls for current user as JSON: |
|
96 | ||
97 |
{ |
|
98 |
'err': 0, |
|
99 |
'data': { |
|
100 |
'calls': [ |
|
101 |
{ |
|
102 |
'caller': '00334545445', |
|
103 |
'callee': '102', |
|
104 |
'data': { ... }, |
|
105 |
}, |
|
106 |
... |
|
107 |
], |
|
108 |
'lines': [ |
|
109 |
'102', |
|
110 |
], |
|
111 |
'all-lines': [ |
|
112 |
'102', |
|
113 |
], |
|
114 |
} |
|
115 |
} |
|
116 | ||
117 |
lines are number the user is currently watching, all-lines is all |
|
118 |
registered numbers. |
|
119 |
''' |
|
120 |
callees = PhoneLine.objects.filter(users=request.user) \ |
|
121 |
.values_list('callee', flat=True) |
|
122 |
all_callees = PhoneCall.objects.values_list('callee', flat=True).distinct() |
|
123 |
phonecalls = PhoneCall.objects.filter(callee__in=callees, |
|
124 |
stop__isnull=True).order_by('start') |
|
125 |
calls = [] |
|
126 |
payload = { |
|
127 |
'err': 0, |
|
128 |
'data': { |
|
129 |
'calls': calls, |
|
130 |
'lines': list(callees), |
|
131 |
'all-lines': list(all_callees), |
|
132 |
}, |
|
133 |
} |
|
134 |
for call in phonecalls: |
|
135 |
calls.append({ |
|
136 |
'caller': call.caller, |
|
137 |
'callee': call.callee, |
|
138 |
'start': call.start.isoformat('T').split('.')[0], |
|
139 |
}) |
|
140 |
if call.data: |
|
141 |
calls[-1]['data'] = json.loads(call.data) |
|
142 |
response = HttpResponse(content_type='application/json') |
|
143 |
json.dump(payload, response, indent=2) |
|
144 |
return response |
|
145 | ||
146 | ||
147 |
@login_required |
|
148 |
def take_line(request): |
|
149 |
'''Take a line, input is JSON: |
|
150 | ||
151 |
{ 'callee': '003369999999' } |
|
152 |
''' |
|
153 |
logger = logging.getLogger(__name__) |
|
154 |
try: |
|
155 |
payload = json.loads(request.body) |
|
156 |
assert isinstance(payload, dict), 'payload is not a JSON object' |
|
157 |
assert payload.keys() == ['callee'], 'payload must have only one key: callee' |
|
158 |
except (TypeError, ValueError, AssertionError), e: |
|
159 |
return HttpResponseBadRequest(json.dumps({'err': 1, 'msg': |
|
160 |
unicode(e)}), |
|
161 |
content_type='application/json') |
|
162 |
PhoneLine.take(payload['callee'], request.user) |
|
163 |
logger.info(u'user %s took line %s', request.user, payload['callee']) |
|
164 |
return HttpResponse(json.dumps({'err': 0}), content_type='application/json') |
|
165 | ||
166 | ||
167 |
@login_required |
|
168 |
def release_line(request): |
|
169 |
'''Release a line, input is JSON: |
|
170 | ||
171 |
{ 'callee': '003369999999' } |
|
172 |
''' |
|
173 |
logger = logging.getLogger(__name__) |
|
174 |
try: |
|
175 |
payload = json.loads(request.body) |
|
176 |
assert isinstance(payload, dict), 'payload is not a JSON object' |
|
177 |
assert payload.keys() == ['callee'], 'payload must have only one key: callee' |
|
178 |
except (TypeError, ValueError, AssertionError), e: |
|
179 |
return HttpResponseBadRequest(json.dumps({'err': 1, 'msg': |
|
180 |
unicode(e)}), |
|
181 |
content_type='application/json') |
|
182 |
PhoneLine.release(payload['callee'], request.user) |
|
183 |
logger.info(u'user %s released line %s', request.user, payload['callee']) |
|
184 |
return HttpResponse(json.dumps({'err': 0}), content_type='application/json') |
welco/urls.py | ||
---|---|---|
24 | 24 |
urlpatterns = patterns('', |
25 | 25 |
url(r'^$', 'welco.views.home', name='home'), |
26 | 26 |
url(r'^phone/$', 'welco.views.home_phone', name='home-phone'), |
27 |
url(r'^phone/', include('welco.sources.phone.urls')), |
|
27 | 28 |
url(r'^ajax/qualification$', 'welco.views.qualification', name='qualif-zone'), |
28 | 29 |
url(r'^ajax/qualification-done$', 'welco.views.qualification_done', name='qualif-done'), |
29 | 30 |
url(r'^ajax/remove-association/(?P<pk>\w+)$', |
30 |
- |