diff --git a/README.md b/README.md index 3f9309c91fb5dc38b86149b9f6f7161f35dbfde4..65c5c2fcdb57b0e715618f239d4f3db39a7b6da5 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,23 @@ DB_PORT=5432 You can adjust `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DB_HOST`, and `DB_PORT` based on your local configuration. +Furthermore, this project utilizes Django's mail engine to send notification email +for contributors that're subscribing to new comments on their uploaded materials. +The codebase already have a class email configured as a default sender email, but you can use your own too. +To do so, please configure your `.env` to have these values: +```bash +EMAIL_HOST_USER=<your-email>@gmail.com +EMAIL_HOST_PASSWORD=<your-email-password> +``` + +> Note: +> Be informed that only **Google Mail accounts** that can be used, since the default SMTP server host & port are Google's. +> +> Your email also have to have [`Less secure app access` turned **on**](https://www.google.com/settings/security/lesssecureapps). +> [Reference](https://stackoverflow.com/a/26852782) +> +> If you want to use other SMTP server, please configure through env var `EMAIL_HOST` and `EMAIL_PORT`. + After you clone this repository, let's make sure you installed all requirements, migrate, and collect static. ```bash pip3 install -r requirements.txt diff --git a/app/forms.py b/app/forms.py index 6ec19dfb8f40dc233c1bc53e6dfa3d5c423a267b..aafd9baf3412973d7942c2e17c1a1aecd54a87d2 100644 --- a/app/forms.py +++ b/app/forms.py @@ -37,7 +37,8 @@ class SuntingProfilForm(forms.ModelForm): model = User fields = ["email","name","instansi", "nik", "alamat", "nomor_telpon", "profile_picture", "linkedin", - "facebook", "twitter", "instagram", "biography"] + "facebook", "twitter", "instagram", "biography", + "is_subscribing_to_material_comments"] def __init__(self, *args, **kwargs): super(SuntingProfilForm, self).__init__(*args, **kwargs) diff --git a/app/tests.py b/app/tests.py index 2eaf4475b33125d3a34db7707da0a6db11d6c28f..837165d773862f354cefe9969833d90d5344f46c 100644 --- a/app/tests.py +++ b/app/tests.py @@ -10,7 +10,7 @@ from datetime import datetime from django.conf import settings from django.contrib import messages as dj_messages from django.contrib.auth import get_user_model -from django.core import serializers +from django.core import mail, serializers from django.core.files import File from django.core.exceptions import PermissionDenied, ValidationError from django.core.files.uploadedfile import SimpleUploadedFile @@ -505,6 +505,90 @@ class DetailMateriTest(TestCase): comment_like_counter = LikeComment.objects.filter(comment=comment, session_id=session_id).count() self.assertEqual(comment_like_counter, 0) + def test_comment_sends_email_to_contributor_that_subscribes(self): + contributor_subscribed = get_user_model().objects.create_user( + email="contributor_subscribing@gov.id", + password="passwordtest", + name="Kontributor-subscribed", + is_contributor=True, + is_subscribing_to_material_comments=True + ) + + material = Materi.objects.create(title="Materi-subscribed", author="Tester", + uploader=contributor_subscribed, publisher="Kelas PMPL", + descriptions="Deskripsi Materi subscribed", status="APPROVE", + cover=self.cover, content=self.content) + url = "/materi/" + str(material.id) + "/" + self.client.login(**self.contributor_credential) # comment with other user + + prev_outbox_count = len(mail.outbox) + + comment_content = "Test comment should send email" + self.client.post( + url, {"comment": comment_content}) + + current_outbox_count = len(mail.outbox) + + # Comment notification email sent + self.assertEqual(current_outbox_count, prev_outbox_count + 1) + + def test_comment_doesnt_send_email_to_contributor_that_not_subscribes(self): + contributor_not_subscribed = get_user_model().objects.create_user( + email="contributor_not_subscribing@gov.id", + password="passwordtest", + name="Kontributor-not-subscribed", + is_contributor=True, + is_subscribing_to_material_comments=False + ) + + material = Materi.objects.create(title="Materi-not-subscribed", author="Tester", + uploader=contributor_not_subscribed, publisher="Kelas PMPL", + descriptions="Deskripsi Materi non-subscribed", status="APPROVE", + cover=self.cover, content=self.content) + url = "/materi/" + str(material.id) + "/" + self.client.login(**self.contributor_credential) # comment with other user + + prev_outbox_count = len(mail.outbox) + + comment_content = "Test comment should not send email" + self.client.post( + url, {"comment": comment_content}) + + current_outbox_count = len(mail.outbox) + + # Comment notification email not sent + self.assertEqual(current_outbox_count, prev_outbox_count) + + def test_comment_doesnt_send_email_to_contributor_that_self_commenting(self): + contributor_subscribed_credentials = { + "email": "contributor_subscribing@gov.id", + "password": "passwordtest" + } + contributor_subscribed = get_user_model().objects.create_user( + **contributor_subscribed_credentials, + name="Kontributor-subscribed", + is_contributor=True, + is_subscribing_to_material_comments=True + ) + + material = Materi.objects.create(title="Materi-subscribed", author="Tester", + uploader=contributor_subscribed, publisher="Kelas PMPL", + descriptions="Deskripsi Materi subscribed", status="APPROVE", + cover=self.cover, content=self.content) + url = "/materi/" + str(material.id) + "/" + self.client.login(**contributor_subscribed_credentials) # comment with the same user + + prev_outbox_count = len(mail.outbox) + + comment_content = "Test comment should not send email for self-comments" + self.client.post( + url, {"comment": comment_content}) + + current_outbox_count = len(mail.outbox) + + # Comment notification email not sent + self.assertEqual(current_outbox_count, prev_outbox_count) + def test_detail_materi_contains_comment_count(self): url = self.url self.client.login(**self.contributor_credential) diff --git a/app/views.py b/app/views.py index 94a3095c8567c7cca87e293dbd898aa29b5317fc..48166f33b0a1e21fb6e1c77f0fe7b113c67b6e2a 100644 --- a/app/views.py +++ b/app/views.py @@ -3,12 +3,14 @@ import os from io import BytesIO import django +from decouple import config import pandas as pd from django.conf import settings from django.contrib import messages from django.contrib.auth.views import PasswordChangeForm from django.contrib.auth.views import PasswordChangeView from django.core.exceptions import PermissionDenied, FieldError +from django.core.mail import send_mail from django.core.paginator import Paginator from django.db.models import Q, Avg from django.http import (Http404, HttpResponse, HttpResponseRedirect, @@ -158,6 +160,19 @@ class DetailMateri(TemplateView): comment=comment_text, username=DetailMateriService.get_user_name(request), materi=materi, user=user_obj ) comment.save() + materi_uploader = materi.uploader + if materi_uploader.is_subscribing_to_material_comments and user_obj.email != materi_uploader.email: + email_content = f'User dengan email {user_obj.email} ' + \ + f'menambahkan komentar pada materi Anda dengan judul "{materi.title}".' + \ + f'\nKomentar: "{comment.comment}".\n' + \ + f'Silahkan akses halaman detail materi untuk berinteraksi lebih lanjut.' + send_mail( + subject = 'DIGIPUS: Komentar Baru pada Materi Anda', + message = email_content, + from_email = getattr(settings, 'EMAIL_HOST_USER'), + recipient_list = [materi_uploader.email,], + fail_silently = False, + ) elif (review_text != None): review = Review.objects.create( review=review_text, username=DetailMateriService.get_user_name(request), materi=materi, user=user_obj diff --git a/authentication/migrations/0008_user_is_subscribing_to_material_comments.py b/authentication/migrations/0008_user_is_subscribing_to_material_comments.py new file mode 100644 index 0000000000000000000000000000000000000000..2348d49d3ce1c3cbe3b11628b81dde0ad5be4478 --- /dev/null +++ b/authentication/migrations/0008_user_is_subscribing_to_material_comments.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2020-10-29 12:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0007_auto_20201009_1415'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='is_subscribing_to_material_comments', + field=models.BooleanField(default=True), + ), + ] diff --git a/authentication/models.py b/authentication/models.py index e5161abbbfbd3f5f4b9a676dab59268264df9994..721b69c9f6d364de73e08ad1cbd4583be39e1172 100644 --- a/authentication/models.py +++ b/authentication/models.py @@ -80,6 +80,7 @@ class User(AbstractUser): biography = models.TextField(max_length=200, blank=True, default="") default_profile_picture = models.BooleanField(blank=True, default=False) profile_picture = models.ImageField(default="default-image.jpg") + is_subscribing_to_material_comments = models.BooleanField(blank=False, default=True) objects = CustomUserManager() diff --git a/digipus/settings.py b/digipus/settings.py index 5294eb2e6338e868549e9be6b835260f0e4ce2d7..884c3e67cf7924e4165161d8d50a3ce04361a306 100644 --- a/digipus/settings.py +++ b/digipus/settings.py @@ -178,4 +178,15 @@ REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', ], -} \ No newline at end of file +} + +# Mail +# https://docs.djangoproject.com/en/3.1/topics/email/ + +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = config('EMAIL_HOST', default='smtp.gmail.com') # use Google Mail SMTP as default +EMAIL_PORT = config('EMAIL_PORT', default=587) # use Google Mail SMTP as default +EMAIL_HOST_USER = config('EMAIL_HOST_USER', default="pmplclass2020@gmail.com") +EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default="pmpldigipusemail") +EMAIL_USE_TLS = True +EMAIL_USE_SSL = False \ No newline at end of file