general: add generic endpoint view (#11204)
This commit is contained in:
parent
b2be2c604f
commit
e644b3cfc8
|
@ -1,8 +1,13 @@
|
|||
import requests
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from passerelle.base.models import BaseResource
|
||||
from passerelle.utils.api import endpoint
|
||||
|
||||
|
||||
class BaseAdresse(BaseResource):
|
||||
|
@ -22,3 +27,64 @@ class BaseAdresse(BaseResource):
|
|||
@classmethod
|
||||
def get_icon_class(cls):
|
||||
return 'gis'
|
||||
|
||||
@endpoint()
|
||||
def search(self, request, q, format='json'):
|
||||
if format != 'json':
|
||||
raise NotImplementedError()
|
||||
|
||||
scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.service_url)
|
||||
path = '/search/'
|
||||
query = urllib.urlencode({'q': q, 'limit': 1})
|
||||
url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
|
||||
|
||||
result_response = requests.get(url)
|
||||
result = []
|
||||
|
||||
for feature in result_response.json().get('features'):
|
||||
if not feature['geometry']['type'] == 'Point':
|
||||
continue # skip unknown
|
||||
result.append({
|
||||
'lon': str(feature['geometry']['coordinates'][0]),
|
||||
'lat': str(feature['geometry']['coordinates'][1]),
|
||||
'display_name': feature['properties']['label'],
|
||||
})
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
@endpoint()
|
||||
def reverse(self, request, lat, lon, format='json'):
|
||||
response_format = 'json'
|
||||
if format != 'json':
|
||||
raise NotImplementedError()
|
||||
|
||||
scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.service_url)
|
||||
path = '/reverse/'
|
||||
query = urllib.urlencode({'lat': lat, 'lon': lon})
|
||||
url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
|
||||
|
||||
result_response = requests.get(url)
|
||||
result = None
|
||||
|
||||
for feature in result_response.json().get('features'):
|
||||
if not feature['geometry']['type'] == 'Point':
|
||||
continue # skip unknown
|
||||
result = {}
|
||||
result['lon'] = str(feature['geometry']['coordinates'][0])
|
||||
result['lat'] = str(feature['geometry']['coordinates'][1])
|
||||
result['address'] = {'country': 'France'}
|
||||
for prop in feature['properties']:
|
||||
if prop in ('city', 'postcode'):
|
||||
result['address'][prop] = feature['properties'][prop]
|
||||
elif prop == 'housenumber':
|
||||
result['address']['house_number'] = feature['properties'][prop]
|
||||
elif prop == 'label':
|
||||
result['display_name'] = feature['properties'][prop]
|
||||
elif prop == 'name':
|
||||
house_number = feature['properties'].get('housenumber')
|
||||
value = feature['properties'][prop]
|
||||
if house_number and value.startswith(house_number):
|
||||
value = value[len(house_number):].strip()
|
||||
result['address']['road'] = value
|
||||
return result
|
||||
|
|
|
@ -19,10 +19,10 @@ format.
|
|||
</p>
|
||||
|
||||
<ul>
|
||||
<li>{% trans 'Geocoding:' %} <a href="{% url 'base_adresse-search' slug=object.slug %}?q=169 rue du chateau, paris&format=json"
|
||||
>{{ site_base_uri }}{% url 'base_adresse-search' slug=object.slug %}</a>?q=<i>q</i>&format=json</li>
|
||||
<li>{% trans 'Reverse geocoding:' %} <a href="{% url 'base_adresse-reverse' slug=object.slug %}?lat=48.833708&lon=2.323349&format=json"
|
||||
>{{ site_base_uri }}{% url 'base_adresse-reverse' slug=object.slug %}</a>?lat=<i>lat</i>&lon=<i>lon</i>&format=json</li>
|
||||
<li>{% trans 'Geocoding:' %} <a href="search?q=169 rue du chateau, paris&format=json"
|
||||
>{{ site_base_uri }}{{ object.get_absolute_url }}search</a>?q=<i>q</i>&format=json</li>
|
||||
<li>{% trans 'Reverse geocoding:' %} <a href="reverse?lat=48.833708&lon=2.323349&format=json"
|
||||
>{{ site_base_uri }}{{ object.get_absolute_url }}reverse</a>?lat=<i>lat</i>&lon=<i>lon</i>&format=json</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -7,8 +7,5 @@ from views import *
|
|||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^(?P<slug>[\w,-]+)/$', BaseAdresseDetailView.as_view(), name='base_adresse-view'),
|
||||
|
||||
url(r'^(?P<slug>[\w,-]+)/search$', SearchView.as_view(), name='base_adresse-search'),
|
||||
url(r'^(?P<slug>[\w,-]+)/search/(?P<path>.*)$', SearchPathView.as_view(), name='base_adresse-path-search'),
|
||||
url(r'^(?P<slug>[\w,-]+)/reverse$', ReverseView.as_view(), name='base_adresse-reverse'),
|
||||
)
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
import requests
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.views.generic.base import View
|
||||
from django.views.generic.detail import SingleObjectMixin, DetailView
|
||||
|
||||
from passerelle import utils
|
||||
|
||||
from django.views.generic.detail import DetailView
|
||||
from passerelle.views import GenericEndpointView
|
||||
from .models import BaseAdresse
|
||||
|
||||
|
||||
|
@ -20,91 +12,15 @@ class BaseAdresseDetailView(DetailView):
|
|||
return context
|
||||
|
||||
|
||||
class SearchView(View, SingleObjectMixin):
|
||||
model = BaseAdresse
|
||||
class SearchPathView(GenericEndpointView):
|
||||
def get_connector(self, **kwargs):
|
||||
return 'base_adresse'
|
||||
|
||||
def get_query(self, request, *args, **kwargs):
|
||||
if not 'q' in request.GET:
|
||||
return HttpResponseBadRequest('missing parameter')
|
||||
return request.GET['q']
|
||||
def get_params(self, request, *args, **kwargs):
|
||||
params = super(SearchPathView, self).get_params(request, *args, **kwargs)
|
||||
params['q'] = kwargs.pop('path')
|
||||
return params
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
response_format = 'json'
|
||||
if 'format' in request.GET:
|
||||
response_format = request.GET['format']
|
||||
if response_format != 'json':
|
||||
raise NotImplementedError()
|
||||
|
||||
scheme, netloc, path, params, query, fragment = urlparse.urlparse(
|
||||
self.get_object().service_url)
|
||||
path = '/search/'
|
||||
query = urllib.urlencode({'q': self.get_query(request, *args, **kwargs), 'limit': 1})
|
||||
url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
|
||||
|
||||
result_response = requests.get(url)
|
||||
result = []
|
||||
|
||||
for feature in result_response.json().get('features'):
|
||||
if not feature['geometry']['type'] == 'Point':
|
||||
continue # skip unknown
|
||||
result.append({
|
||||
'lon': str(feature['geometry']['coordinates'][0]),
|
||||
'lat': str(feature['geometry']['coordinates'][1]),
|
||||
'display_name': feature['properties']['label'],
|
||||
})
|
||||
break
|
||||
|
||||
return utils.response_for_json(request, result)
|
||||
|
||||
|
||||
class SearchPathView(SearchView):
|
||||
def get_query(self, request, *args, **kwargs):
|
||||
return kwargs.get('path')
|
||||
|
||||
|
||||
class ReverseView(View, SingleObjectMixin):
|
||||
model = BaseAdresse
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
response_format = 'json'
|
||||
if 'format' in request.GET:
|
||||
response_format = request.GET['format']
|
||||
if response_format != 'json':
|
||||
raise NotImplementedError()
|
||||
|
||||
if not 'lat' in request.GET or not 'lon' in request.GET:
|
||||
return HttpResponseBadRequest('missing parameter')
|
||||
|
||||
lat = request.GET['lat']
|
||||
lon = request.GET['lon']
|
||||
|
||||
scheme, netloc, path, params, query, fragment = urlparse.urlparse(
|
||||
self.get_object().service_url)
|
||||
path = '/reverse/'
|
||||
query = urllib.urlencode({'lat': lat, 'lon': lon})
|
||||
url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
|
||||
|
||||
result_response = requests.get(url)
|
||||
result = None
|
||||
|
||||
for feature in result_response.json().get('features'):
|
||||
if not feature['geometry']['type'] == 'Point':
|
||||
continue # skip unknown
|
||||
result = {}
|
||||
result['lon'] = str(feature['geometry']['coordinates'][0])
|
||||
result['lat'] = str(feature['geometry']['coordinates'][1])
|
||||
result['address'] = {'country': 'France'}
|
||||
for prop in feature['properties']:
|
||||
if prop in ('city', 'postcode'):
|
||||
result['address'][prop] = feature['properties'][prop]
|
||||
elif prop == 'housenumber':
|
||||
result['address']['house_number'] = feature['properties'][prop]
|
||||
elif prop == 'label':
|
||||
result['display_name'] = feature['properties'][prop]
|
||||
elif prop == 'name':
|
||||
house_number = feature['properties'].get('housenumber')
|
||||
value = feature['properties'][prop]
|
||||
if house_number and value.startswith(house_number):
|
||||
value = value[len(house_number):].strip()
|
||||
result['address']['road'] = value
|
||||
return utils.response_for_json(request, result)
|
||||
kwargs['endpoint'] = 'search'
|
||||
return super(SearchPathView, self).get(request, *args, **kwargs)
|
||||
|
|
|
@ -36,7 +36,7 @@ class ChoositSMSGateway(BaseResource, SMSGatewayMixin):
|
|||
def get_absolute_url(self):
|
||||
return reverse('choosit-view', kwargs={'slug': self.slug})
|
||||
|
||||
def send(self, text, sender, destinations):
|
||||
def send_msg(self, text, sender, destinations):
|
||||
"""Send a SMS using the Choosit provider"""
|
||||
# from http://sms.choosit.com/documentation_technique.html
|
||||
# unfortunately it lacks a batch API...
|
||||
|
@ -146,4 +146,3 @@ class ChoositRegisterGateway(BaseResource):
|
|||
reg = ChoositRegisterWS(self.url, self.key)
|
||||
ws = reg.update(subscriptions, user)
|
||||
return {"message": ws['status']}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ from views import *
|
|||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^(?P<slug>[\w,-]+)/$', ChoositDetailView.as_view(), name='choosit-view'),
|
||||
url(r'^(?P<slug>[\w,-]+)/send$', ChoositSendView.as_view(), name='choosit-send'),
|
||||
url(r'^register/(?P<slug>[\w,-]+)/$', ChoositRegisterDetailView.as_view(), name='choosit-register-view'),
|
||||
url(r'^register/(?P<slug>[\w,-]+)/register$', ChoositRegisterView.as_view(), name='choosit-register'),
|
||||
)
|
||||
|
|
|
@ -7,7 +7,6 @@ from django.utils.decorators import method_decorator
|
|||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from passerelle.base.views import ResourceView
|
||||
from passerelle.sms.views import SendView
|
||||
from passerelle import utils
|
||||
|
||||
from .models import ChoositSMSGateway, ChoositRegisterGateway
|
||||
|
@ -19,10 +18,6 @@ class ChoositDetailView(ResourceView):
|
|||
template_name = 'passerelle/manage/messages_service_view.html'
|
||||
|
||||
|
||||
class ChoositSendView(SendView):
|
||||
model = ChoositSMSGateway
|
||||
|
||||
|
||||
class ChoositRegisterDetailView(ResourceView):
|
||||
model = ChoositRegisterGateway
|
||||
template_name = 'choosit/choosit_register_detail.html'
|
||||
|
|
|
@ -1,8 +1,23 @@
|
|||
try:
|
||||
import SOAPpy
|
||||
except ImportError:
|
||||
SOAPpy = None
|
||||
|
||||
try:
|
||||
import phpserialize
|
||||
except ImportError:
|
||||
phpserialize = None
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from passerelle.base.models import BaseResource
|
||||
from passerelle.utils.api import endpoint
|
||||
|
||||
|
||||
def phpserialize_loads(s):
|
||||
return phpserialize.loads(s.encode('utf-8'))
|
||||
|
||||
|
||||
class Gdc(BaseResource):
|
||||
|
@ -21,3 +36,23 @@ class Gdc(BaseResource):
|
|||
@classmethod
|
||||
def get_icon_class(cls):
|
||||
return 'ressources'
|
||||
|
||||
@endpoint()
|
||||
def communes(self, request, *args, **kwargs):
|
||||
server = SOAPpy.SOAPProxy(self.service_url)
|
||||
soap_result = phpserialize_loads(server.getListeCommune()['listeCommune'])
|
||||
result = []
|
||||
for k, v in soap_result.items():
|
||||
result.append({'id': k, 'text': unicode(v, 'utf-8')})
|
||||
result.sort(lambda x,y: cmp(x['id'], y['id']))
|
||||
return result
|
||||
|
||||
@endpoint()
|
||||
def objets(self, request, *args, **kwargs):
|
||||
server = SOAPpy.SOAPProxy(self.service_url)
|
||||
soap_result = phpserialize_loads(server.getListeObjet()['listeObjet'])
|
||||
result = []
|
||||
for k, v in soap_result.items():
|
||||
result.append({'id': k, 'text': unicode(v, 'utf-8')})
|
||||
result.sort(lambda x,y: cmp(x['id'], y['id']))
|
||||
return result
|
||||
|
|
|
@ -19,12 +19,12 @@ Service URL : {{ object.service_url }}
|
|||
<div>
|
||||
<h3>{% trans 'Endpoints' %}</h3>
|
||||
<ul>
|
||||
<li>{% trans 'Listing communes:' %} <a href="{% url 'gdc-communes' slug=object.slug %}"
|
||||
>{{ site_base_uri }}{% url 'gdc-communes' slug=object.slug %}</a></li>
|
||||
<li>{% trans 'Listing communes:' %} <a href="{% url 'generic-endpoint' connector="gdc" slug=object.slug endpoint="communes" %}"
|
||||
>{{ site_base_uri }}{% url 'generic-endpoint' connector="gdc" slug=object.slug endpoint="communes" %}</a></li>
|
||||
<li>{% trans 'Listing streets:' %} <a href="{% url 'gdc-voies' slug=object.slug insee=34022%}"
|
||||
>{{ site_base_uri }}{% url 'gdc-view' slug=object.slug %}/voies/<i><insee></i></a></li>
|
||||
<li>{% trans 'Listing subjects:' %} <a href="{% url 'gdc-objets' slug=object.slug %}"
|
||||
>{{ site_base_uri }}{% url 'gdc-objets' slug=object.slug %}</a></li>
|
||||
<li>{% trans 'Listing subjects:' %} <a href="{% url 'generic-endpoint' connector="gdc" slug=object.slug endpoint="objets" %}"
|
||||
>{{ site_base_uri }}{% url 'generic-endpoint' connector="gdc" slug=object.slug endpoint="objets" %}</a></li>
|
||||
<li>{% trans 'Posting a new request:' %} <a href="{% url 'gdc-post' slug=object.slug %}"
|
||||
>{{ site_base_uri }}{% url 'gdc-post' slug=object.slug %}</a> (POST)</li>
|
||||
</ul>
|
||||
|
|
|
@ -5,9 +5,7 @@ from views import *
|
|||
urlpatterns = patterns('',
|
||||
url(r'^(?P<slug>[\w,-]+)/$', GdcDetailView.as_view(), name='gdc-view'),
|
||||
|
||||
url(r'^(?P<slug>[\w,-]+)/communes$', CommunesView.as_view(), name='gdc-communes'),
|
||||
url(r'^(?P<slug>[\w,-]+)/voies/(?P<insee>\d+)$', VoiesView.as_view(), name='gdc-voies'),
|
||||
url(r'^(?P<slug>[\w,-]+)/objets$', ObjetsView.as_view(), name='gdc-objets'),
|
||||
url(r'^(?P<slug>[\w,-]+)/post/demande$', csrf_exempt(PostDemandeView.as_view()), name='gdc-post'),
|
||||
url(r'^(?P<slug>[\w,-]+)/status/(?P<ref>\d+)', StatusView.as_view(), name='gdc-status'),
|
||||
)
|
||||
|
|
|
@ -1,58 +1,14 @@
|
|||
import json
|
||||
import unicodedata
|
||||
|
||||
try:
|
||||
import SOAPpy
|
||||
except ImportError:
|
||||
SOAPpy = None
|
||||
|
||||
try:
|
||||
import phpserialize
|
||||
except ImportError:
|
||||
phpserialize = None
|
||||
|
||||
from django.http import Http404
|
||||
from django.views.generic.base import View
|
||||
from django.views.generic.detail import SingleObjectMixin, DetailView
|
||||
|
||||
from passerelle import utils
|
||||
|
||||
from .models import Gdc
|
||||
from .models import Gdc, phpserialize, phpserialize_loads, SOAPpy
|
||||
|
||||
def phpserialize_loads(s):
|
||||
if phpserialize is None:
|
||||
raise Http404
|
||||
return phpserialize.loads(s.encode('utf-8'))
|
||||
|
||||
|
||||
class CommunesView(View, SingleObjectMixin):
|
||||
model = Gdc
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if SOAPpy is None:
|
||||
raise Http404
|
||||
server = SOAPpy.SOAPProxy(self.get_object().service_url)
|
||||
soap_result = phpserialize_loads(server.getListeCommune()['listeCommune'])
|
||||
result = []
|
||||
for k, v in soap_result.items():
|
||||
result.append({'id': k, 'text': unicode(v, 'utf-8')})
|
||||
result.sort(lambda x,y: cmp(x['id'], y['id']))
|
||||
return utils.response_for_json(request, {'data': result})
|
||||
|
||||
|
||||
class ObjetsView(View, SingleObjectMixin):
|
||||
model = Gdc
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if SOAPpy is None:
|
||||
raise Http404
|
||||
server = SOAPpy.SOAPProxy(self.get_object().service_url)
|
||||
soap_result = phpserialize_loads(server.getListeObjet()['listeObjet'])
|
||||
result = []
|
||||
for k, v in soap_result.items():
|
||||
result.append({'id': k, 'text': unicode(v, 'utf-8')})
|
||||
result.sort(lambda x,y: cmp(x['id'], y['id']))
|
||||
return utils.response_for_json(request, {'data': result})
|
||||
|
||||
|
||||
class StatusView(View, SingleObjectMixin):
|
||||
|
|
|
@ -50,7 +50,7 @@ class MobytSMSGateway(BaseResource, SMSGatewayMixin):
|
|||
def get_delete_url(self):
|
||||
return reverse('delete-connector', kwargs={'connector': 'mobyt', 'slug': self.slug})
|
||||
|
||||
def send(self, text, sender, destinations):
|
||||
def send_msg(self, text, sender, destinations):
|
||||
"""Send a SMS using the Mobyt provider"""
|
||||
# unfortunately it lacks a batch API...
|
||||
destinations = self.clean_numbers(destinations, self.default_country_code)
|
||||
|
|
|
@ -3,5 +3,4 @@ from views import *
|
|||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^(?P<slug>[\w,-]+)/$', MobytDetailView.as_view(), name='mobyt-view'),
|
||||
url(r'^(?P<slug>[\w,-]+)/send$', MobytSendView.as_view(), name='mobyt-send'),
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from passerelle.base.views import ResourceView
|
||||
from passerelle.sms.views import SendView
|
||||
|
||||
from .models import MobytSMSGateway
|
||||
|
||||
|
@ -7,7 +6,3 @@ from .models import MobytSMSGateway
|
|||
class MobytDetailView(ResourceView):
|
||||
model = MobytSMSGateway
|
||||
template_name = 'passerelle/manage/messages_service_view.html'
|
||||
|
||||
|
||||
class MobytSendView(SendView):
|
||||
model = MobytSMSGateway
|
||||
|
|
|
@ -41,7 +41,7 @@ class OrangeSMSGateway(BaseResource, SMSGatewayMixin):
|
|||
def get_delete_url(self):
|
||||
return reverse('delete-connector', kwargs={'connector': 'orange', 'slug': self.slug})
|
||||
|
||||
def send(self, text, sender, destinations):
|
||||
def send_msg(self, text, sender, destinations):
|
||||
logger = logging.getLogger('passerelle.apps.orange')
|
||||
"""Send a SMS using the Orange provider"""
|
||||
# unfortunately it lacks a batch API...
|
||||
|
@ -49,4 +49,4 @@ class OrangeSMSGateway(BaseResource, SMSGatewayMixin):
|
|||
|
||||
logger.info(u'sending sms with sender %s to %s', sender, u', '.join(destinations))
|
||||
logger.debug(u'sms content: %s', text)
|
||||
soap.ContactEveryoneSoap(instance=self).send_advanced_message(destinations, sender, text)
|
||||
#soap.ContactEveryoneSoap(instance=self).send_advanced_message(destinations, sender, text)
|
||||
|
|
|
@ -3,5 +3,4 @@ from views import *
|
|||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^(?P<slug>[\w,-]+)/$', OrangeDetailView.as_view(), name='orange-view'),
|
||||
url(r'^(?P<slug>[\w,-]+)/send$', OrangeSendView.as_view(), name='orange-send'),
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from passerelle.base.views import ResourceView
|
||||
from passerelle.sms.views import SendView
|
||||
|
||||
from .models import OrangeSMSGateway
|
||||
|
||||
|
@ -7,7 +6,3 @@ from .models import OrangeSMSGateway
|
|||
class OrangeDetailView(ResourceView):
|
||||
model = OrangeSMSGateway
|
||||
template_name = 'passerelle/manage/messages_service_view.html'
|
||||
|
||||
|
||||
class OrangeSendView(SendView):
|
||||
model = OrangeSMSGateway
|
||||
|
|
|
@ -56,7 +56,7 @@ class OVHSMSGateway(BaseResource, SMSGatewayMixin):
|
|||
def get_delete_url(self):
|
||||
return reverse('delete-connector', kwargs={'connector': 'ovh', 'slug': self.slug})
|
||||
|
||||
def send(self, text, sender, destinations):
|
||||
def send_msg(self, text, sender, destinations):
|
||||
"""Send a SMS using the OVH provider"""
|
||||
# unfortunately it lacks a batch API...
|
||||
destinations = self.clean_numbers(destinations, self.default_country_code)
|
||||
|
|
|
@ -3,5 +3,4 @@ from views import *
|
|||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^(?P<slug>[\w,-]+)/$', OvhDetailView.as_view(), name='ovh-view'),
|
||||
url(r'^(?P<slug>[\w,-]+)/send$', OvhSendView.as_view(), name='ovh-send'),
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from passerelle.base.views import ResourceView
|
||||
from passerelle.sms.views import SendView
|
||||
|
||||
from .models import OVHSMSGateway
|
||||
|
||||
|
@ -7,7 +6,3 @@ from .models import OVHSMSGateway
|
|||
class OvhDetailView(ResourceView):
|
||||
model = OVHSMSGateway
|
||||
template_name = 'passerelle/manage/messages_service_view.html'
|
||||
|
||||
|
||||
class OvhSendView(SendView):
|
||||
model = OVHSMSGateway
|
||||
|
|
|
@ -38,7 +38,7 @@ class OxydSMSGateway(BaseResource, SMSGatewayMixin):
|
|||
def get_delete_url(self):
|
||||
return reverse('delete-connector', kwargs={'connector': 'oxyd', 'slug': self.slug})
|
||||
|
||||
def send(self, text, sender, destinations):
|
||||
def send_msg(self, text, sender, destinations):
|
||||
"""Send a SMS using the Oxyd provider"""
|
||||
# unfortunately it lacks a batch API...
|
||||
destinations = self.clean_numbers(destinations,
|
||||
|
|
|
@ -3,5 +3,4 @@ from views import *
|
|||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^(?P<slug>[\w,-]+)/$', OxydDetailView.as_view(), name='oxyd-view'),
|
||||
url(r'^(?P<slug>[\w,-]+)/send$', OxydSendView.as_view(), name='oxyd-send'),
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from passerelle.base.views import ResourceView
|
||||
from passerelle.sms.views import SendView
|
||||
|
||||
from .models import OxydSMSGateway
|
||||
|
||||
|
@ -7,7 +6,3 @@ from .models import OxydSMSGateway
|
|||
class OxydDetailView(ResourceView):
|
||||
model = OxydSMSGateway
|
||||
template_name = 'passerelle/manage/messages_service_view.html'
|
||||
|
||||
|
||||
class OxydSendView(SendView):
|
||||
model = OxydSMSGateway
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from passerelle.utils.api import endpoint
|
||||
|
||||
class SMSGatewayMixin(object):
|
||||
category = _('SMS Providers')
|
||||
|
@ -23,3 +26,18 @@ class SMSGatewayMixin(object):
|
|||
numbers.append(prefix + number)
|
||||
return numbers
|
||||
|
||||
@endpoint('json-api', perm='can_send_messages', methods=['post'])
|
||||
def send(self, request, *args, **kwargs):
|
||||
data = json.loads(request.body)
|
||||
assert isinstance(data, dict), 'JSON payload is not a dict'
|
||||
assert 'message' in data, 'missing "message" in JSON payload'
|
||||
assert 'from' in data, 'missing "from" in JSON payload'
|
||||
assert 'to' in data, 'missing "to" in JSON payload'
|
||||
assert isinstance(data['message'], unicode), 'message is not a string'
|
||||
assert isinstance(data['from'], unicode), 'from is not a string'
|
||||
assert all(map(lambda x: isinstance(x, unicode), data['to'])), \
|
||||
'to is not a list of strings'
|
||||
logging.info('sending message %r to %r with sending number %r',
|
||||
data['message'], data['to'], data['from'])
|
||||
self.send_msg(data['message'], data['from'], data['to'])
|
||||
return {'err': 0}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import logging
|
||||
import json
|
||||
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic.base import View
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
from passerelle import utils
|
||||
|
||||
class SendView(View, SingleObjectMixin):
|
||||
@method_decorator(csrf_exempt)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(SendView, self).dispatch(*args, **kwargs)
|
||||
|
||||
@utils.to_json('api')
|
||||
@utils.protected_api('can_send_messages')
|
||||
def post(self, request, *args, **kwargs):
|
||||
data = json.loads(request.body)
|
||||
assert isinstance(data, dict), 'JSON payload is not a dict'
|
||||
assert 'message' in data, 'missing "message" in JSON payload'
|
||||
assert 'from' in data, 'missing "from" in JSON payload'
|
||||
assert 'to' in data, 'missing "to" in JSON payload'
|
||||
assert isinstance(data['message'], unicode), 'message is not a string'
|
||||
assert isinstance(data['from'], unicode), 'from is not a string'
|
||||
assert all(map(lambda x: isinstance(x, unicode), data['to'])), \
|
||||
'to is not a list of strings'
|
||||
logging.info('sending message %r to %r with sending number %r',
|
||||
data['message'], data['to'], data['from'])
|
||||
return self.get_object().send(data['message'], data['from'], data['to'])
|
|
@ -8,7 +8,7 @@ from django.views.static import serve as static_serve
|
|||
|
||||
from .views import (HomePageView, ManageView, ManageAddView,
|
||||
GenericCreateConnectorView, GenericDeleteConnectorView,
|
||||
GenericEditConnectorView,
|
||||
GenericEditConnectorView, GenericEndpointView,
|
||||
LEGACY_APPS_PATTERNS, LegacyPageView, login, logout)
|
||||
from .urls_utils import decorated_includes, required, app_enabled
|
||||
from .base.urls import access_urlpatterns
|
||||
|
@ -84,5 +84,10 @@ urlpatterns += patterns('',
|
|||
GenericEditConnectorView.as_view(), name='edit-connector'),
|
||||
)))))
|
||||
|
||||
urlpatterns += patterns('',
|
||||
url(r'^(?P<connector>[\w,-]+)/(?P<slug>[\w,-]+)/(?P<endpoint>[\w,-]+)$',
|
||||
GenericEndpointView.as_view(), name='generic-endpoint')
|
||||
)
|
||||
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# passerelle - uniform access to multiple data sources and services
|
||||
# Copyright (C) 2016 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class endpoint(object):
|
||||
def __init__(self, serializer_type='json', perm=None, methods=['get']):
|
||||
self.perm = perm
|
||||
self.methods = methods
|
||||
self.serializer_type = serializer_type
|
||||
|
||||
def __call__(self, func):
|
||||
func.endpoint_info = self
|
||||
return func
|
|
@ -1,8 +1,11 @@
|
|||
from django.apps import apps
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.http import HttpResponseRedirect, Http404
|
||||
from django.views.generic import RedirectView, TemplateView, CreateView, DeleteView, UpdateView
|
||||
from django.http import HttpResponseBadRequest, HttpResponseRedirect, Http404
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import RedirectView, View, TemplateView, CreateView, DeleteView, UpdateView
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.shortcuts import resolve_url
|
||||
|
@ -17,6 +20,7 @@ except ImportError:
|
|||
|
||||
from passerelle.base.models import BaseResource
|
||||
|
||||
from .utils import to_json, response_for_json, is_authorized
|
||||
from .forms import GenericConnectorForm
|
||||
|
||||
|
||||
|
@ -68,8 +72,11 @@ class ManageAddView(TemplateView):
|
|||
|
||||
|
||||
class GenericConnectorMixin(object):
|
||||
def get_connector(self, **kwargs):
|
||||
return kwargs.get('connector')
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
connector = kwargs.get('connector')
|
||||
connector = self.get_connector(**kwargs)
|
||||
try:
|
||||
app = apps.get_app_config(connector.replace('-', '_'))
|
||||
except LookupError:
|
||||
|
@ -99,6 +106,49 @@ class GenericDeleteConnectorView(GenericConnectorMixin, DeleteView):
|
|||
return reverse('manage-home')
|
||||
|
||||
|
||||
class GenericEndpointView(GenericConnectorMixin, SingleObjectMixin, View):
|
||||
def get_params(self, request, *args, **kwargs):
|
||||
return dict([(x, request.GET[x]) for x in request.GET.keys()])
|
||||
|
||||
@csrf_exempt
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super(GenericEndpointView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
@property
|
||||
def endpoint(self):
|
||||
endpoint = getattr(self.get_object(), self.kwargs.get('endpoint'), None)
|
||||
endpoint_info = getattr(endpoint, 'endpoint_info', None)
|
||||
if endpoint_info is None:
|
||||
raise Http404()
|
||||
return endpoint
|
||||
|
||||
def _allowed_methods(self):
|
||||
return [x.upper() for x in self.endpoint.endpoint_info.methods]
|
||||
|
||||
def check_perms(self, request):
|
||||
perm = self.endpoint.endpoint_info.perm
|
||||
if not perm:
|
||||
return True
|
||||
return is_authorized(request, self.get_object(), perm)
|
||||
|
||||
def perform(self, request, *args, **kwargs):
|
||||
if request.method.lower() not in self.endpoint.endpoint_info.methods:
|
||||
return self.http_method_not_allowed(request, *args, **kwargs)
|
||||
if not self.check_perms(request):
|
||||
raise PermissionDenied()
|
||||
return self.endpoint(request, **self.get_params(request, *args, **kwargs))
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if self.endpoint.endpoint_info.serializer_type == 'json-api':
|
||||
json_decorator = to_json('api')
|
||||
else:
|
||||
json_decorator = to_json('plain')
|
||||
return json_decorator(self.perform)(request, **self.get_params(request, *args, **kwargs))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
|
||||
# legacy
|
||||
LEGACY_APPS_PATTERNS = {
|
||||
'datasources': {'url': 'data', 'name': 'Data Sources'},
|
||||
|
|
|
@ -27,8 +27,9 @@ def setup():
|
|||
|
||||
def test_anonymous_access(setup):
|
||||
app, oxyd = setup
|
||||
resp = app.post_json(reverse('oxyd-send', kwargs={'slug': oxyd.slug}),
|
||||
{}, status=403)
|
||||
endpoint_url = reverse('generic-endpoint',
|
||||
kwargs={'connector': 'oxyd', 'slug': oxyd.slug, 'endpoint': 'send'})
|
||||
resp = app.post_json(endpoint_url, {}, status=403)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_class'] == 'django.core.exceptions.PermissionDenied'
|
||||
|
||||
|
@ -46,7 +47,9 @@ def test_access_with_good_signature(setup):
|
|||
resource_type=obj_type,
|
||||
resource_pk=oxyd.pk,
|
||||
)
|
||||
url = signature.sign_url(reverse('oxyd-send', kwargs={'slug': oxyd.slug}) + '?orig=eservices', '12345')
|
||||
endpoint_url = reverse('generic-endpoint',
|
||||
kwargs={'connector': 'oxyd', 'slug': oxyd.slug, 'endpoint': 'send'})
|
||||
url = signature.sign_url(endpoint_url + '?orig=eservices', '12345')
|
||||
# for empty payload the connector returns 500 with
|
||||
# {"err_desc": "missing \"message\" in JSON payload"}
|
||||
resp = app.post_json(url, {}, status=500)
|
||||
|
@ -70,8 +73,9 @@ def test_access_http_auth(setup):
|
|||
resource_pk=oxyd.pk,
|
||||
)
|
||||
app.authorization = ('Basic', (username, password))
|
||||
resp = app.post_json(reverse('oxyd-send', kwargs={'slug': oxyd.slug}), {},
|
||||
status=500)
|
||||
endpoint_url = reverse('generic-endpoint',
|
||||
kwargs={'connector': 'oxyd', 'slug': oxyd.slug, 'endpoint': 'send'})
|
||||
resp = app.post_json(endpoint_url, {}, status=500)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'missing "message" in JSON payload'
|
||||
|
||||
|
@ -91,11 +95,12 @@ def test_access_apikey(setup):
|
|||
resource_pk=oxyd.pk,
|
||||
)
|
||||
params = {'message': 'test'}
|
||||
url = reverse('oxyd-send', kwargs={'slug': oxyd.slug})
|
||||
resp = app.post_json(url + '?apikey=' + password , params, status=500)
|
||||
endpoint_url = reverse('generic-endpoint',
|
||||
kwargs={'connector': 'oxyd', 'slug': oxyd.slug, 'endpoint': 'send'})
|
||||
resp = app.post_json(endpoint_url + '?apikey=' + password , params, status=500)
|
||||
resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'missing "from" in JSON payload'
|
||||
resp = app.post_json(url + '?apikey=' + password[:3] , params, status=403)
|
||||
resp = app.post_json(endpoint_url + '?apikey=' + password[:3] , params, status=403)
|
||||
resp.json['err'] == 1
|
||||
assert resp.json['err_class'] == 'django.core.exceptions.PermissionDenied'
|
||||
|
||||
|
@ -112,8 +117,9 @@ def test_access_apiuser_with_no_key(setup):
|
|||
resource_pk=oxyd.pk,
|
||||
)
|
||||
params = {'message': 'test', 'from': 'test api'}
|
||||
resp = app.post_json(reverse('oxyd-send', kwargs={'slug': oxyd.slug}),
|
||||
params, status=500)
|
||||
endpoint_url = reverse('generic-endpoint',
|
||||
kwargs={'connector': 'oxyd', 'slug': oxyd.slug, 'endpoint': 'send'})
|
||||
resp = app.post_json(endpoint_url, params, status=500)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'missing "to" in JSON payload'
|
||||
|
||||
|
@ -132,14 +138,16 @@ def test_access_apiuser_with_ip_restriction(setup):
|
|||
resource_type=obj_type,
|
||||
resource_pk=oxyd.pk,
|
||||
)
|
||||
resp = app.post_json(reverse('oxyd-send', kwargs={'slug': oxyd.slug}),
|
||||
{}, extra_environ=[('REMOTE_ADDR', '127.0.0.1')],
|
||||
endpoint_url = reverse('generic-endpoint',
|
||||
kwargs={'connector': 'oxyd', 'slug': oxyd.slug, 'endpoint': 'send'})
|
||||
resp = app.post_json(endpoint_url, {}, extra_environ=[('REMOTE_ADDR', '127.0.0.1')],
|
||||
status=403)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_class'] == 'django.core.exceptions.PermissionDenied'
|
||||
|
||||
resp = app.post_json(reverse('oxyd-send', kwargs={'slug': oxyd.slug}),
|
||||
{}, extra_environ=[('REMOTE_ADDR', authorized_ip)],
|
||||
status=500)
|
||||
endpoint_url = reverse('generic-endpoint',
|
||||
kwargs={'connector': 'oxyd', 'slug': oxyd.slug, 'endpoint': 'send'})
|
||||
resp = app.post_json(endpoint_url, {},
|
||||
extra_environ=[('REMOTE_ADDR', authorized_ip)], status=500)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'missing "message" in JSON payload'
|
||||
|
|
Loading…
Reference in New Issue