Skip to content

Commit

Permalink
[orga] Allow to delete organisers and events
Browse files Browse the repository at this point in the history
Closes #476
  • Loading branch information
rixx committed Nov 5, 2018
1 parent 1128d44 commit 7712ebb
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 33 deletions.
1 change: 1 addition & 0 deletions doc/changelog.rst
Expand Up @@ -3,6 +3,7 @@
Release Notes
=============

- :feature:`476` Both events and organisers can now be deleted (although only by administrators).
- :feature:`493`: Speaker email addresses are now available via the API for users with sufficient access permissions.
- :bug:`515`: Under very rare circumstances, the pretalx database could reach a state pretalx couldn't cope with due to duplicate schedule versions being created.
- :feature:`512`: You can now configure if speakers should be asked for their availability during talk submission.
Expand Down
1 change: 1 addition & 0 deletions src/pretalx/event/models/event.py
Expand Up @@ -203,6 +203,7 @@ class orga_urls(EventUrls):
create = '/orga/event/new'
base = '/orga/event/{self.slug}'
live = '{base}/live'
delete = '{base}/delete'
cfp = '{base}/cfp'
users = '{base}/api/users'
url_list = '{base}/api/urls'
Expand Down
1 change: 1 addition & 0 deletions src/pretalx/event/models/organiser.py
Expand Up @@ -42,6 +42,7 @@ def __str__(self) -> str:

class orga_urls(EventUrls):
base = '/orga/organiser/{self.slug}'
delete = '{base}/delete'
teams = '{base}/teams'
new_team = '{teams}/new'

Expand Down
20 changes: 20 additions & 0 deletions src/pretalx/orga/templates/orga/event/delete.html
@@ -0,0 +1,20 @@
{% extends "orga/cfp/base.html" %}
{% load i18n %}
{% block content %}
<h2>
{% trans "Do you really want to delete this event?" %}
</h2>
<strong>»{{ request.event.name }}«</strong> – {% trans "ALL related data, such as submissions, and speaker profiles, and uploads, will also be deleted and cannot be restored." %}<p>
<form method="post">
{% csrf_token %}
<div class="submit-group">
<a href="{{ request.event.orga_urls.base }}" class="btn btn-lg btn-outline-info">
{% trans "Back" %}
</a>
<button type="submit" class="btn btn-lg btn-danger">
<i class="fa fa-trash"></i>
{% trans "Yes" %}
</button>
</div>
</form>
{% endblock %}
20 changes: 20 additions & 0 deletions src/pretalx/orga/templates/orga/organiser/delete.html
@@ -0,0 +1,20 @@
{% extends "orga/cfp/base.html" %}
{% load i18n %}
{% block content %}
<h2>
{% trans "Do you really want to delete this organiser?" %}
</h2>
<strong>»{{ request.organiser.name }}«</strong> – {% trans "ALL related data for ALL events, such as submissions, and speaker profiles, and uploads, will also be deleted and cannot be restored." %}<p>
<form method="post">
{% csrf_token %}
<div class="submit-group">
<a href="{{ request.organiser.orga_urls.base }}" class="btn btn-lg btn-outline-info">
{% trans "Back" %}
</a>
<button type="submit" class="btn btn-lg btn-danger">
<i class="fa fa-trash"></i>
{% trans "Yes" %}
</button>
</div>
</form>
{% endblock %}
18 changes: 17 additions & 1 deletion src/pretalx/orga/templates/orga/organiser/detail.html
@@ -1,6 +1,7 @@
{% extends "orga/base.html" %}
{% load bootstrap4 %}
{% load i18n %}
{% load rules %}

{% block title %}{% if request.event %}{{ request.event.name }}{% else %}{{ request.organiser.name }}{% endif %}{% endblock %}

Expand All @@ -9,7 +10,22 @@
<legend>{% trans "Settings" %}</legend>
{% csrf_token %}
{% bootstrap_form form layout='event' %}
{% include "orga/submit_row.html" %}
<div class="submit-group panel">
<span>
{% has_perm "person.is_administrator" request.user request.organiser as can_delete_event %}
{% if can_delete_event %}
<a class="btn btn-outline-danger btn-lg" href="{{ request.organiser.orga_urls.delete }}">
{% trans "Delete organiser" %}
</a>
{% endif %}
</span>
<span>
<button type="submit" class="btn btn-success btn-lg">
<i class="fa fa-check"></i>
{% trans "Save" %}
</button>
</span>
</div>
</fieldset>

<fieldset>
Expand Down
18 changes: 17 additions & 1 deletion src/pretalx/orga/templates/orga/settings/form.html
@@ -1,6 +1,7 @@
{% extends "orga/settings/base.html" %}
{% load bootstrap4 %}
{% load i18n %}
{% load rules %}
{% load static %}

{% block settings_content %}
Expand Down Expand Up @@ -54,7 +55,22 @@ <h2>{% trans "Settings" %}</h2>
</div>
{% bootstrap_field sform.display_header_pattern layout='event' %}
<fieldset>
{% include "orga/submit_row.html" %}
<div class="submit-group panel">
<span>
{% has_perm "person.is_administrator" request.user request.event as can_delete_event %}
{% if can_delete_event %}
<a class="btn btn-outline-danger btn-lg" href="{{ request.event.orga_urls.delete }}">
{% trans "Delete event" %}
</a>
{% endif %}
</span>
<span>
<button type="submit" class="btn btn-success btn-lg">
<i class="fa fa-check"></i>
{% trans "Save" %}
</button>
</span>
</div>
</form>
</div></div>
{% endblock %}
2 changes: 2 additions & 0 deletions src/pretalx/orga/urls.py
Expand Up @@ -23,6 +23,7 @@
url('^organiser/new$', organiser.OrganiserDetail.as_view(), name='organiser.create'),
url(f'^organiser/(?P<organiser>[{SLUG_CHARS}]+)/', include([
url('^$', organiser.OrganiserDetail.as_view(), name='organiser.view'),
url('^delete$', organiser.OrganiserDelete.as_view(), name='organiser.delete'),
url('^teams/$', organiser.TeamDetail.as_view(), name='organiser.teams'),
url('^teams/new$', organiser.TeamDetail.as_view(), name='organiser.teams.create'),
url('^teams/(?P<pk>[0-9]+)$', organiser.TeamDetail.as_view(), name='organiser.teams.view'),
Expand All @@ -36,6 +37,7 @@
url('^event/$', dashboard.DashboardEventListView.as_view(), name='event.list'),
url(f'^event/(?P<event>[{SLUG_CHARS}]+)/', include([
url('^$', dashboard.EventDashboardView.as_view(), name='event.dashboard'),
url('^delete$', event.EventDelete.as_view(), name='event.delete'),
url('^live$', event.EventLive.as_view(), name='event.live'),
url('^api/users$', person.UserList.as_view(), name='event.user_list'),
url('^api/urls/$', dashboard.url_list, name='url_list'),
Expand Down
17 changes: 16 additions & 1 deletion src/pretalx/orga/views/event.py
Expand Up @@ -9,7 +9,7 @@
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView, TemplateView, UpdateView
from django.views.generic import DeleteView, FormView, TemplateView, UpdateView
from formtools.wizard.views import SessionWizardView
from pytz import timezone
from rest_framework.authtoken.models import Token
Expand Down Expand Up @@ -425,3 +425,18 @@ def done(self, form_list, *args, **kwargs):
event.copy_data_from(steps['copy']['copy_from_event'])

return redirect(event.orga_urls.base + '?congratulations')


class EventDelete(PermissionRequired, DeleteView):
template_name = 'orga/event/delete.html'
permission_required = 'person.is_administrator'
model = Event

def get_object(self):
return getattr(self.request, 'event', None)

def delete(self, request, *args, **kwargs):
event = self.get_object()
if event:
event.shred()
return redirect('/orga/')
18 changes: 17 additions & 1 deletion src/pretalx/orga/views/organiser.py
@@ -1,9 +1,10 @@
from django.contrib import messages
from django.db.models import Q
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.views.generic import DetailView, ListView, TemplateView
from django.views.generic import DeleteView, DetailView, ListView, TemplateView

from pretalx.common.mail import SendMailException
from pretalx.common.mixins.views import PermissionRequired
Expand Down Expand Up @@ -217,3 +218,18 @@ def get_object(self):
def get_success_url(self):
messages.success(self.request, _('Saved!'))
return self.request.path


class OrganiserDelete(PermissionRequired, DeleteView):
template_name = 'orga/organiser/delete.html'
permission_required = 'person.is_administrator'
model = Organiser

def get_object(self):
return getattr(self.request, 'organiser', None)

def delete(self, request, *args, **kwargs):
organiser = self.get_object()
if organiser:
organiser.shred()
return HttpResponseRedirect('/orga/')
13 changes: 10 additions & 3 deletions src/pretalx/person/permissions.py
Expand Up @@ -7,7 +7,9 @@
def can_change_submissions(user, obj):
if not user or user.is_anonymous or not obj or not hasattr(obj, 'event'):
return False
return user.is_administrator or obj.event in user.get_events_for_permission(can_change_submissions=True)
return user.is_administrator or obj.event in user.get_events_for_permission(
can_change_submissions=True
)


@rules.predicate
Expand All @@ -31,8 +33,13 @@ def person_can_view_information(user, obj):
return submissions.exists()
if obj.exclude_unconfirmed:
return submissions.filter(state=SubmissionStates.CONFIRMED).exists()
return submissions.filter(state__in=[SubmissionStates.CONFIRMED, SubmissionStates.ACCEPTED]).exists()
return submissions.filter(
state__in=[SubmissionStates.CONFIRMED, SubmissionStates.ACCEPTED]
).exists()


rules.add_perm('person.view_information', can_change_submissions | person_can_view_information)
rules.add_perm('person.is_administrator', is_administrator)
rules.add_perm(
'person.view_information', can_change_submissions | person_can_view_information
)
rules.add_perm('person.change_information', can_change_submissions)
93 changes: 67 additions & 26 deletions src/tests/functional/orga/test_organiser.py
Expand Up @@ -31,10 +31,7 @@ def test_orga_create_organiser(administrator_client):
def test_orga_edit_organiser(orga_client, organiser):
response = orga_client.post(
organiser.orga_urls.base + '/',
data={
'name_0': 'The bestest organiser',
'name_1': 'The bestest organiser',
},
data={'name_0': 'The bestest organiser', 'name_1': 'The bestest organiser'},
follow=True,
)
organiser.refresh_from_db()
Expand All @@ -46,11 +43,14 @@ def test_orga_edit_organiser(orga_client, organiser):
@pytest.mark.django_db
def test_orga_edit_team(orga_client, organiser, event):
team = organiser.teams.first()
url = reverse('orga:organiser.teams.view', kwargs={'organiser': organiser.slug, 'pk': team.pk})
url = reverse(
'orga:organiser.teams.view', kwargs={'organiser': organiser.slug, 'pk': team.pk}
)
response = orga_client.get(url, follow=True)
assert response.status_code == 200
response = orga_client.post(
url, follow=True,
url,
follow=True,
data={
'all_events': True,
'can_change_submissions': True,
Expand Down Expand Up @@ -78,7 +78,8 @@ def test_orga_create_team(orga_client, organiser, event, is_administrator, orga_
response = orga_client.get(organiser.orga_urls.new_team, follow=True)
assert response.status_code == 200
response = orga_client.post(
organiser.orga_urls.new_team, follow=True,
organiser.orga_urls.new_team,
follow=True,
data={
'all_events': True,
'can_change_submissions': True,
Expand All @@ -101,15 +102,13 @@ def test_orga_create_team(orga_client, organiser, event, is_administrator, orga_
def test_invite_orga_member_as_orga(orga_client, organiser):
djmail.outbox = []
team = organiser.teams.get(can_change_submissions=True, is_reviewer=False)
url = reverse('orga:organiser.teams.view', kwargs={'organiser': organiser.slug, 'pk': team.pk})
url = reverse(
'orga:organiser.teams.view', kwargs={'organiser': organiser.slug, 'pk': team.pk}
)
assert team.members.count() == 1
assert team.invites.count() == 0
response = orga_client.post(
url,
{
'email': 'other@user.org',
'form': 'invite',
}, follow=True,
url, {'email': 'other@user.org', 'form': 'invite'}, follow=True
)
assert response.status_code == 200
assert team.members.count() == 1
Expand All @@ -131,26 +130,40 @@ def post(self, step, data):
return response

def submit_initial(self, organiser):
return self.post(step='initial', data={'locales': ['en', 'de'], 'organiser': organiser.pk})
return self.post(
step='initial', data={'locales': ['en', 'de'], 'organiser': organiser.pk}
)

def submit_basics(self):
return self.post(step='basics', data={
'email': 'foo@bar.com',
'locale': 'en',
'name_0': 'New event!',
'slug': 'newevent',
'timezone': 'Europe/Amsterdam',
})
return self.post(
step='basics',
data={
'email': 'foo@bar.com',
'locale': 'en',
'name_0': 'New event!',
'slug': 'newevent',
'timezone': 'Europe/Amsterdam',
},
)

def submit_timeline(self, deadline):
_now = now()
tomorrow = _now + timedelta(days=1)
date = '%Y-%m-%d'
datetime = '%Y-%m-%d %H:%M:%S'
return self.post(step='timeline', data={'date_from': _now.strftime(date), 'date_to': tomorrow.strftime(date), 'deadline': _now.strftime(datetime) if deadline else ''})
return self.post(
step='timeline',
data={
'date_from': _now.strftime(date),
'date_to': tomorrow.strftime(date),
'deadline': _now.strftime(datetime) if deadline else '',
},
)

def submit_display(self):
return self.post(step='display', data={'header_pattern': '', 'logo': '', 'primary_color': ''})
return self.post(
step='display', data={'header_pattern': '', 'logo': '', 'primary_color': ''}
)

def submit_copy(self, copy=False):
return self.post(step='copy', data={'copy_from_event': copy if copy else ''})
Expand All @@ -168,7 +181,9 @@ def test_orga_create_event(self, orga_client, organiser, deadline):
event = Event.objects.get(slug='newevent')
assert Event.objects.count() == count + 1
assert organiser.teams.count() == team_count + 1
assert organiser.teams.filter(name__icontains='new').exists(), organiser.teams.all()
assert organiser.teams.filter(
name__icontains='new'
).exists(), organiser.teams.all()
assert str(event.name) == 'New event!'
assert event.locales == ['en', 'de']

Expand All @@ -184,9 +199,13 @@ def test_orga_create_event_with_copy(self, orga_client, organiser, event, deadli
self.submit_copy(copy=event.pk)
assert Event.objects.count() == count + 1
assert organiser.teams.count() == team_count + 1
assert organiser.teams.filter(name__icontains='new').exists(), organiser.teams.all()
assert organiser.teams.filter(
name__icontains='new'
).exists(), organiser.teams.all()

def test_orga_create_event_no_new_team(self, orga_client, organiser, event, deadline):
def test_orga_create_event_no_new_team(
self, orga_client, organiser, event, deadline
):
self.client = orga_client
organiser.teams.update(all_events=True, can_create_events=True)
count = Event.objects.count()
Expand All @@ -198,3 +217,25 @@ def test_orga_create_event_no_new_team(self, orga_client, organiser, event, dead
self.submit_copy()
assert Event.objects.count() == count + 1
assert organiser.teams.count() == team_count


@pytest.mark.django_db
def test_organiser_cannot_delete_organiser(event, orga_client, submission):
assert Event.objects.count() == 1
assert Organiser.objects.count() == 1
response = orga_client.post(event.organiser.orga_urls.delete, follow=True)
assert response.status_code == 404
assert Event.objects.count() == 1
assert Organiser.objects.count() == 1


@pytest.mark.django_db
def test_administrator_can_delete_organiser(event, administrator_client, submission):
assert Event.objects.count() == 1
assert Organiser.objects.count() == 1
response = administrator_client.get(event.organiser.orga_urls.delete, follow=True)
assert response.status_code == 200
response = administrator_client.post(event.organiser.orga_urls.delete, follow=True)
assert response.status_code == 200
assert Event.objects.count() == 0
assert Organiser.objects.count() == 0

0 comments on commit 7712ebb

Please sign in to comment.