Skip to content

Commit

Permalink
Merge pull request #239 from liqd/ym-2016-08-membership-requests
Browse files Browse the repository at this point in the history
Implement memberships requests
  • Loading branch information
vellip committed Sep 1, 2016
2 parents 0e95ef4 + 66e9dbb commit d98c500
Show file tree
Hide file tree
Showing 24 changed files with 379 additions and 59 deletions.
143 changes: 120 additions & 23 deletions euth/contrib/emails.py
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 added euth/memberships/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions euth/memberships/admin.py
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)
6 changes: 6 additions & 0 deletions euth/memberships/apps.py
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'
11 changes: 11 additions & 0 deletions euth/memberships/emails.py
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'
31 changes: 31 additions & 0 deletions euth/memberships/migrations/0001_initial.py
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.
44 changes: 44 additions & 0 deletions euth/memberships/models.py
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 euth/memberships/templates/emails/request_accepted.en.html
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 %}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Your membership request to {{ request.project.name }} accepted
8 changes: 8 additions & 0 deletions euth/memberships/templates/emails/request_accepted.en.txt
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 euth/memberships/templates/emails/request_received.en.html
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 %}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Access requested to {{ applicaiton.project }} on {{ site.name }}
8 changes: 8 additions & 0 deletions euth/memberships/templates/emails/request_received.en.txt
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 euth/memberships/templates/euth_memberships/request_detail.html
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 %}
11 changes: 11 additions & 0 deletions euth/memberships/urls.py
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'
),
]
40 changes: 40 additions & 0 deletions euth/memberships/views.py
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)

0 comments on commit d98c500

Please sign in to comment.