Added a new API endpoint to be used by the registration system, to trigger account creation.
- Legacy-Id: 17941
This commit is contained in:
parent
2416a46f5e
commit
ec5d159b4f
|
@ -23,6 +23,7 @@ from ietf.meeting.factories import MeetingFactory, SessionFactory
|
|||
from ietf.meeting.test_data import make_meeting_test_data
|
||||
from ietf.person.factories import PersonFactory
|
||||
from ietf.person.models import PersonalApiKey
|
||||
from ietf.utils.mail import outbox, get_payload_text
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
|
||||
|
||||
OMITTED_APPS = (
|
||||
|
@ -31,7 +32,7 @@ OMITTED_APPS = (
|
|||
'ietf.ipr',
|
||||
)
|
||||
|
||||
class CustomApiTestCase(TestCase):
|
||||
class CustomApiTests(TestCase):
|
||||
|
||||
# Using mock to patch the import functions in ietf.meeting.views, where
|
||||
# api_import_recordings() are using them:
|
||||
|
@ -197,6 +198,48 @@ class CustomApiTestCase(TestCase):
|
|||
self.assertEqual(data['name'], person.name)
|
||||
self.assertEqual(data['email'], person.email().address)
|
||||
|
||||
def test_api_new_meeting_registration(self):
|
||||
meeting = MeetingFactory(type_id='ietf')
|
||||
reg = {
|
||||
'apikey': 'invalid',
|
||||
'affiliation': "Alguma Corporação",
|
||||
'country_code': 'PT',
|
||||
'email': 'foo@example.pt',
|
||||
'first_name': 'Foo',
|
||||
'last_name': 'Bar',
|
||||
'meeting': meeting.number,
|
||||
'reg_type': 'hackathon',
|
||||
'ticket_type': 'regular',
|
||||
}
|
||||
url = urlreverse('ietf.api.views.api_new_meeting_registration')
|
||||
r = self.client.post(url, reg)
|
||||
self.assertContains(r, 'Invalid apikey', status_code=400)
|
||||
person = PersonFactory(user__is_staff=True)
|
||||
# Make sure 'person' has an acceptable role
|
||||
RoleFactory(name_id='robot', person=person, email=person.email(), group__acronym='secretariat')
|
||||
key = PersonalApiKey.objects.create(person=person, endpoint=url)
|
||||
reg['apikey'] = key.hash()
|
||||
#
|
||||
# Test valid POST
|
||||
r = self.client.post(url, reg)
|
||||
self.assertContains(r, "Accepted, New registration, Email sent", status_code=202)
|
||||
#
|
||||
# Check outgoing mail
|
||||
self.assertEqual(len(outbox), 1)
|
||||
body = get_payload_text(outbox[-1])
|
||||
self.assertIn(reg['email'], outbox[-1]['To'] )
|
||||
self.assertIn(reg['email'], body)
|
||||
self.assertIn('account creation request', body)
|
||||
#
|
||||
# Test incomplete POST
|
||||
drop_fields = ['affiliation', 'first_name', 'reg_type']
|
||||
for field in drop_fields:
|
||||
del reg[field]
|
||||
r = self.client.post(url, reg)
|
||||
self.assertContains(r, 'Missing parameters:', status_code=400)
|
||||
err, fields = r.content.decode().split(':', 1)
|
||||
missing_fields = [ f.strip() for f in fields.split(',') ]
|
||||
self.assertEqual(set(missing_fields), set(drop_fields))
|
||||
|
||||
class TastypieApiTestCase(ResourceTestCaseMixin, TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -12,22 +12,30 @@ from ietf.utils.urls import url
|
|||
api.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
# Top endpoint for Tastypie's REST API (this isn't standard Tastypie):
|
||||
# General API help page
|
||||
url(r'^$', api_views.api_help),
|
||||
# Top endpoint for Tastypie's REST API (this isn't standard Tastypie):
|
||||
url(r'^v1/?$', api_views.top_level),
|
||||
# Custom API endpoints
|
||||
url(r'^notify/meeting/import_recordings/(?P<number>[a-z0-9-]+)/?$', meeting_views.api_import_recordings),
|
||||
url(r'^meeting/session/video/url$', meeting_views.api_set_session_video_url),
|
||||
url(r'^submit/?$', submit_views.api_submit),
|
||||
url(r'^iesg/position', views_ballot.api_set_position),
|
||||
# GPRD: export of personal information for the logged-in person
|
||||
url(r'^export/personal-information/$', api_views.PersonalInformationExportView.as_view()),
|
||||
# For mailarchive use, requires secretariat role
|
||||
url(r'^v2/person/person', api_views.ApiV2PersonExportView.as_view()),
|
||||
# For meetecho access
|
||||
url(r'^person/access/meetecho', api_views.person_access_meetecho),
|
||||
#
|
||||
# --- Custom API endpoints, sorted alphabetically ---
|
||||
# GPRD: export of personal information for the logged-in person
|
||||
url(r'^export/personal-information/$', api_views.PersonalInformationExportView.as_view()),
|
||||
# Let IESG members set positions programmatically
|
||||
url(r'^iesg/position', views_ballot.api_set_position),
|
||||
# Let Meetecho set session video URLs
|
||||
url(r'^meeting/session/video/url$', meeting_views.api_set_session_video_url),
|
||||
# Let Meetecho trigger recording imports
|
||||
url(r'^notify/meeting/import_recordings/(?P<number>[a-z0-9-]+)/?$', meeting_views.api_import_recordings),
|
||||
# Let the registration system notify us about registrations
|
||||
url(r'^notify/meeting/registration', api_views.api_new_meeting_registration),
|
||||
# OpenID authentication provider
|
||||
url(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')),
|
||||
# For meetecho access
|
||||
url(r'^person/access/meetecho', api_views.person_access_meetecho),
|
||||
# Draft submission API
|
||||
url(r'^submit/?$', submit_views.api_submit),
|
||||
]
|
||||
|
||||
# Additional (standard) Tastypie endpoints
|
||||
|
|
|
@ -8,6 +8,9 @@ from jwcrypto.jwk import JWK
|
|||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.urls import reverse
|
||||
|
@ -23,11 +26,14 @@ from tastypie.serializers import Serializer
|
|||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.person.models import Person
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.api import _api_list
|
||||
from ietf.api.serializer import JsonExportMixin
|
||||
from ietf.utils.decorators import require_api_key
|
||||
from ietf.ietfauth.views import send_account_creation_email
|
||||
from ietf.ietfauth.utils import role_required
|
||||
from ietf.meeting.models import Meeting
|
||||
from ietf.stats.models import MeetingRegistration
|
||||
from ietf.utils.decorators import require_api_key
|
||||
|
||||
|
||||
def top_level(request):
|
||||
|
@ -108,3 +114,61 @@ def person_access_meetecho(request):
|
|||
'secr': list(person.role_set.filter(name='secr', group__state__in=['active', 'bof', 'proposed']).values_list('group__acronym', flat=True)),
|
||||
}
|
||||
}), content_type='application/json')
|
||||
|
||||
@require_api_key
|
||||
@role_required('Robot')
|
||||
@csrf_exempt
|
||||
def api_new_meeting_registration(request):
|
||||
'''REST API to notify the datatracker about a new meeting registration'''
|
||||
def err(code, text):
|
||||
return HttpResponse(text, status=code, content_type='text/plain')
|
||||
required_fields = [ 'meeting', 'first_name', 'last_name', 'affiliation', 'country_code', 'email', 'reg_type', 'ticket_type', ]
|
||||
fields = required_fields + []
|
||||
if request.method == 'POST':
|
||||
# parameters:
|
||||
# apikey:
|
||||
# meeting
|
||||
# name
|
||||
# email
|
||||
# reg_type (In Person, Remote, Hackathon Only)
|
||||
# ticket_type (full_week, one_day, student)
|
||||
#
|
||||
data = {}
|
||||
missing_fields = []
|
||||
for item in fields:
|
||||
value = request.POST.get(item, None)
|
||||
if value is None and item in required_fields:
|
||||
missing_fields.append(item)
|
||||
data[item] = value
|
||||
if missing_fields:
|
||||
return err(400, "Missing parameters: %s" % ', '.join(missing_fields))
|
||||
number = data['meeting']
|
||||
try:
|
||||
meeting = Meeting.objects.get(number=number)
|
||||
except Meeting.DoesNotExist:
|
||||
return err(400, "Invalid meeting value: '%s'" % (number, ))
|
||||
email = data['email']
|
||||
try:
|
||||
validate_email(email)
|
||||
except ValidationError:
|
||||
return err(400, "Invalid email value: '%s'" % (email, ))
|
||||
if request.POST.get('cancelled', 'false') == 'true':
|
||||
MeetingRegistration.objects.filter(meeting_id=meeting.pk, email=email).delete()
|
||||
return HttpResponse('OK', status=200, content_type='text/plain')
|
||||
else:
|
||||
object, created = MeetingRegistration.objects.get_or_create(meeting_id=meeting.pk, email=email)
|
||||
try:
|
||||
MeetingRegistration.objects.filter(id=object.id).update(**data)
|
||||
object.save()
|
||||
except ValueError as e:
|
||||
return err(400, "Unexpected POST data: %s" % e)
|
||||
response = "Accepted, New registration" if created else "Accepted, Updated registration"
|
||||
if User.objects.filter(username=email).exists() or Email.objects.filter(address=email).exists():
|
||||
pass
|
||||
else:
|
||||
send_account_creation_email(request, email)
|
||||
response += ", Email sent"
|
||||
return HttpResponse(response, status=202, content_type='text/plain')
|
||||
else:
|
||||
return HttpResponse(status=405)
|
||||
|
|
@ -85,7 +85,7 @@ def has_role(user, role_names, *args, **kwargs):
|
|||
"Reviewer": Q(person=person, name="reviewer", group__state="active"),
|
||||
"Review Team Secretary": Q(person=person, name="secr", group__reviewteamsettings__isnull=False,group__state="active", ),
|
||||
"IRSG Member": (Q(person=person, name="member", group__acronym="irsg") | Q(person=person, name="chair", group__acronym="irtf") | Q(person=person, name="atlarge", group__acronym="irsg")),
|
||||
|
||||
"Robot": Q(person=person, name="robot", group__acronym="secretariat"),
|
||||
}
|
||||
|
||||
filter_expr = Q()
|
||||
|
|
|
@ -337,6 +337,7 @@ PERSON_API_KEY_VALUES = [
|
|||
("/api/v2/person/person", "/api/v2/person/person", "Secretariat"),
|
||||
("/api/meeting/session/video/url", "/api/meeting/session/video/url", "Recording Manager"),
|
||||
("/api/person/access/meetecho", "/api/person/access/meetecho", None),
|
||||
("/api/notify/meeting/registration", "/api/notify/meeting/registration", "Robot"),
|
||||
]
|
||||
PERSON_API_KEY_ENDPOINTS = [ (v, n) for (v, n, r) in PERSON_API_KEY_VALUES ]
|
||||
|
||||
|
|
|
@ -249,12 +249,13 @@ def get_meeting_registration_data(meeting):
|
|||
address = registration['Email'].strip()
|
||||
object, created = MeetingRegistration.objects.get_or_create(
|
||||
meeting_id=meeting.pk,
|
||||
first_name=first_name[:200],
|
||||
last_name=last_name[:200],
|
||||
affiliation=affiliation,
|
||||
country_code=country_code,
|
||||
email=address,
|
||||
)
|
||||
object.first_name=first_name[:200]
|
||||
object.last_name=last_name[:200]
|
||||
object.affiliation=affiliation
|
||||
object.country_code=country_code
|
||||
object.save()
|
||||
|
||||
# Add a Person object to MeetingRegistration object
|
||||
# if valid email is available
|
||||
|
|
Loading…
Reference in a new issue