Commit d3888377 authored by Robert's avatar Robert

Add join by email views

parent 4feec34f
Pipeline #1269 failed with stage
in 1 minute and 40 seconds
......@@ -20,5 +20,5 @@ class Apply(forms.ModelForm):
return application
class ResignRequest(forms.Form):
class Request(forms.Form):
member = forms.EmailField(label='E-Mail-Adresse')
from core.notifications import Notification
class Member(Notification):
class MembershipRequest(Notification):
subject = 'Mitgliedschaft'
def get_context_data(self, **kwargs):
......@@ -9,6 +9,10 @@ class Member(Notification):
return super().get_context_data(**kwargs)
class Join(MembershipRequest):
pass
class MembershipCreated(Notification):
subject = 'Mitgliedschaft'
......@@ -18,3 +22,7 @@ class NoMember(Notification):
def get_formatted_recipient(self):
return '<{}>'.format(self.recipient)
class Resign(MembershipRequest):
pass
......@@ -8,11 +8,9 @@ from features.memberships.predicates import is_member_of
from . import predicates as memberships
rules.add_perm(
'memberships.join_group',
rules.is_authenticated
& ~ memberships.is_member_of
& ~ groups.is_closed)
add_perm('memberships.join', is_authenticated & ~is_closed & ~is_member_of)
add_perm('memberships.join_request', ~is_authenticated & ~is_closed)
add_perm(
'memberships.try_to_join',
......
{% extends 'stadt/form.html' %}
{% extends 'stadt/stadt.html' %}
{% block form_footer %}
<p><small>* Falls Du in der <em>echten Welt</em> noch nicht Mitglied in der Gruppe bist und es werden möchtest, sprich bitte die anderen Gruppenmitglieder an.</small></p>
{% block title %}Mitgliedschaft - {{ block.super }}{% endblock %}
{% block menu %}{% menu 'group' view.group %}{% endblock %}
{% block breadcrumb %}{% breadcrumb view.group 'Mitgliedschaft' %}{% endblock %}
{% block heading_title_text %}Mitgliedschaft{% endblock %}
{% block sidebar %}
{% include 'associations/_sidebar_entity.html' with entity=view.group %}
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
<p>Möchtest Du der Gruppe <em>{{ view.group }}</em> auf {{ site.name }} beitreten? *</p>
<button class="btn btn-primary">Der Gruppe beitreten *</button>
</form>
<p><small>* Falls Du in der <em>echten Welt</em> noch nicht Mitglied in der Gruppe bist und es werden möchtest, sprich bitte die anderen Gruppenmitglieder an.</small></p>
{% endblock %}
Jemand hat Deine E-Mail-Adresse auf {{ site.name }} angegeben. Wenn Du es nicht warst, kannst Du diese Nachricht gefahrlos ignorieren.
Wenn Du der Gruppe '{{ object }}' beitreten möchtest, bestätige dies bitte, indem Du diesem Verweis folgst:
{% url 'join-confirm' token.secret_key as url %}{{ url|full_url }}
{% extends 'stadt/stadt.html' %}
{% block title %}Mitgliedschaft - {{ block.super }}{% endblock %}
{% block menu %}{% menu 'group' view.group %}{% endblock %}
{% block breadcrumb %}{% breadcrumb view.group 'Mitgliedschaft' %}{% endblock %}
{% block heading_title_text %}Mitgliedschaft{% endblock %}
{% block sidebar %}
{% include 'associations/_sidebar_entity.html' with entity=view.group %}
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
<div class="disclaimer content-block">
<p>
Deine E-Mail-Adresse wird dauerhaft gespeichert, um Dir Benachrichtigungen zu schicken.
</p>
<p>Um die E-Mail-Adresse später zu löschen, <a href="{% url 'account_signup' %}">leg ein Benutzerkonto unter der E-Mail-Adresse an</a> und lösche es anschließend oder <a href="{% url 'create-group-conversation' about_group.pk %}">schreib uns eine Nachricht</a>.
</p>
</div>
{% include 'core/_field.html' with field=form.member %}
<button class="btn btn-primary">Der Gruppe beitreten</button>
</form>
{% endblock %}
......@@ -3,7 +3,6 @@ from django.urls import reverse
from django.test import TestCase
from core.models import PermissionToken
from features.gestalten.models import Gestalt
from features.memberships.test_mixins import MemberMixin
from . import models
......@@ -12,9 +11,33 @@ TEST_EMAIL = 'test.membership@test.local'
class Membership(MemberMixin, TestCase):
def test_memberships(self):
# force membership of TEST_EMAIL
gestalt = Gestalt.objects.get_or_create_by_email(TEST_EMAIL)
self.group.memberships.create(member=gestalt, created_by=gestalt)
# join form renders ok
join_request_url = reverse('join-request', args=(self.group.slug,))
r = self.client.get(join_request_url)
self.assertEquals(r.status_code, 200)
# join succeeds with email address
r = self.client.post(join_request_url, {'member': TEST_EMAIL})
self.assertRedirects(r, self.group.get_absolute_url())
# email with link to join confirmation is sent
token = PermissionToken.objects.get(feature_key='group-join')
join_confirm_url = reverse('join-confirm', args=(token.secret_key,))
self.assertEqual(len(mail.outbox), 1)
self.assertTrue(join_confirm_url in mail.outbox[0].body)
# join confirmation page renders ok
r = self.client.get(join_confirm_url)
self.assertEquals(r.status_code, 200)
# join confirmation creates membership
num_memberships = models.Membership.objects.count()
r = self.client.post(join_confirm_url)
self.assertRedirects(r, self.group.get_absolute_url())
self.assertEqual(models.Membership.objects.count(), num_memberships+1)
# cleanup
mail.outbox = []
# member gets notified on group content
self.client.force_login(self.gestalt.user)
......
......@@ -3,42 +3,52 @@ from django.conf import urls
urlpatterns = [
urls.url(
r'^groups/(?P<group_pk>[0-9]+)/join/$',
r'^stadt/groups/(?P<group_pk>[0-9]+)/join/$',
views.Join.as_view(),
name='join'),
name='join'
),
urls.url(
r'^stadt/groups/join/confirm/(?P<secret_key>[a-z0-9]+)/$',
views.JoinConfirm.as_view(),
name='join-confirm'
),
urls.url(
r'^(?P<group_slug>[\w-]+)/join/request/$',
views.JoinRequest.as_view(),
name='join-request'
),
urls.url(
r'^groups/(?P<group_pk>[0-9]+)/members/$',
r'^stadt/groups/(?P<group_pk>[0-9]+)/members/$',
views.Members.as_view(),
name='members'),
urls.url(
r'^groups/(?P<group_pk>[0-9]+)/members/add/$',
r'^stadt/groups/(?P<group_pk>[0-9]+)/members/add/$',
views.MemberAdd.as_view(),
name='member-create'),
urls.url(
r'^groups/(?P<group_pk>[0-9]+)/resign/$',
r'^stadt/groups/(?P<group_pk>[0-9]+)/resign/$',
views.Resign.as_view(),
name='resign'
),
urls.url(
r'^groups/resign/confirm/(?P<secret_key>[a-z0-9]+)/$',
r'^stadt/groups/resign/confirm/(?P<secret_key>[a-z0-9]+)/$',
views.ResignConfirm.as_view(),
name='resign-confirm'
),
urls.url(
r'^groups/(?P<group_pk>[0-9]+)/resign/request/$',
r'^stadt/groups/(?P<group_pk>[0-9]+)/resign/request/$',
views.ResignRequest.as_view(),
name='resign-request'
),
urls.url(
r'^associations/(?P<association_pk>[0-9]+)/apply/$',
r'^stadt/associations/(?P<association_pk>[0-9]+)/apply/$',
views.Apply.as_view(),
name='create-membership-application'),
urls.url(
r'^memberships/applications/(?P<application_pk>[0-9]+)/accept/$',
r'^stadt/memberships/applications/(?P<application_pk>[0-9]+)/accept/$',
views.AcceptApplication.as_view(),
name='accept-membership-application'),
]
......@@ -2,8 +2,9 @@ import django
from django import db, http, shortcuts, urls
from django.contrib import messages
from django.contrib.messages import info, success
from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import get_object_or_404
from django.views.generic import DeleteView, FormView
from django.views.generic import CreateView, DeleteView, FormView
import core
from core import fields, views
......@@ -78,26 +79,77 @@ class MembershipMixin(groups_views.Mixin):
return self.get_group()
class Join(MembershipMixin, views.Create):
action = 'Beitreten *'
data_field_classes = (
fields.current_gestalt('created_by'),
fields.related_object('group'),
fields.current_gestalt('member'))
description = (
'Der Gruppe <em>{{ group }}</em> auf {{ site.name }} beitreten *')
permission_required = 'memberships.join_group'
class Join(PermissionMixin, SuccessMessageMixin, CreateView):
permission_required = 'memberships.join'
model = models.Membership
fields = []
template_name = 'memberships/join.html'
success_message = 'Du bist nun Mitglied der Gruppe.'
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['instance'] = self.model(
created_by=self.gestalt, group=self.group, member=self.gestalt)
return kwargs
def get_permission_object(self):
self.gestalt = self.request.user.gestalt if self.request.user.is_authenticated else None
self.group = get_object_or_404(Group, pk=self.kwargs.get('group_pk'))
return self.group
def get_success_url(self):
return self.group.get_absolute_url()
def handle_no_permission(self):
if self.request.user.is_authenticated and self.related_object.members.filter(
pk=self.request.user.gestalt.pk).exists():
if self.gestalt and self.gestalt.user.is_authenticated and self.group.members.filter(
pk=self.gestalt.pk).exists():
django.contrib.messages.info(self.request, 'Du bist bereits Mitglied der Gruppe.')
return django.http.HttpResponseRedirect(self.related_object.get_absolute_url())
return django.http.HttpResponseRedirect(self.group.get_absolute_url())
else:
return super().handle_no_permission()
class JoinConfirm(Join):
def form_valid(self, form):
self.token.delete()
return super().form_valid(form)
def get_permission_object(self):
self.token = get_object_or_404(
PermissionToken, feature_key='group-join',
secret_key=self.kwargs.get('secret_key'))
self.gestalt = self.token.gestalt
self.group = self.token.target
return self.group
def has_permission(self):
obj = self.get_permission_object()
perms = self.get_permission_required()
return self.gestalt.user.has_perms(perms, obj)
class JoinRequest(PermissionMixin, FormView):
permission_required = 'memberships.join_request'
form_class = forms.Request
template_name = 'memberships/join_request.html'
def form_valid(self, form):
gestalt = Gestalt.objects.get_or_create_by_email(form.cleaned_data['member'])
notification = notifications.Join(self.group)
notification.token = PermissionToken.objects.create(
gestalt=gestalt, target=self.group, feature_key='group-join')
notification.send(gestalt)
info(self.request, 'Es wurde eine E-Mail an die angebene Adresse versendet.')
return super().form_valid(form)
def get_permission_object(self):
self.group = get_object_or_404(Group, slug=self.kwargs.get('group_slug'))
return self.group
def get_success_url(self):
return self.group.get_absolute_url()
class Members(gestalten_views.List):
permission_required = 'memberships.view_list'
template_name = 'memberships/list.html'
......@@ -153,12 +205,16 @@ class Resign(PermissionMixin, DeleteView):
class ResignConfirm(Resign):
def delete(self, *args, **kwargs):
self.token.delete()
return super().delete(*args, **kwargs)
def get_permission_object(self):
token = get_object_or_404(
self.token = get_object_or_404(
PermissionToken, feature_key='group-resign',
secret_key=self.kwargs.get('secret_key'))
self.gestalt = token.gestalt
self.group = token.target
self.gestalt = self.token.gestalt
self.group = self.token.target
return self.group
def has_permission(self):
......@@ -169,14 +225,14 @@ class ResignConfirm(Resign):
class ResignRequest(PermissionMixin, FormView):
permission_required = 'memberships.delete_request'
form_class = forms.ResignRequest
form_class = forms.Request
template_name = 'memberships/delete_request.html'
def form_valid(self, form):
email = form.cleaned_data['member']
try:
member = self.group.members.get_by_email(email)
notification = notifications.Member(self.group)
notification = notifications.Resign(self.group)
notification.token = PermissionToken.objects.create(
gestalt=member, target=self.group, feature_key='group-resign')
notification.send(member)
......
......@@ -82,12 +82,16 @@ class GroupUnsubscribe(PermissionMixin, DeleteView):
class GroupUnsubscribeConfirm(GroupUnsubscribe):
def delete(self, *args, **kwargs):
self.token.delete()
return super().delete(*args, **kwargs)
def get_permission_object(self):
token = get_object_or_404(
self.token = get_object_or_404(
PermissionToken, feature_key='group-unsubscribe',
secret_key=self.kwargs.get('secret_key'))
self.gestalt = token.gestalt
self.group = token.target
self.gestalt = self.token.gestalt
self.group = self.token.target
return self.group
def has_permission(self):
......
......@@ -7,7 +7,6 @@ urlpatterns = [
urls.url(r'^stadt/api/', urls.include('core.api_urls')),
urls.url(r'^stadt/', urls.include('features.conversations.urls')),
urls.url(r'^stadt/', urls.include('features.memberships.urls')),
urls.url(r'^stadt/', urls.include('features.sharing.urls')),
urls.url(r'^', urls.include('core.urls')),
......@@ -19,6 +18,7 @@ urlpatterns = [
urls.url(r'^', urls.include('features.gestalten.urls')),
urls.url(r'^', urls.include('features.gestalten.auth.urls')),
urls.url(r'^', urls.include('features.groups.urls')),
urls.url(r'^', urls.include('features.memberships.urls')),
urls.url(r'^', urls.include('features.polls.urls')),
urls.url(r'^', urls.include('features.stadt.urls')),
urls.url(r'^', urls.include('features.subscriptions.urls')),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment