[WEB - 1749] chore: send email when a user is added to the project (#4952)
* chore: send email when a user is added to the project * dev: add email template for project addition
This commit is contained in:
parent
1a37c1542d
commit
d1ec83039c
3 changed files with 1714 additions and 11 deletions
|
|
@ -24,6 +24,8 @@ from plane.db.models import (
|
||||||
TeamMember,
|
TeamMember,
|
||||||
IssueProperty,
|
IssueProperty,
|
||||||
)
|
)
|
||||||
|
from plane.bgtasks.project_add_user_email_task import project_add_user_email
|
||||||
|
from plane.utils.host import base_host
|
||||||
|
|
||||||
|
|
||||||
class ProjectMemberViewSet(BaseViewSet):
|
class ProjectMemberViewSet(BaseViewSet):
|
||||||
|
|
@ -64,33 +66,29 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
)
|
)
|
||||||
|
|
||||||
def create(self, request, slug, project_id):
|
def create(self, request, slug, project_id):
|
||||||
|
# Get the list of members to be added to the project and their roles i.e. the user_id and the role
|
||||||
members = request.data.get("members", [])
|
members = request.data.get("members", [])
|
||||||
|
|
||||||
# get the project
|
# get the project
|
||||||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||||
|
|
||||||
|
# Check if the members array is empty
|
||||||
if not len(members):
|
if not len(members):
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Atleast one member is required"},
|
{"error": "Atleast one member is required"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Initialize the bulk arrays
|
||||||
bulk_project_members = []
|
bulk_project_members = []
|
||||||
bulk_issue_props = []
|
bulk_issue_props = []
|
||||||
|
|
||||||
project_members = (
|
# Create a dictionary of the member_id and their roles
|
||||||
ProjectMember.objects.filter(
|
|
||||||
workspace__slug=slug,
|
|
||||||
member_id__in=[member.get("member_id") for member in members],
|
|
||||||
)
|
|
||||||
.values("member_id", "sort_order")
|
|
||||||
.order_by("sort_order")
|
|
||||||
)
|
|
||||||
|
|
||||||
bulk_project_members = []
|
|
||||||
member_roles = {
|
member_roles = {
|
||||||
member.get("member_id"): member.get("role") for member in members
|
member.get("member_id"): member.get("role") for member in members
|
||||||
}
|
}
|
||||||
# Update roles in the members array based on the member_roles dictionary
|
|
||||||
|
# Update roles in the members array based on the member_roles dictionary and set is_active to True
|
||||||
for project_member in ProjectMember.objects.filter(
|
for project_member in ProjectMember.objects.filter(
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
member_id__in=[member.get("member_id") for member in members],
|
member_id__in=[member.get("member_id") for member in members],
|
||||||
|
|
@ -104,13 +102,27 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
bulk_project_members, ["is_active", "role"], batch_size=100
|
bulk_project_members, ["is_active", "role"], batch_size=100
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Get the list of project members of the requested workspace with the given slug
|
||||||
|
project_members = (
|
||||||
|
ProjectMember.objects.filter(
|
||||||
|
workspace__slug=slug,
|
||||||
|
member_id__in=[member.get("member_id") for member in members],
|
||||||
|
)
|
||||||
|
.values("member_id", "sort_order")
|
||||||
|
.order_by("sort_order")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Loop through requested members
|
||||||
for member in members:
|
for member in members:
|
||||||
|
|
||||||
|
# Get the sort orders of the member
|
||||||
sort_order = [
|
sort_order = [
|
||||||
project_member.get("sort_order")
|
project_member.get("sort_order")
|
||||||
for project_member in project_members
|
for project_member in project_members
|
||||||
if str(project_member.get("member_id"))
|
if str(project_member.get("member_id"))
|
||||||
== str(member.get("member_id"))
|
== str(member.get("member_id"))
|
||||||
]
|
]
|
||||||
|
# Create a new project member
|
||||||
bulk_project_members.append(
|
bulk_project_members.append(
|
||||||
ProjectMember(
|
ProjectMember(
|
||||||
member_id=member.get("member_id"),
|
member_id=member.get("member_id"),
|
||||||
|
|
@ -122,6 +134,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# Create a new issue property
|
||||||
bulk_issue_props.append(
|
bulk_issue_props.append(
|
||||||
IssueProperty(
|
IssueProperty(
|
||||||
user_id=member.get("member_id"),
|
user_id=member.get("member_id"),
|
||||||
|
|
@ -130,6 +143,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Bulk create the project members and issue properties
|
||||||
project_members = ProjectMember.objects.bulk_create(
|
project_members = ProjectMember.objects.bulk_create(
|
||||||
bulk_project_members,
|
bulk_project_members,
|
||||||
batch_size=10,
|
batch_size=10,
|
||||||
|
|
@ -144,7 +158,18 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
member_id__in=[member.get("member_id") for member in members],
|
member_id__in=[member.get("member_id") for member in members],
|
||||||
)
|
)
|
||||||
|
# Send emails to notify the users
|
||||||
|
[
|
||||||
|
project_add_user_email.delay(
|
||||||
|
base_host(request=request, is_app=True),
|
||||||
|
project_member.id,
|
||||||
|
request.user.id,
|
||||||
|
)
|
||||||
|
for project_member in project_members
|
||||||
|
]
|
||||||
|
# Serialize the project members
|
||||||
serializer = ProjectMemberRoleSerializer(project_members, many=True)
|
serializer = ProjectMemberRoleSerializer(project_members, many=True)
|
||||||
|
# Return the serialized data
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
def list(self, request, slug, project_id):
|
def list(self, request, slug, project_id):
|
||||||
|
|
|
||||||
87
apiserver/plane/bgtasks/project_add_user_email_task.py
Normal file
87
apiserver/plane/bgtasks/project_add_user_email_task.py
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
# Python imports
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Third party imports
|
||||||
|
from celery import shared_task
|
||||||
|
|
||||||
|
# Third party imports
|
||||||
|
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.html import strip_tags
|
||||||
|
|
||||||
|
|
||||||
|
# Module imports
|
||||||
|
from plane.license.utils.instance_value import get_email_configuration
|
||||||
|
from plane.utils.exception_logger import log_exception
|
||||||
|
from plane.db.models import ProjectMember
|
||||||
|
from plane.db.models import User
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def project_add_user_email(current_site, project_member_id, invitor_id):
|
||||||
|
try:
|
||||||
|
# Get the invitor
|
||||||
|
invitor = User.objects.get(pk=invitor_id)
|
||||||
|
inviter_first_name = invitor.first_name
|
||||||
|
# Get the project member
|
||||||
|
project_member = ProjectMember.objects.get(pk=project_member_id)
|
||||||
|
# Get the project member details
|
||||||
|
project_name = project_member.project.name
|
||||||
|
workspace_name = project_member.workspace.name
|
||||||
|
member_email = project_member.member.email
|
||||||
|
project_url = f"{current_site}/{project_member.workspace.slug}/projects/{project_member.project_id}/issues"
|
||||||
|
# set the context
|
||||||
|
context = {
|
||||||
|
"project_name": project_name,
|
||||||
|
"workspace_name": workspace_name,
|
||||||
|
"email": member_email,
|
||||||
|
"inviter_first_name": inviter_first_name,
|
||||||
|
"project_url": project_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the email configuration
|
||||||
|
(
|
||||||
|
EMAIL_HOST,
|
||||||
|
EMAIL_HOST_USER,
|
||||||
|
EMAIL_HOST_PASSWORD,
|
||||||
|
EMAIL_PORT,
|
||||||
|
EMAIL_USE_TLS,
|
||||||
|
EMAIL_USE_SSL,
|
||||||
|
EMAIL_FROM,
|
||||||
|
) = get_email_configuration()
|
||||||
|
|
||||||
|
# Set the subject
|
||||||
|
subject = "You have been invited to a Plane project"
|
||||||
|
|
||||||
|
# Render the email template
|
||||||
|
html_content = render_to_string(
|
||||||
|
"emails/notifications/project_addition.html", context
|
||||||
|
)
|
||||||
|
text_content = strip_tags(html_content)
|
||||||
|
# Initialize the connection
|
||||||
|
connection = get_connection(
|
||||||
|
host=EMAIL_HOST,
|
||||||
|
port=int(EMAIL_PORT),
|
||||||
|
username=EMAIL_HOST_USER,
|
||||||
|
password=EMAIL_HOST_PASSWORD,
|
||||||
|
use_tls=EMAIL_USE_TLS == "1",
|
||||||
|
use_ssl=EMAIL_USE_SSL == "1",
|
||||||
|
)
|
||||||
|
# Send the email
|
||||||
|
msg = EmailMultiAlternatives(
|
||||||
|
subject=subject,
|
||||||
|
body=text_content,
|
||||||
|
from_email=EMAIL_FROM,
|
||||||
|
to=[member_email],
|
||||||
|
connection=connection,
|
||||||
|
)
|
||||||
|
# Attach the html content
|
||||||
|
msg.attach_alternative(html_content, "text/html")
|
||||||
|
# Send the email
|
||||||
|
msg.send()
|
||||||
|
# Log the success
|
||||||
|
logging.getLogger("plane").info("Email sent successfully.")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
log_exception(e)
|
||||||
|
return
|
||||||
1591
apiserver/templates/emails/notifications/project_addition.html
Normal file
1591
apiserver/templates/emails/notifications/project_addition.html
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue