Fakultas Ilmu Komputer UI

Commit ea1bd139 authored by oki priyadi's avatar oki priyadi
Browse files

add subscribe function

parent f7557f93
Pipeline #58125 failed with stages
in 33 minutes and 47 seconds
# Generated by Django 3.1 on 2020-10-09 07:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0017_auto_20201005_2145'),
]
operations = [
migrations.CreateModel(
name='SubscribeModel',
fields=[
('sys_id', models.AutoField(primary_key=True, serialize=False)),
('email', models.EmailField(blank=True, max_length=200, unique=True)),
('status', models.CharField(blank=True, max_length=64)),
('created_date', models.DateTimeField(blank=True)),
('updated_date', models.DateTimeField(blank=True)),
],
options={
'db_table': 'app_subscribe',
},
),
]
...@@ -145,4 +145,18 @@ class RatingContributor(models.Model): ...@@ -145,4 +145,18 @@ class RatingContributor(models.Model):
if 1 <= self.score <= 5: if 1 <= self.score <= 5:
super().save(force_insert, force_update, using, update_fields) super().save(force_insert, force_update, using, update_fields)
else: else:
raise ValidationError("Rating score must be integer between 1-5") raise ValidationError("Rating score must be integer between 1-5")
\ No newline at end of file
class SubscribeModel(models.Model):
sys_id = models.AutoField(primary_key=True, null=False, blank=True)
email = models.EmailField(null=False, blank=True, max_length=200, unique=True)
status = models.CharField(max_length=64, null=False, blank=True)
created_date = models.DateTimeField(null=False, blank=True)
updated_date = models.DateTimeField(null=False, blank=True)
class Meta:
app_label = "app"
db_table = "app_subscribe"
def __str__(self):
return self.email
\ No newline at end of file
<div style="padding: 20px; background: #fafafa;font-size:15px;">
Hi<br>
Thanks for subscribing to {{ project_name }} newsletter.<br>
We will be sending you latest published articles on <a href="{{ site_url }}">{{ site_url }}</a>. Mail frequency won't be more than twice a month.<br>
We hate spamming as much as you do.<br>
<br>
To confirm your subscription, please click on the link given below. If clicking doesn't work, copy paste the URL in browser.<br>
If you think this is a mistake, just ignore this email and we won't bother you again.
<br>
<br>
<a href="{{ confirmation_url }}">{{ confirmation_url }}</a>
<br>
<br>
<p>
Note:<br>
This is notification only email. Please do not reply on this email.<br>
You can <a href="{{ contact_us_url }}">contact us here</a>.
</p>
</div>
\ No newline at end of file
...@@ -54,6 +54,7 @@ ...@@ -54,6 +54,7 @@
<div class="container"> <div class="container">
<div class="row header"> <div class="row header">
<div class="col"> <div class="col">
<a href= "/subscribeform"><img src="{% static 'images/sub-btn.png' %}" style="height:100px"></a>
<h2 class="pageTitle">Temukan Materi Yang Kamu Mau!</h2> <h2 class="pageTitle">Temukan Materi Yang Kamu Mau!</h2>
<p class="description">Cari dengan judul buku, penerbit, atau penulis</p> <p class="description">Cari dengan judul buku, penerbit, atau penulis</p>
<form class="searchBar" action=''> <form class="searchBar" action=''>
......
<div style="border: 1px solid darkgreen; border-radius: 2px; padding:10px; text-align: center;">
<div><strong>SUBSCRIBE</strong></div>
<div style="margin-bottom: 10px;">Please subscribe to get the latest articles in your mailbox.</div>
<div>
<form action="{% url 'subscribe' %}" method="post" class="form-horizontal">
{% csrf_token %}
<input type="email" name="email" class="form-control" placeholder="Your Email ID Please" required>
<br>
<input type="submit" value="Subscribe" class="btn btn-primary btn-sm">
</form>
</div>
</div>
\ No newline at end of file
...@@ -25,7 +25,8 @@ from .views import (DaftarKatalog, DashboardKontributorView, DetailMateri, ...@@ -25,7 +25,8 @@ from .views import (DaftarKatalog, DashboardKontributorView, DetailMateri,
from app.forms import SuntingProfilForm from app.forms import SuntingProfilForm
from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata
import pandas as pd import pandas as pd
from django.core import mail
from app.utils.email_utility import send_gmail
class DaftarKatalogTest(TestCase): class DaftarKatalogTest(TestCase):
def test_daftar_katalog_url_exist(self): def test_daftar_katalog_url_exist(self):
...@@ -1658,3 +1659,13 @@ class RatingContributorTest(TransactionTestCase): ...@@ -1658,3 +1659,13 @@ class RatingContributorTest(TransactionTestCase):
self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count()) self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count())
self.client.post(url, data={"user": self.contributor.id, "score": 0}) self.client.post(url, data={"user": self.contributor.id, "score": 0})
self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count()) self.assertEqual(0, RatingContributor.objects.filter(user=self.contributor.id).count())
class EmailTest(TestCase):
def test_send_email(self):
send_gmail('bass_of_ncreep@yahoo.com', 'Here is the message')
mail.send_mail('Subject here', 'Here is the message.',
'pmplmailer@gmail.com', ['bass_of_ncreep@yahoo.com'],
fail_silently=False)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, 'Subject here')
\ No newline at end of file
...@@ -32,4 +32,7 @@ urlpatterns = [ ...@@ -32,4 +32,7 @@ urlpatterns = [
path("profil/<str:email>/", KatalogPerKontributorView.as_view(), path("profil/<str:email>/", KatalogPerKontributorView.as_view(),
name="katalog-per-kontributor"), name="katalog-per-kontributor"),
path("materi/rate/", views.add_rating_materi, name="rate-materi"), path("materi/rate/", views.add_rating_materi, name="rate-materi"),
path(r'subscribe/', views.subscribe, name='subscribe'),
path(r'subscribeform/', views.subscribeform, name='subscribeform'),
path(r'subscription_confirmation/', views.subscription_confirmation, name='subscription_confirmation'),
] ]
import logging, traceback
from django.urls import reverse
import requests
from django.template.loader import get_template
from django.utils.html import strip_tags
from django.conf import settings
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import email
import email.mime.application
from django.utils.html import strip_tags
def send_email(data):
try:
url = "https://api.mailgun.net/v3/<domain-name>/messages"
status = requests.post(
url,
auth=("api", settings.MAILGUN_API_KEY),
data={"from": "OKI <https://app.mailgun.com/app/sending/domains/sandbox34675edbad2a4ab08e896c3bd80524ec.mailgun.org>",
"to": [data["email"]],
"subject": data["subject"],
"text": data["plain_text"],
"html": data["html_text"]}
)
logging.getLogger("info").info("Mail sent to " + data["email"] + ". status: " + str(status))
return status
except Exception as e:
logging.getLogger("error").error(traceback.format_exc())
return False
def send_subscription_email(email, subscription_confirmation_url):
data = dict()
data["confirmation_url"] = subscription_confirmation_url
data["subject"] = "Please Confirm The Subscription"
data["email"] = email
template = get_template("app/emails/subscription.html")
data["html_text"] = template.render(data)
data["plain_text"] = strip_tags(data["html_text"])
return send_email(data)
def send_gmail(email, subscription_confirmation_url):
# create message
msg = MIMEMultipart('alternative')
msg['Subject'] = "Please verify your mail"
msg['From'] = "pmplmailer@gmail.com"
msg['To'] = email
# create body
html_text = '<div style="border:1px solid black">Thanks for subscribing to Us. Please click this link to activate your subscription : ' + subscription_confirmation_url + '</div>'
plain_text = strip_tags(html_text)
# Create the body of the message (a plain-text and an HTML version).
# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(plain_text, 'plain')
part2 = MIMEText(html_text, 'html')
# Attach image if any
# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(part1)
msg.attach(part2)
# Send the message via local SMTP server.
host = "smtp.gmail.com"
port = 587
mail = smtplib.SMTP(host, port, timeout=60)
mail.ehlo()
mail.starttls()
# Add recepiens, cc or bcc if any
recepient = [msg["To"]]
# username and password of gmail id which will be used to send email
username = "pmplmailer@gmail.com"
password = "asdf1234$"
# login using credentials
mail.login(username, password)
# send email
mail.sendmail(msg["From"], recepient, msg.as_string())
mail.quit()
return "\nSent\n"
\ No newline at end of file
from cryptography.fernet import Fernet
import base64
import logging
import traceback
from django.conf import settings
#this is your "password/ENCRYPT_KEY". keep it in settings.py file
#key = Fernet.generate_key()
def encrypt(txt):
try:
# convert integer etc to string first
txt = str(txt)
# get the key from settings
cipher_suite = Fernet(settings.ENCRYPT_KEY) # key should be byte
# #input should be byte, so convert the text to byte
encrypted_text = cipher_suite.encrypt(txt.encode('ascii'))
# encode to urlsafe base64 format
encrypted_text = base64.urlsafe_b64encode(encrypted_text).decode("ascii")
return encrypted_text
except Exception as e:
# log the error if any
logging.getLogger("error_logger").error(traceback.format_exc())
return None
def decrypt(txt):
try:
# base64 decode
txt = base64.urlsafe_b64decode(txt)
cipher_suite = Fernet(settings.ENCRYPT_KEY)
decoded_text = cipher_suite.decrypt(txt).decode("ascii")
return decoded_text
except Exception as e:
# log the error
logging.getLogger("error_logger").error(traceback.format_exc())
return None
\ No newline at end of file
import mimetypes import mimetypes
import os import os
import re
import time
import logging
import traceback
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
...@@ -9,7 +12,7 @@ from django.core.paginator import Paginator ...@@ -9,7 +12,7 @@ from django.core.paginator import Paginator
from django.db.models import Q, Count from django.db.models import Q, Count
from django.http import (Http404, HttpResponse, HttpResponseRedirect, from django.http import (Http404, HttpResponse, HttpResponseRedirect,
JsonResponse) JsonResponse)
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect, render
from django.template import loader from django.template import loader
from django.views.generic import TemplateView from django.views.generic import TemplateView
...@@ -23,6 +26,11 @@ import django ...@@ -23,6 +26,11 @@ import django
import pandas as pd import pandas as pd
from io import BytesIO from io import BytesIO
from django.contrib import messages from django.contrib import messages
from django.urls import reverse
from app.utils.encryption_util import encrypt, decrypt
from app.utils.email_utility import send_subscription_email, send_gmail
from app.models import SubscribeModel
from datetime import datetime
class DaftarKatalog(TemplateView): class DaftarKatalog(TemplateView):
...@@ -235,7 +243,99 @@ def add_rating_materi(request): ...@@ -235,7 +243,99 @@ def add_rating_materi(request):
return JsonResponse({"success": True, "msg": "Rating successfully created", "rating_score": rating_score}, return JsonResponse({"success": True, "msg": "Rating successfully created", "rating_score": rating_score},
status=201) status=201)
return JsonResponse({"success": False, "msg": "Forbidden"}, status=403) return JsonResponse({"success": False, "msg": "Forbidden"}, status=403)
def subscribeform(request):
return render(request, 'subscribe.html', )
def subscribe(request):
post_data = request.POST.copy()
email = post_data.get("email", None)
error_msg = validate_email(email)
if error_msg:
messages.error(request, error_msg)
return HttpResponseRedirect(reverse('subscribe'))
token = encrypt(email + "_" + str(time.time()))
subscription_confirmation_url = request.build_absolute_uri( reverse('subscription_confirmation')) + "?token=" + token
status = send_gmail(email, subscription_confirmation_url)
save_status = save_email(email)
if save_status:
token = encrypt(email + "_" + str(time.time()))
subscription_confirmation_url = request.build_absolute_uri(reverse('subscription_confirmation')) + "?token=" + token
status = send_gmail(email, subscription_confirmation_url)
if not status:
SubscribeModel.objects.get(email=email).delete()
logging.getLogger("info").info(
"Deleted the record from Subscribe table for " + email + " as email sending failed. status: " + str(
status))
else:
msg = "Mail sent to email Id '" + email + "'. Please confirm your subscription by clicking on " \
"confirmation link provided in email. " \
"Please check your spam folder as well."
messages.success(request, msg)
return HttpResponse(msg)
else:
msg = "Some error occurred. Please try in some time. Meanwhile we are looking into it."
messages.error(request, msg)
return HttpResponse(msg)
return HttpResponseRedirect(reverse('subscribeform'))
def subscription_confirmation(request):
if "POST" == request.method:
raise Http404
token = request.GET.get("token", None)
if not token:
logging.getLogger("warning").warning("Invalid Link ")
messages.error(request, "Invalid Link")
return HttpResponseRedirect(reverse('subscribeform'))
token = decrypt(token)
if token:
token = token.split("_")
email = token[0]
print(email)
initiate_time = token[1] # time when email was sent , in epoch format. can be used for later calculations
try:
subscribe_model_instance = SubscribeModel.objects.get(email=email)
subscribe_model_instance.status = "CONFIRMED"
subscribe_model_instance.updated_date = datetime.today()
subscribe_model_instance.save()
messages.success(request, "Subscription Confirmed. Thank you.")
except Exception as e:
logging.getLogger("warning").warning(traceback.format_exc())
messages.error(request, "Invalid Link")
else:
logging.getLogger("warning").warning("Invalid token ")
messages.error(request, "Invalid Link")
return HttpResponse('<h1>Thank you for subscribing us</h1>')
def validate_email(email):
if email is None:
return "Email is required."
elif not re.match(r"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$", email):
return "Invalid Email Address."
else:
return None
def save_email(email):
try:
subscribe_model_instance = SubscribeModel.objects.get(email=email)
except Exception as e:
subscribe_model_instance = SubscribeModel()
# does not matter if already subscribed or not...resend the email
subscribe_model_instance.email = email
subscribe_model_instance.status = "SUBSCRIBED"
subscribe_model_instance.created_date = datetime.today()
subscribe_model_instance.updated_date = datetime.today()
subscribe_model_instance.save()
return True
def download_materi(request, pk): def download_materi(request, pk):
materi = get_object_or_404(Materi, pk=pk) materi = get_object_or_404(Materi, pk=pk)
...@@ -736,6 +836,8 @@ class RevisiMateriView(TemplateView): ...@@ -736,6 +836,8 @@ class RevisiMateriView(TemplateView):
context["form_revisi"] = form context["form_revisi"] = form
return self.render_to_response(context) return self.render_to_response(context)
def pages(request): def pages(request):
context = {} context = {}
# All resource paths end in .html. # All resource paths end in .html.
......
...@@ -163,3 +163,5 @@ MEDIA_URL = "/media/" ...@@ -163,3 +163,5 @@ MEDIA_URL = "/media/"
LOGOUT_REDIRECT_URL = "/" LOGOUT_REDIRECT_URL = "/"
CRISPY_TEMPLATE_PACK = 'bootstrap4' CRISPY_TEMPLATE_PACK = 'bootstrap4'
ENCRYPT_KEY = b'YVLSFH4WgxkLgCOmCBchVmahU_VIF2xPFdD7GUTsvgY='
MAILGUN_API_KEY = 'a93a52270d7871930c8813bf0b96f21b-0d2e38f7-bb01a5b0'
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment