Refactored email functionality go into core neried. Few minor fixes and restructured templates

This commit is contained in:
Shalabh Aggarwal 2012-09-07 15:08:02 +05:30
parent 47c6f03f59
commit 26cc02ffab
7 changed files with 238 additions and 115 deletions

View File

@ -14,11 +14,9 @@ import warnings
from datetime import datetime
from itertools import groupby, chain
from mimetypes import guess_type
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from nereid import (request, abort, render_template, login_required, url_for,
redirect, flash, jsonify, current_app)
redirect, flash, jsonify, current_app, render_email)
from flask import send_file
from nereid.ctx import has_request_context
from nereid.signals import registration
@ -82,8 +80,10 @@ class ProjectInvitation(ModelSQL, ModelView):
_name = 'project.work.invitation'
_description = __doc__
email = fields.Char('Email', required=True)
invitation_code = fields.Char('Invitation Code', required=True)
email = fields.Char('Email', required=True, select=True)
invitation_code = fields.Char(
'Invitation Code', select=True
)
nereid_user = fields.Many2One('nereid.user', 'Nereid User')
project = fields.Many2One('project.work', 'Project')
@ -101,7 +101,7 @@ class ProjectInvitation(ModelSQL, ModelView):
ProjectInvitation()
class ProjectWorkinvitation(ModelSQL):
class ProjectWorkInvitation(ModelSQL):
"Project Work Invitation"
_name = 'project.work-project.invitation'
_description = __doc__
@ -115,7 +115,7 @@ class ProjectWorkinvitation(ModelSQL):
ondelete='CASCADE', select=1, required=True
)
ProjectWorkinvitation()
ProjectWorkInvitation()
class Project(ModelSQL, ModelView):
@ -162,7 +162,7 @@ class Project(ModelSQL, ModelView):
all_participants = fields.Function(
fields.Many2Many(
'project.work-nereid.user', None, None,
'project.work-nereid.user', 'project', 'user',
'All Participants', depends=['company']
), 'get_all_participants'
)
@ -451,76 +451,6 @@ class Project(ModelSQL, ModelView):
])
return render_template('project/projects.jinja', projects=projects)
def render_email_message(self, templates, subject, recepients, project,
obj=None):
"""Read the templates for email messages, format them, construct
the email from them and return the multipart email instance
:param templates: A dictionary in format:
'text': <Text email template path>
'html': <HTML email template path>
:param subject: Email subject
:param recepients: Email IDs to recepients
:return: Email multipart instance
"""
msg = MIMEMultipart('alternative')
msg['subject'] = subject
msg['from'] = CONFIG['smtp_user']
msg['to'] = recepients
# Create the body of the message (a plain-text and an HTML version).
# text is your plain-text email
# html is your html version of the email
# if the reciever is able to view html emails then only the html
# email will be displayed
if templates.get('text'):
text = render_template(templates['text'], project=project, obj=obj)
part1 = MIMEText(text, 'plain')
msg.attach(part1)
if templates.get('html'):
html = render_template(templates['html'], project=project, obj=obj)
part2 = MIMEText(html, 'html')
msg.attach(part2)
return msg
def split_emails(email_ids):
"""Email IDs could be separated by ';' or ','
>>> email_list = '1@x.com;2@y.com , 3@z.com '
>>> emails = split_emails(email_list)
>>> emails
['1@x.com', '2@y.com', '3@z.com']
:param email_ids: email id
:type email_ids: str or unicode
"""
if not email_ids:
return [ ]
email_ids = email_ids.replace(' ', '').replace(',', ';')
return email_ids.split(';')
def send_email(self, email_id):
"""
Send out the given email using the SMTP_CLIENT if configured in the
Tryton Server configuration
:param email_id: ID of the email to be sent
"""
email_obj = Pool().get('electronic_mail')
email_record = email_obj.browse(email_id)
recepients = [ ]
for field in ('to', 'cc', 'bcc'):
recepients.extend(self.split_emails(getattr(email_record, field)))
server = get_smtp_server()
server.sendmail(email_record.from_, recepients,
email_obj._get_email(email_record))
server.quit()
return True
@login_required
def invite(self, project_id):
"""Invite a user via email to the project
@ -529,7 +459,6 @@ class Project(ModelSQL, ModelView):
"""
nereid_user_obj = Pool().get('nereid.user')
project_invitation_obj = Pool().get('project.work.invitation')
email_object = Pool().get('electronic_mail')
data_obj = Pool().get('ir.model.data')
if not request.method == 'POST':
@ -542,21 +471,23 @@ class Project(ModelSQL, ModelView):
existing_user_id = nereid_user_obj.search([
('email', '=', email),
('company', '=', request.nereid_website.company.id),
])
subject = 'You have been invited to join the project [%s]' \
], limit=1)
subject = '[%s] You have been invited to join the project' \
% project.name
mailbox_id = data_obj.get_id(
'nereid_project', 'nereid_project_email_mailbox'
)
if existing_user_id:
existing_user = nereid_user_obj.browse(existing_user_id)
email_templates = {
'text': 'project/emails/inform_addition_2_project_text.html',
'html': 'project/emails/inform_addition_2_project_html.html'
}
email = self.render_email_message(
email_templates, subject, email, project, obj=existing_user
existing_user = nereid_user_obj.browse(existing_user_id[0])
email_message = render_email(
text_template='project/emails/inform_addition_2_project_text.html',
subject=subject, to=email, from_email=CONFIG['smtp_from'],
context={
'project': project, 'user': existing_user
}
)
self.write(
project.id, {
'participants': [('add', existing_user_id)]
}
)
flash_message = "%s has been invited to the project" \
% existing_user.display_name
@ -572,19 +503,19 @@ class Project(ModelSQL, ModelView):
'invitation_code': invitation_code
})
new_invite = project_invitation_obj.browse(new_invite_id)
email_templates = {
'text': 'project/emails/invite_2_project_text.html',
'html': 'project/emails/invite_2_project_html.html'
}
email = self.render_email_message(
email_templates, subject, email, project, obj=new_invite
email_message = render_email(
text_template='project/emails/invite_2_project_text.html',
subject=subject, to=email, from_email=CONFIG['smtp_from'],
context={
'project': project, 'invitation': new_invite
}
)
flash_message = "%s has been invited to the project" % email
email_id = email_object.create_from_email(
email, mailbox_id
)
self.send_email(email_id)
server = get_smtp_server()
server.sendmail(CONFIG['smtp_from'], [email],
email_message.as_string())
server.quit()
if request.is_xhr:
return jsonify({
@ -593,6 +524,33 @@ class Project(ModelSQL, ModelView):
flash(flash_message)
return redirect(request.referrer)
@login_required
def remove_participant(self, project_id, participant_id):
"""Remove the participant form project
"""
# Check if user is among the project admins
if not request.nereid_user.is_project_admin(request.nereid_user):
flash("Sorry! You are not allowed to remove participants. \
Contact your project admin for the same.")
return redirect(request.referrer)
if request.method == 'POST' and request.is_xhr:
project = self.get_project(project_id)
records_to_update = [project.id]
records_to_update.extend([child.id for child in project.children])
self.write(
records_to_update, {
'participants': [('unlink', [participant_id])]
}
)
return jsonify({
'success': True,
})
flash("Could not remove participant! Try again.")
return redirect(request.referrer)
@login_required
def render_task_list(self, project_id):
"""
@ -610,7 +568,7 @@ class Project(ModelSQL, ModelView):
query = request.args.get('q', None)
if query:
# This search is probably the suckiest search in the
# This search is probably the suckiest search in the
# history of mankind in terms of scalability and utility
# TODO: Figure out something better
filter_domain.append(('name', 'ilike', '%%%s%%' % query))
@ -1244,17 +1202,18 @@ def invitation_new_user_handler(nereid_user_id):
# Just return silently. This KeyError is cause if the module is not
# installed for a specific database but exists in the python path
# and is loaded by the tryton module loader
current_app.logger.warning(
"nereid-project module installed but not in database"
warnings.warn(
"nereid-project module installed but not in database",
DeprecationWarning, stacklevel=2
)
return
invitation_code = request.args.get('invitation_code', None)
invitation_code = request.args.get('invitation_code')
if not invitation_code:
return
ids = invitation_obj.search({
'invitation_code': invitation_code,
})
ids = invitation_obj.search([
('invitation_code', '=', invitation_code)
])
if not ids:
return
@ -1269,3 +1228,4 @@ def invitation_new_user_handler(nereid_user_id):
'participants': [('add', [nereid_user_id])]
}
)
invitation_obj.delete(invitation.id)

View File

@ -3,10 +3,6 @@
of this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record id="nereid_project_email_mailbox" model="electronic_mail.mailbox">
<field name="name">Nereid Project Mailbox</field>
</record>
<record id="nereid_work_view_form" model="ir.ui.view">
<field name="model">project.work</field>
<field name="inherit" ref="project.work_view_form"/>

View File

@ -0,0 +1,74 @@
{% macro render_field(field, class_="") %}
<div class="clearfix {% if field.errors %}error{% endif %}">
<label for="{{ field.name }}">{{ field.label.text }}
{% if field.flags.required %}<span>*</span>{% endif %}:
</label>
<div class="input">
{% if field.flags.required %}
{% set class_ = class_ + " required" %}
{% endif %}
{{ field(class_=class_, **kwargs) }}
{% if field.description %}
<br/><span class="help-inline">{{ field.description }}</span>
{% endif %}
{% for error in field.errors %}
<br/><span class="help-inline">{{ error|string }}</span>
{% endfor %}
</div>
</div>
{% endmacro %}
{%- macro status_label(task, class_="") -%}
{% set states={'opened': _('Open'), 'done': _('Done')} %}
<span class="label {% if task.state == 'opened' %}label-info{% else %}label-success{% endif %} {{ class_ }}">{{ states[task.state] }}</span>
{%- endmacro -%}
{% macro render_pagination(pagination, uri, endpoint) %}
<div class="pagination pagination-right">
<ul>
{% if pagination.has_prev -%}
<li>
<a href="{{ url_for(endpoint, uri=uri, page=pagination.prev_num, **kwargs) }}">
&laquo; {% trans %}Previous{% endtrans %}
</a>
</li>
{% else %}
<li class="disabled">
<a>
&laquo; {% trans %}Previous{% endtrans %}
</a>
</li>
{% endif %}
{%- for page in pagination.iter_pages(left_edge=0, left_current=5, right_current=5, right_edge=0) %}
{% if loop.first and page !=1 %}
<li class="disabled">...</li>
{% endif %}
{% if page %}
<li {% if page == pagination.page %}class="active"{% endif %}>
<a href="{{ url_for(endpoint, uri=uri, page=page, **kwargs) }}">{{ page }}</a>
</li>
{% endif %}
{% if loop.last and page != pagination.pages %}
<li class="disabled">...</li>
{% endif %}
{% endfor %}
{% if pagination.has_next -%}
<li>
<a class="" href="{{ url_for(endpoint, uri=uri, page=pagination.next_num, **kwargs) }}">
{% trans %}Next{% endtrans %} &raquo;
</a>
</li>
{% else %}
<li class="disabled">
<a>{% trans %}Next{% endtrans %} &raquo;</a>
</li>
{% endif %}
</ul>
</div>
{% endmacro %}

View File

@ -0,0 +1,6 @@
Howdy, {{ context['user'].display_name }},
You have been invited to collaborate on Project **{{ context['project'].name }}**
Please login to you existing Account to start collaborating.
Your email for login is {{ context['user'].email }}

View File

@ -0,0 +1,8 @@
Howdy, friend,
You have been invited to collaborate on Project **{{ context['project'].name }}**
Please click on the link below to join the project.
{{ url_for('nereid.user.registration') }}?invitation_code={{ context['invitation'].invitation_code }}
If you are not able to click on the link, copy and paste the same to your browser to continue.

View File

@ -0,0 +1,78 @@
{% extends 'base.jinja' %}
{% block extra_head %}
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
// Client side validation for the form
$("form#new-project-form").validate();
});
</script>
{% endblock %}
{% block container %}
<div class="row-fluid">
<div class="span12">
<h3>{% block title %}{% endblock %}</h3>
</div>
</div>
<div class="row-fluid">
<section id="page-header" class="toolbar">
<ul class="breadcrumb">
{% block breadcrumb %}
<li><a href="{{ url_for('nereid.website.home') }}">{{ _('Dashboard') }}</a></li>
{% endblock %}
</ul>
</section>
<div class="span2" style="margin-left:0px;">
{% block sidebar %}
<div class="well" style="padding: 8px 0; ">
<ul class="nav nav-list">
<li class="nav-header">
{{ _('Projects') }}
</li>
<li class="">
<input type="search" results="5" name="s" placeholder="Search"
class="input-medium">
</li>
{% for project in projects %}
<li>
<a href="{{ url_for('project.work.render_project', project_id=project.id) }}"><i class="icon-tasks"></i> {{ project.name }}</a>
</li>
{% endfor %}
{% if tasks %}
<li class="nav-header">
{{ _('My tasks') }}
</li>
{% for task in tasks %}
<li>
<a href="#"><i class="icon-tasks"></i> {{ task.name }}</a>
</li>
{% endfor %}
{% endif %}
{% if request.nereid_user.is_project_admin(request.nereid_user) %}
{# admin only area #}
<hr/>
<li>
<a data-toggle="modal" href="#new-project">
<i class="icon-plus"></i> {{ _("New Project") }}
</a>
</li>
{% endif %}
<hr/>
<li>
<a href="#"><i class="icon-question-sign"></i> {{ _("Help") }}</a>
</li>
</ul>
</div>
{% endblock %}
</div>
<div class="span10">
{% block main %}
{% endblock %}
</div>
</div>
{% endblock %}

View File

@ -165,10 +165,11 @@
<field name="methods">("POST",)</field>
<field name="url_map" ref="nereid.default_url_map" />
</record>
<record id="project_accept_invite" model="nereid.url_rule">
<field name="rule">/&lt;language&gt;/project-&lt;int:project_id&gt;/invite-accept/&lt;int:user_id&gt;/&lt;activation_code&gt;</field>
<field name="endpoint">project.work.accept_invite</field>
<record id="project_remove_participant" model="nereid.url_rule">
<field name="rule">/&lt;language&gt;/project-&lt;int:project_id&gt;/participant-&lt;int:participant_id&gt;/-remove</field>
<field name="endpoint">project.work.remove_participant</field>
<field name="sequence" eval="60" />
<field name="methods">("POST",)</field>
<field name="url_map" ref="nereid.default_url_map" />
</record>
</data>