-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #239 from liqd/ym-2016-08-membership-requests
Implement memberships requests
- Loading branch information
Showing
24 changed files
with
379 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,129 @@ | ||
from email.mime.image import MIMEImage | ||
|
||
from django.conf import settings | ||
from django.contrib.sites import models as site_models | ||
from django.contrib.staticfiles import finders | ||
from django.core.mail.message import EmailMultiAlternatives | ||
from django.core.urlresolvers import reverse | ||
from django.template.loader import select_template | ||
from django.utils.translation import get_language | ||
|
||
|
||
def send_email_with_template(receivers, template, context): | ||
languages = [get_language(), 'en'] | ||
subject = select_template(['emails/{}.{}.subject'.format(template, lang) | ||
for lang in languages]) | ||
plaintext = select_template(['emails/{}.{}.txt'.format(template, lang) | ||
for lang in languages]) | ||
html = select_template(['emails/{}.{}.html'.format(template, lang) | ||
for lang in languages]) | ||
|
||
mail = EmailMultiAlternatives( | ||
subject=subject.render(context).strip(), | ||
body=plaintext.render(context), | ||
from_email=settings.DEFAULT_FROM_EMAIL, | ||
to=receivers, | ||
) | ||
mail.mixed_subtype = 'related' | ||
filename = finders.find('images/logo.png') | ||
f = open(filename, 'rb') | ||
opin_logo = MIMEImage(f.read()) | ||
opin_logo.add_header('Content-ID', '<{}>'.format('opin_logo')) | ||
mail.attach(opin_logo) | ||
mail.attach_alternative(html.render(context), 'text/html') | ||
mail.send() | ||
class Email(): | ||
site_id = 1 | ||
object = None | ||
template_name = None | ||
fallback_language = 'en' | ||
for_moderator = False | ||
|
||
def get_site(self): | ||
return site_models.Site.objects.get(pk=self.site_id) | ||
|
||
def get_url(self, view, *args, **kwargs): | ||
site = self.get_site() | ||
ssl_enabled = True | ||
if site.domain.startswith('localhost:'): | ||
ssl_enabled = False | ||
|
||
url = 'http{ssl_flag}://{domain}{path}'.format( | ||
ssl_flag='s' if ssl_enabled else '', | ||
domain=site.domain, | ||
path=reverse(view, *args, **kwargs) | ||
) | ||
return url | ||
|
||
def get_context(self): | ||
object_context_key = self.object.__class__.__name__.lower() | ||
return { | ||
'email': self, | ||
'site': self.get_site(), | ||
object_context_key: self.object | ||
} | ||
|
||
def get_receivers(self): | ||
if self.for_moderator: | ||
return self.object.project.moderators.all() | ||
else: | ||
return [getattr(self.object, 'user_key')] | ||
|
||
def get_receiver_emails(self): | ||
return [receiver.email for receiver in self.get_receivers()] | ||
|
||
def get_attachments(self): | ||
return [] | ||
|
||
@classmethod | ||
def send(cls, object, *args, **kwargs): | ||
return cls().dispatch(object, *args, **kwargs) | ||
|
||
def dispatch(self, object, *args, **kwargs): | ||
self.object = object | ||
languages = [get_language(), self.fallback_language] | ||
receivers = self.get_receiver_emails() | ||
context = self.get_context() | ||
attachments = self.get_attachments() | ||
template = self.template_name | ||
|
||
subject = select_template([ | ||
'emails/{}.{}.subject'.format(template, lang) for lang in languages | ||
]) | ||
plaintext = select_template([ | ||
'emails/{}.{}.txt'.format(template, lang) for lang in languages | ||
]) | ||
html = select_template([ | ||
'emails/{}.{}.html'.format(template, lang) for lang in languages | ||
]) | ||
mail = EmailMultiAlternatives( | ||
subject=subject.render(context).strip(), | ||
body=plaintext.render(context), | ||
from_email=settings.DEFAULT_FROM_EMAIL, | ||
to=receivers, | ||
) | ||
for attachment in attachments: | ||
mail.attach(attachment) | ||
mail.attach_alternative(html.render(context), 'text/html') | ||
mail.send() | ||
return mail | ||
|
||
|
||
class UserNotification(Email): | ||
user_attr_name = 'creator' | ||
|
||
def get_receivers(self): | ||
[getattr(self.object, self.user_attr_name)] | ||
|
||
def get_context(self): | ||
context = super.get_context(self) | ||
context['receiver'] = getattr(self.object, self.user_attr_name) | ||
|
||
|
||
class ModeratorNotification(Email): | ||
def get_receivers(self): | ||
return self.object.project.moderators.all() | ||
|
||
|
||
class OpinEmail(Email): | ||
def get_attachments(self): | ||
attachments = super().get_attachments() | ||
filename = finders.find('images/logo.png') | ||
f = open(filename, 'rb') | ||
opin_logo = MIMEImage(f.read()) | ||
opin_logo.add_header('Content-ID', '<{}>'.format('opin_logo')) | ||
return attachments + [opin_logo] | ||
|
||
|
||
def send_email_with_template(receivers, template, additional_context): | ||
|
||
class EmailWithTemplate(OpinEmail): | ||
template_name = template | ||
|
||
def get_receiver_emails(self): | ||
return receivers | ||
|
||
def get_context(self): | ||
context = super().get_context() | ||
for d in additional_context.dicts: | ||
context.update(d) | ||
return context | ||
|
||
EmailWithTemplate.send(None) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.contrib import admin | ||
|
||
from . import models | ||
|
||
admin.site.register(models.Request) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class MembershipsConfig(AppConfig): | ||
name = 'euth.memberships' | ||
label = 'euth_memberships' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from euth.contrib import emails | ||
|
||
|
||
class RequestReceivedEmail(emails.OpinEmail, | ||
emails.ModeratorNotification): | ||
template_name = 'request_received' | ||
|
||
|
||
class RequestAcceptedEmail(emails.OpinEmail, | ||
emails.UserNotification): | ||
template_name = 'request_accepted' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# -*- coding: utf-8 -*- | ||
from __future__ import unicode_literals | ||
|
||
from django.db import migrations, models | ||
import django.utils.timezone | ||
from django.conf import settings | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('euth_projects', '0003_auto_20160715_1640'), | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='Request', | ||
fields=[ | ||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), | ||
('created', models.DateTimeField(editable=False, default=django.utils.timezone.now)), | ||
('modified', models.DateTimeField(editable=False, null=True, blank=True)), | ||
('creator', models.ForeignKey(to=settings.AUTH_USER_MODEL)), | ||
('project', models.ForeignKey(to='euth_projects.Project')), | ||
], | ||
), | ||
migrations.AlterUniqueTogether( | ||
name='request', | ||
unique_together=set([('creator', 'project')]), | ||
), | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from django.conf import settings | ||
from django.db import models | ||
|
||
from euth.contrib.base_models import TimeStampedModel | ||
from euth.projects import models as prj_models | ||
|
||
from . import emails | ||
|
||
|
||
class RequestManager(models.Manager): | ||
def request_membership(self, project, user): | ||
request = super().create(creator=user, project=project) | ||
emails.RequestReceivedEmail.send(request) | ||
return request | ||
|
||
|
||
class Request(TimeStampedModel): | ||
""" | ||
An requestt for joining a private project. | ||
""" | ||
creator = models.ForeignKey( | ||
settings.AUTH_USER_MODEL, | ||
on_delete=models.CASCADE | ||
) | ||
project = models.ForeignKey( | ||
prj_models.Project, | ||
on_delete=models.CASCADE | ||
) | ||
|
||
class Meta: | ||
unique_together = ('creator', 'project') | ||
|
||
objects = RequestManager() | ||
|
||
def __str__(self): | ||
return "Request by {s.creator} for {s.project}".format(s=self) | ||
|
||
def accept(self): | ||
self.project.participants.add(self.creator) | ||
self.delete() | ||
emails.RequestAcceptedEmail.send(self) | ||
|
||
def decline(self): | ||
self.delete() |
19 changes: 19 additions & 0 deletions
19
euth/memberships/templates/emails/request_accepted.en.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{% extends 'email_base.html' %} | ||
{% block subject %} | ||
Your membership request to {{ request.project.name }} accepted | ||
{% endblock %} | ||
{% block content %} | ||
<h2>Your membership request to {{ request.project.name }} on {{ request.site.name }}</h2> | ||
<p>Dear {{ receiver.username }},</p> | ||
|
||
<p> | ||
Your membership request was approved, you are now participant of | ||
<a href={% email.get_url 'project-detail' slug=request.project.slug %}>{{ request.project.name }}</a>. | ||
</p> | ||
|
||
<p> | ||
Best wishes<br> | ||
<br> | ||
{{ site.name }} team | ||
</p> | ||
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Your membership request to {{ request.project.name }} accepted |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
Dear {{ receiver.username }}, | ||
|
||
|
||
Your membership request was approved, you are now participant of | ||
{{ request.project.name }}. Visit the project page using the link below: | ||
|
||
|
||
{% email.get_url 'project-detail' slug=request.project.slug %}>{{ request.project.name }} |
24 changes: 24 additions & 0 deletions
24
euth/memberships/templates/emails/request_received.en.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{% extends 'email_base.html' %} | ||
{% block subject %} | ||
Access requested to {{ request.project }} on {{ site.name }} | ||
{% endblock %} | ||
{% block content %} | ||
<h2>You requested to register to {{sitename}}</h2> | ||
<p>Dear moderators of {{ request.project }},</p> | ||
|
||
<p> | ||
We have received a membership request for your project on {{ site.name }}: | ||
</p> | ||
<p> | ||
{{ request.creator.username }} ({{ request.creator.email }}) | ||
</p> | ||
<p> | ||
Please visit <a href="{% url 'dashboard-overview' %}">{{ site.name }}</a> to accept or decline. | ||
</p> | ||
|
||
<p> | ||
Best wishes<br> | ||
<br> | ||
{{site.name}} team | ||
</p> | ||
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Access requested to {{ applicaiton.project }} on {{ site.name }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
We have received a membership request for your project on {{ site.name }}: | ||
|
||
{{ request.creator.username }} ({{ request.creator.email }}) | ||
|
||
Please visit {{ site.name }}[1] to accept or decline. | ||
|
||
|
||
[1] {% url 'dashboard-overview' %} |
17 changes: 17 additions & 0 deletions
17
euth/memberships/templates/euth_memberships/request_detail.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{% extends "base.html" %} | ||
{% load i18n %} | ||
{% block content %} | ||
{% include 'euth_projects/includes/project_hero_unit.html' with project=view.project %} | ||
{% if join_request %} | ||
<p> | ||
{% blocktrans with date=join_request.created %} | ||
You have applied on {{ date }}. Your request is awaiting moderation. | ||
{% endblocktrans %} | ||
</p> | ||
{% else %} | ||
<form enctype="multipart/form-data" action="{{ request.path }}" method="post"> | ||
{% csrf_token %} | ||
<button type="submit" class="submit-button">{% trans 'request membership' %}</button> | ||
</form> | ||
{% endif %} | ||
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from django.conf.urls import url | ||
|
||
from . import views | ||
|
||
urlpatterns = [ | ||
url( | ||
r'^apply/(?P<project_slug>[-\w_]+)/$', | ||
views.RequestView.as_view(), | ||
name='memberships-request' | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
from django.http import Http404 | ||
from django.shortcuts import redirect | ||
from django.views import generic | ||
from rules.compat import access_mixins as mixin | ||
|
||
from euth.projects import models as prj_models | ||
|
||
from . import models | ||
|
||
|
||
class RequestView(mixin.LoginRequiredMixin, generic.DetailView): | ||
""" | ||
Displays membership request if it exists or allows to create one. | ||
""" | ||
template_name = 'euth_memberships/request_detail.html' | ||
model = models.Request | ||
slug_field = 'project__slug' | ||
slug_url_kwarg = 'project_slug' | ||
context_object_name = 'join_request' | ||
fields = [] | ||
|
||
def get_queryset(self): | ||
return self.model.objects.filter(creator=self.request.user) | ||
|
||
def post(self, request, *args, **kwargs): | ||
user = request.user | ||
project = self.project | ||
models.Request.objects.request_membership(project, user) | ||
return redirect(self.request.path) | ||
|
||
def get_object(self, queryset=None): | ||
try: | ||
return super().get_object(queryset) | ||
except Http404: | ||
return None | ||
|
||
@property | ||
def project(self): | ||
project_slug = self.kwargs[self.slug_url_kwarg] | ||
return prj_models.Project.objects.get(slug=project_slug) |
Oops, something went wrong.