Skip to content

Commit

Permalink
Merge pull request #1420 from liqd/mn-2019-03-delete-projects
Browse files Browse the repository at this point in the history
Mn 2019 03 delete projects
  • Loading branch information
fuzzylogic2000 committed Mar 4, 2019
2 parents 19d4a91 + 1fb54cf commit 9101daa
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 3 deletions.
3 changes: 3 additions & 0 deletions euth/projects/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
class Config(AppConfig):
name = 'euth.projects'
label = 'euth_projects'

def ready(self):
import euth.projects.signals # noqa
29 changes: 29 additions & 0 deletions euth/projects/emails.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.contrib.auth import get_user_model

from adhocracy4 import emails
from euth.projects import tasks

User = get_user_model()

Expand All @@ -10,3 +11,31 @@ class ModeratorAddedEmail(emails.Email):

def get_receivers(self):
return [User.objects.get(id=self.kwargs['user_id'])]


class DeleteProjectEmail(emails.Email):
template_name = 'euth_projects/emails/delete_project'

@classmethod
def send_no_object(cls, object, *args, **kwargs):
organisation = object.organisation
object_dict = {
'name': object.name,
'initiators': list(organisation.initiators.all()
.distinct()
.values_list('email', flat=True)),
'organisation': organisation.name
}
tasks.send_async_no_object(
cls.__module__, cls.__name__,
object_dict, args, kwargs)
return []

def get_receivers(self):
return self.object['initiators']

def get_context(self):
context = super().get_context()
context['name'] = self.object['name']
context['organisation'] = self.object['organisation']
return context
11 changes: 11 additions & 0 deletions euth/projects/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.db.models.signals import post_delete
from django.dispatch import receiver

from adhocracy4.projects.models import Project

from .emails import DeleteProjectEmail


@receiver(post_delete, sender=Project)
def send_delete_project_notification(sender, instance, **kwargs):
DeleteProjectEmail.send_no_object(instance)
12 changes: 12 additions & 0 deletions euth/projects/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import importlib

from background_task import background


@background(schedule=1)
def send_async_no_object(email_module_name,
email_class_name,
object, args, kwargs):
mod = importlib.import_module(email_module_name)
cls = getattr(mod, email_class_name)
return cls().dispatch(object, *args, **kwargs)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% extends 'email_base.'|add:part_type %}
{% load verbose_name %}

{% block subject %}Deletion of project{% endblock %}

{% block headline %}The project {{ name }} was deleted.{% endblock %}

{% block content %}The project "{{ name }}" on the participation platform {{ site.name }} was deleted.{% endblock %}

{% block reason %}This email was sent to {{ receiver }}. This email was sent to you because you are an initiator of the organisation '{{ organisation }}', in wich a project was deleted.{% endblock %}
3 changes: 3 additions & 0 deletions euth/projects/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
from . import views

urlpatterns = [
url(r'^project-delete/(?P<pk>[-\w_]+)/$',
views.ProjectDeleteView.as_view(),
name='project-delete'),
url(r'^$',
views.ProjectListView.as_view(), name='project-list')
]
24 changes: 24 additions & 0 deletions euth/projects/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from django.contrib import messages
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views import generic
from rules.contrib.views import PermissionRequiredMixin

from adhocracy4.dashboard import mixins
from adhocracy4.filters import views as filter_views
Expand Down Expand Up @@ -41,3 +45,23 @@ def dispatch(self, request, *args, **kwargs):

def get_object(self, queryset=None):
return self.project


class ProjectDeleteView(PermissionRequiredMixin,
generic.DeleteView):
model = prj_models.Project
permission_required = 'a4projects.change_project'
http_method_names = ['post']
success_message = _("Project '%(name)s' was deleted successfully.")

def get_success_url(self):
return reverse_lazy('a4dashboard:project-list',
kwargs={
'organisation_slug':
self.get_object().organisation.slug}
)

def delete(self, request, *args, **kwargs):
obj = self.get_object()
messages.success(self.request, self.success_message % obj.__dict__)
return super().delete(request, *args, **kwargs)
20 changes: 20 additions & 0 deletions euth/users/migrations/0009_auto_20190301_1551.py

Large diffs are not rendered by default.

30 changes: 28 additions & 2 deletions euth_wagtail/templates/a4dashboard/includes/project_list_item.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ <h2 class="dashboard-list-name">
<button type="button" class="btn btn-gray btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
...
</button>
<ul class="dropdown-menu">
<ul class="dropdown-menu">
<li>
<form method="post" class="">
{% csrf_token %}
Expand All @@ -51,10 +51,36 @@ <h2 class="dashboard-list-name">
</button>
</form>
</li>
</ul>
<li>
<a href="#" class="dropdown-item u-fontweight-normal" data-toggle="modal" data-target="#project{{ project.pk }}DeleteModal">
<i class="fa fa-trash" aria-hidden="true"></i>
{% trans 'delete' %}
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</li>

<div class="modal fade" id="project{{ project.pk }}DeleteModal" tabindex="-1" role="dialog" aria-labelledby="projectDeleteModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="project{{ project.pk }}DeleteModal">{% blocktrans with project_name=project.name %} Are you sure you want to delete the project '{{ project_name }}'? {% endblocktrans %}</h2>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-footer">
<form action="{% url "project-delete" pk=project.pk %}" method="post">
{% csrf_token %}
<button type="submit" class="btn btn--primary">{% trans 'delete' %}</button>
<button type="button" class="btn btn--secondary" data-dismiss="modal">{% trans 'cancel' %}</button>
</form>
</div>
</div>
</div>
</div>
2 changes: 1 addition & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
git+git://github.com/liqd/adhocracy4.git@02dd093a95bf8b8033f347aa04e40e714a885688#egg=adhocracy4
Django==1.11.16 # pyup: <2.0
Django==1.11.20 # pyup: <2.0
wagtail==1.13.2 # pyup: <2.0
feedparser==5.2.1
python-dateutil==2.7.3
Expand Down
77 changes: 77 additions & 0 deletions tests/projects/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import pytest
from django.core.urlresolvers import reverse

from adhocracy4.projects import models
from adhocracy4.test.helpers import redirect_target


@pytest.mark.django_db
def test_anonymous_cannot_delete(client, project_factory):
project = project_factory()
count = models.Project.objects.all().count()
assert count == 1
url = reverse(
'project-delete',
kwargs={
'pk': project.pk
})
response = client.post(url)
assert response.status_code == 302
assert redirect_target(response) == 'account_login'
count = models.Project.objects.all().count()
assert count == 1


@pytest.mark.django_db
def test_user_cannot_delete(client, project_factory, user):
project = project_factory()
count = models.Project.objects.all().count()
assert count == 1
client.login(username=user.email, password='password')
url = reverse(
'project-delete',
kwargs={
'pk': project.pk
})
response = client.post(url)
assert response.status_code == 302
count = models.Project.objects.all().count()
assert count == 1


@pytest.mark.django_db
def test_moderator_cannot_delete(client, project_factory, user):
project = project_factory()
count = models.Project.objects.all().count()
assert count == 1
moderator = project.moderators.first()
client.login(username=moderator.email, password='password')
url = reverse(
'project-delete',
kwargs={
'pk': project.pk
})
response = client.post(url)
assert response.status_code == 302
count = models.Project.objects.all().count()
assert count == 1


@pytest.mark.django_db
def test_initator_can_delete(client, project_factory):
project = project_factory()
count = models.Project.objects.all().count()
assert count == 1
initiator = project.organisation.initiators.first()
client.login(username=initiator.email, password='password')
url = reverse(
'project-delete',
kwargs={
'pk': project.pk
})
response = client.get(url)
assert response.status_code == 405
response = client.post(url)
assert response.status_code == 302
count = models.Project.objects.all().count()
assert count == 0

0 comments on commit 9101daa

Please sign in to comment.