Added email functionality and restructured the template layout

This commit is contained in:
Shalabh Aggarwal 2012-09-05 13:35:55 +05:30
parent 26cec2a9e8
commit daabcdbbb7
11 changed files with 254 additions and 3 deletions

View File

@ -8,18 +8,26 @@
:license: GPLv3, see LICENSE for more details.
"""
import tempfile
import random
import string
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)
redirect, flash, jsonify, current_app)
from flask import send_file
from nereid.ctx import has_request_context
from nereid.signals import registration
from nereid.contrib.pagination import Pagination
from trytond.model import ModelView, ModelSQL, fields
from trytond.pool import Pool
from trytond.pyson import And, Not, Or, Bool, Equal, Eval
from trytond.config import CONFIG
from trytond.tools import get_smtp_server
class WebSite(ModelSQL, ModelView):
@ -28,7 +36,6 @@ class WebSite(ModelSQL, ModelView):
"""
_name = "nereid.website"
@login_required
def home(self):
"""
@ -93,6 +100,47 @@ class ProjectUsers(ModelSQL):
ProjectUsers()
class ProjectInvitation(ModelSQL, ModelView):
"Project Invitation store"
_name = 'project.work.invitation'
_description = __doc__
email = fields.Char('Email', required=True)
invitation_code = fields.Char('Invitation Code', required=True)
nereid_user = fields.Many2One('nereid.user', 'Nereid User')
project = fields.Many2One('project.work', 'Project')
def create(self, vals):
existing_invite = self.search([
('invitation_code', '=', vals['invitation_code'])
])
if existing_invite:
vals['invitation_code'] = ''.join(
random.sample(string.letters + string.digits, 20)
)
return super(ProjectInvitation, self).create(vals)
ProjectInvitation()
class ProjectWorkinvitation(ModelSQL):
"Project Work Invitation"
_name = 'project.work-project.invitation'
_description = __doc__
invitation = fields.Many2One(
'project.work.invitation', 'Invitation',
ondelete='CASCADE', select=1, required=True
)
project = fields.Many2One(
'project.work.invitation', 'Project',
ondelete='CASCADE', select=1, required=True
)
ProjectWorkinvitation()
class Project(ModelSQL, ModelView):
"""
Tryton itself is very flexible in allowing multiple layers of Projects and
@ -253,7 +301,7 @@ class Project(ModelSQL, ModelView):
def get_project(self, project_id):
"""
Common base for fetching the project while validating if the user
Common base for fetching the project while validating if the user
can use it.
:param project_id: ID of the project
@ -426,6 +474,148 @@ 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
:param project_id: ID of Project
"""
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':
return abort(404)
project = self.get_project(project_id)
email = request.form['email']
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]' \
% 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
)
flash_message = "%s has been invited to the project" \
% existing_user.display_name
else:
invitation_code = ''.join(
random.sample(string.letters + string.digits, 20)
)
new_invite_id = project_invitation_obj.create({
'email': email,
'project': project.id,
'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
)
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)
if request.is_xhr:
return jsonify({
'success': True,
})
flash(flash_message)
return redirect(request.referrer)
@login_required
def render_task_list(self, project_id):
"""
@ -1058,3 +1248,47 @@ class ProjectHistory(ModelSQL, ModelView):
ProjectHistory()
@registration.connect
def invitation_new_user_handler(nereid_user_id):
"""When the invite is sent to a new user, he is sent an invitation key
with the url which guides the user to registration page
This method checks if the invitation key is present in the url
If yes, search for the invitation with this key, attache the user
to the invitation and project to the user
If not, perform normal operation
"""
try:
invitation_obj = Pool().get('project.work.invitation')
project_obj = Pool().get('project.work')
except KeyError:
# 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"
)
return
invitation_code = request.args.get('invitation_code', None)
if not invitation_code:
return
ids = invitation_obj.search({
'invitation_code': invitation_code,
})
if not ids:
return
invitation = invitation_obj.browse(ids[0])
invitation_obj.write(invitation.id, {
'nereid_user': nereid_user_id
})
project_obj.write(
invitation.project.id, {
'participants': [('add', [nereid_user_id])]
}
)

View File

@ -3,6 +3,10 @@
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

@ -152,5 +152,18 @@
<field name="methods">("POST",)</field>
<field name="url_map" ref="nereid.default_url_map" />
</record>
<record id="project_invite" model="nereid.url_rule">
<field name="rule">/&lt;language&gt;/project-&lt;int:project_id&gt;/-invite</field>
<field name="endpoint">project.work.invite</field>
<field name="sequence" eval="60" />
<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>
<field name="sequence" eval="60" />
<field name="url_map" ref="nereid.default_url_map" />
</record>
</data>
</tryton>