0001-api-add-a-Publik-api-to-add-events-47337.patch
chrono/api/urls.py | ||
---|---|---|
33 | 33 |
views.slot_status, |
34 | 34 |
name='api-event-status', |
35 | 35 |
), |
36 | 36 |
url( |
37 | 37 |
r'^agenda/(?P<agenda_identifier>[\w-]+)/bookings/(?P<event_identifier>[\w-]+)/$', |
38 | 38 |
views.slot_bookings, |
39 | 39 |
name='api-event-bookings', |
40 | 40 |
), |
41 |
url( |
|
42 |
r'^agenda/(?P<agenda_identifier>[\w-]+)/add-event/$', |
|
43 |
views.agenda_add_event, |
|
44 |
name='api-agenda-add-event', |
|
45 |
), |
|
41 | 46 |
url( |
42 | 47 |
r'^agenda/meetings/(?P<meeting_identifier>[\w-]+)/datetimes/$', |
43 | 48 |
views.meeting_datetimes, |
44 | 49 |
name='api-agenda-meeting-datetimes-legacy', |
45 | 50 |
), |
46 | 51 |
url(r'^agenda/(?P<agenda_identifier>[\w-]+)/meetings/$', views.meeting_list, name='api-agenda-meetings'), |
47 | 52 |
url( |
48 | 53 |
r'^agenda/(?P<agenda_identifier>[\w-]+)/meetings/(?P<meeting_identifier>[\w-]+)/$', |
chrono/api/views.py | ||
---|---|---|
1483 | 1483 | |
1484 | 1484 |
def get(self, request, booking_pk=None, format=None): |
1485 | 1485 |
booking = get_object_or_404(Booking, id=booking_pk) |
1486 | 1486 |
response = HttpResponse(booking.get_ics(request), content_type='text/calendar') |
1487 | 1487 |
return response |
1488 | 1488 | |
1489 | 1489 | |
1490 | 1490 |
booking_ics = BookingICS.as_view() |
1491 | ||
1492 | ||
1493 |
class ExceptionHandlerMixin(object): |
|
1494 |
def handle_exception(self, exc): |
|
1495 |
if not isinstance(exc, APIError): |
|
1496 |
exc = APIError(str(exc), err_class=str(type(exc))) |
|
1497 |
return exc.to_response() |
|
1498 | ||
1499 | ||
1500 |
class ViewableAgendaMixin(ExceptionHandlerMixin): |
|
1501 |
agenda = None |
|
1502 | ||
1503 |
def set_agenda(self, **kwargs): |
|
1504 |
agenda_identifier = kwargs.get('agenda_identifier') |
|
1505 |
try: |
|
1506 |
self.agenda = Agenda.objects.get(slug=agenda_identifier) |
|
1507 |
except Agenda.DoesNotExist: |
|
1508 |
raise APIError( |
|
1509 |
_('The agenda "%s" does not exist.') % agenda_identifier, |
|
1510 |
err_class='invalid agenda' |
|
1511 |
) |
|
1512 | ||
1513 |
def initial(self, request, *args, **kwargs): |
|
1514 |
super().initial(request, *args, **kwargs) |
|
1515 |
self.set_agenda(**kwargs) |
|
1516 |
if not self.check_agenda_permissions(request.user): |
|
1517 |
raise APIError( |
|
1518 |
_('User "%s" cannot access agenda "%s".') % ( |
|
1519 |
str(request.user), self.agenda.slug), |
|
1520 |
err_class='invalid agenda' |
|
1521 |
) |
|
1522 | ||
1523 |
def check_agenda_permissions(self, user): |
|
1524 |
return self.agenda.can_be_viewed(user) |
|
1525 | ||
1526 |
def success(self, event): |
|
1527 |
response = {'err': 0, 'event_id': event.pk} |
|
1528 |
return Response(response) |
|
1529 | ||
1530 | ||
1531 |
class ManagedAgendaMixin(ViewableAgendaMixin): |
|
1532 |
def check_agenda_permissions(self, user): |
|
1533 |
return self.agenda.can_be_managed(user) |
|
1534 | ||
1535 | ||
1536 |
class EventSerializer(serializers.ModelSerializer): |
|
1537 |
class Meta: |
|
1538 |
model = Event |
|
1539 |
fields = [ |
|
1540 |
'start_datetime', |
|
1541 |
'duration', |
|
1542 |
'publication_date', |
|
1543 |
'places', |
|
1544 |
'waiting_list_places', |
|
1545 |
'label', |
|
1546 |
'description', |
|
1547 |
'pricing', |
|
1548 |
'url', |
|
1549 |
] |
|
1550 | ||
1551 | ||
1552 |
class AgendaAddEventView(ManagedAgendaMixin, APIView): |
|
1553 |
permission_classes = (permissions.IsAuthenticated, ) |
|
1554 |
serializer_class = EventSerializer |
|
1555 | ||
1556 |
def post(self, request, *args, **kwargs): |
|
1557 |
serializer = self.serializer_class(data=request.data) |
|
1558 |
if not serializer.is_valid(): |
|
1559 |
raise APIError( |
|
1560 |
_('invalid payload'), |
|
1561 |
err_class='invalid payload', |
|
1562 |
errors=serializer.errors |
|
1563 |
) |
|
1564 |
payload = serializer.validated_data |
|
1565 |
event = Event.objects.create(agenda=self.agenda, **payload) |
|
1566 |
return self.success(event) |
|
1567 | ||
1568 | ||
1569 |
agenda_add_event = AgendaAddEventView.as_view() |
tests/test_api.py | ||
---|---|---|
2 | 2 | |
3 | 3 |
import datetime |
4 | 4 |
import mock |
5 | 5 |
import urllib.parse as urlparse |
6 | 6 |
import pytest |
7 | 7 |
import sys |
8 | 8 | |
9 | 9 |
from django.contrib.auth import get_user_model |
10 |
from django.contrib.auth.models import Group |
|
10 | 11 |
from django.db import connection |
11 | 12 |
from django.test import override_settings |
12 | 13 |
from django.test.utils import CaptureQueriesContext |
13 | 14 |
from django.utils.timezone import now, make_aware, localtime |
14 | 15 | |
15 | 16 |
from chrono.agendas.models import ( |
16 | 17 |
Agenda, |
17 | 18 |
Booking, |
... | ... | |
40 | 41 |
user = User.objects.create( |
41 | 42 |
username='john.doe', first_name=u'John', last_name=u'Doe', email='john.doe@example.net' |
42 | 43 |
) |
43 | 44 |
user.set_password('password') |
44 | 45 |
user.save() |
45 | 46 |
return user |
46 | 47 | |
47 | 48 | |
49 |
@pytest.fixture |
|
50 |
def managers_group(): |
|
51 |
group, _ = Group.objects.get_or_create(name='Managers') |
|
52 |
return group |
|
53 | ||
54 | ||
55 |
@pytest.fixture |
|
56 |
def manager_user(managers_group): |
|
57 |
User = get_user_model() |
|
58 |
user = User.objects.create_user('manager', password='manager') |
|
59 |
user.groups.set([managers_group]) |
|
60 |
return user |
|
61 | ||
62 | ||
48 | 63 |
@pytest.fixture(params=['Europe/Brussels', 'Asia/Kolkata', 'Brazil/East']) |
49 | 64 |
def time_zone(request, settings): |
50 | 65 |
settings.TIME_ZONE = request.param |
51 | 66 | |
52 | 67 | |
53 | 68 |
# 2017-05-20 -> saturday |
54 | 69 |
@pytest.fixture( |
55 | 70 |
params=[ |
... | ... | |
4573 | 4588 |
agenda_foo.save() |
4574 | 4589 |
resp = app.get(foo_api_url, params=params) |
4575 | 4590 |
assert len(resp.json['data']) == 16 |
4576 | 4591 |
# also on virtual agenda |
4577 | 4592 |
virtual_agenda.maximal_booking_delay = 5 |
4578 | 4593 |
virtual_agenda.save() |
4579 | 4594 |
resp = app.get(virtual_api_url, params=params) |
4580 | 4595 |
assert len(resp.json['data']) == 16 |
4596 | ||
4597 | ||
4598 |
def test_add_event(app, user): |
|
4599 |
agenda = Agenda(label=u'Foo bar') |
|
4600 |
agenda.maximal_booking_delay = 0 |
|
4601 |
agenda.save() |
|
4602 | ||
4603 |
params = { |
|
4604 |
'start_datetime': '2020-12-08 15:38', |
|
4605 |
'places': 10, |
|
4606 |
} |
|
4607 |
api_url = '/api/agenda/%s/add-event/' % (agenda.slug) |
|
4608 | ||
4609 |
# not an admin user |
|
4610 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
4611 |
resp = app.post(api_url, params=params, status=200) |
|
4612 |
assert resp.json['err'] |
|
4613 |
assert resp.json['err_desc'] == 'User "john.doe" cannot access agenda "foo-bar".' |
|
4614 | ||
4615 |
# add an event |
|
4616 |
user.is_staff = True |
|
4617 |
user.save() |
|
4618 |
resp = app.post(api_url, params=params) |
|
4619 |
assert not resp.json['err'] |
|
4620 |
event_id = resp.json['event_id'] |
|
4621 |
assert Event.objects.filter(agenda=agenda).count() == 1 |
|
4622 |
event = Event.objects.filter(agenda=agenda, id=event_id)[0] |
|
4623 |
assert str(event.start_datetime) == '2020-12-08 14:38:00+00:00' |
|
4624 |
assert str(event.start_datetime.tzinfo) == 'UTC' |
|
4625 |
assert event.places == 10 |
|
4626 |
assert event.publication_date is None |
|
4627 | ||
4628 |
# add with a description |
|
4629 |
params = { |
|
4630 |
'start_datetime': '2020-12-08 15:38', |
|
4631 |
'publication_date': '2020-05-11', |
|
4632 |
'description': 'A description', |
|
4633 |
'places': 11, |
|
4634 |
} |
|
4635 |
resp = app.post(api_url, params=params) |
|
4636 |
event_id = resp.json['event_id'] |
|
4637 |
event = Event.objects.filter(agenda=agenda, id=event_id)[0] |
|
4638 |
assert event.description == 'A description' |
|
4639 |
assert event.publication_date == datetime.date(2020, 5, 11) |
|
4640 | ||
4641 |
# add with errors in datetime parts |
|
4642 |
params = { |
|
4643 |
'start_datetime': '2020-12-08 minuit', |
|
4644 |
'places': 10, |
|
4645 |
} |
|
4646 |
resp = app.post(api_url, params=params, status=200) |
|
4647 |
assert resp.json['err'] |
|
4648 |
assert resp.json['err_desc'] == 'invalid payload' |
|
4649 |
assert 'Datetime has wrong format' in resp.json['errors']['start_datetime'][0] |
|
4650 |
assert Event.objects.filter(agenda=agenda).count() == 2 |
|
4651 | ||
4652 | ||
4653 |
def test_add_event_on_missing_agenda(app, user): |
|
4654 |
api_url = '/api/agenda/%s/add-event/' % ('999') |
|
4655 | ||
4656 |
# no authentication |
|
4657 |
resp = app.post(api_url, status=200) |
|
4658 |
assert resp.json['err'] |
|
4659 |
assert resp.json['err_desc'] == 'Authentication credentials were not provided.' |
|
4660 | ||
4661 |
# wrong password |
|
4662 |
app.authorization = ('Basic', ('john.doe', 'wrong')) |
|
4663 |
resp = app.post(api_url, status=200) |
|
4664 |
assert resp.json['err'] |
|
4665 |
assert resp.json['err_desc'] == 'Invalid username/password.' |
|
4666 | ||
4667 |
# wrong agenda |
|
4668 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
4669 |
resp = app.post(api_url, status=200) |
|
4670 |
assert resp.json['err'] |
|
4671 |
assert resp.json['err_desc'] == 'The agenda "999" does not exist.' |
|
4672 | ||
4673 | ||
4674 |
def test_add_event_as_manager(app, manager_user): |
|
4675 |
agenda = Agenda(label=u'Foo bar') |
|
4676 |
agenda.view_role = manager_user.groups.all()[0] |
|
4677 |
agenda.save() |
|
4678 | ||
4679 |
app.authorization = ('Basic', ('manager', 'manager')) |
|
4680 |
params = { |
|
4681 |
'start_datetime': '2020-12-08 15:38', |
|
4682 |
'places': 10, |
|
4683 |
} |
|
4684 |
api_url = '/api/agenda/%s/add-event/' % (agenda.slug) |
|
4685 |
resp = app.post(api_url, params=params, status=200) |
|
4686 |
assert resp.json['err'] |
|
4687 |
assert resp.json['err_desc'] == 'User "manager" cannot access agenda "foo-bar".' |
|
4688 |
assert not Event.objects.filter(agenda=agenda).count() |
|
4689 | ||
4690 |
agenda.edit_role = manager_user.groups.all()[0] |
|
4691 |
agenda.save() |
|
4692 |
resp = app.post(api_url, params=params) |
|
4693 |
assert not resp.json['err'] |
|
4694 |
assert Event.objects.filter(agenda=agenda).count() |
|
4581 |
- |