Added 'send_email' feature

This commit is contained in:
ayxan 2022-12-08 09:33:57 +04:00
parent 7708b7ccb1
commit 369b33de6f
17 changed files with 161 additions and 86 deletions

View File

@ -10,25 +10,29 @@ click-didyoumean==0.3.0
click-plugins==1.1.1 click-plugins==1.1.1
click-repl==0.2.0 click-repl==0.2.0
Deprecated==1.2.13 Deprecated==1.2.13
Django==4.1 Django==4.0.8
django-autoslug==1.9.8 django-autoslug==1.9.8
django-compat==1.0.15 django-compat==1.0.15
django-crispy-forms==1.14.0 django-crispy-forms==1.14.0
django-environ==0.9.0 django-environ==0.9.0
django-ratelimit==4.0.0
django-smtp-ssl==1.0 django-smtp-ssl==1.0
django-timezone-field==5.0
idna==3.3 idna==3.3
kombu==5.2.4 kombu==5.2.4
packaging==21.3 packaging==21.3
prompt-toolkit==3.0.31 prompt-toolkit==3.0.31
psycopg2==2.9.3 psycopg2==2.9.3
pyparsing==3.0.9 pyparsing==3.0.9
python-crontab==2.6.0
python-dateutil==2.8.2
pytz==2022.2.1 pytz==2022.2.1
redis==4.3.4 redis==4.3.4
requests==2.28.1 requests==2.28.1
six==1.16.0 six==1.16.0
sqlparse==0.4.2 sqlparse==0.4.2
tzdata==2022.5
urllib3==1.26.12 urllib3==1.26.12
vine==5.0.0 vine==5.0.0
wcwidth==0.2.5 wcwidth==0.2.5
wrapt==1.14.1 wrapt==1.14.1
gunicorn

View File

@ -8,7 +8,7 @@ class CustomAdmin(UserAdmin):
fieldsets = ( fieldsets = (
(None, {'fields': ('username', 'email', 'password')}), (None, {'fields': ('username', 'email', 'password')}),
(('Permissions'), { (('Permissions'), {
'fields': ('is_active', 'email_notification_is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions'), 'fields': ('is_active', 'email_is_verified', 'send_email', 'is_staff', 'is_superuser', 'groups', 'user_permissions'),
}), }),
(('Important dates'), {'fields': ('last_login', 'date_joined')}), (('Important dates'), {'fields': ('last_login', 'date_joined')}),
) )
@ -19,7 +19,7 @@ class CustomAdmin(UserAdmin):
}), }),
) )
list_display = ('username', 'email', 'is_staff') list_display = ('username', 'email', 'is_staff')
list_filter = ('is_staff', 'email_notification_is_active', 'is_superuser', 'is_active', 'groups') list_filter = ('is_staff', 'email_is_verified', 'send_email', 'is_superuser', 'is_active', 'groups')
search_fields = ('email',) search_fields = ('email',)
ordering = ('email',) ordering = ('email',)
filter_horizontal = ('groups', 'user_permissions',) filter_horizontal = ('groups', 'user_permissions',)

View File

@ -7,4 +7,4 @@ class ProfileEditingForm(UserChangeForm):
class Meta: class Meta:
model = User model = User
fields = ('email', 'username', 'imdb_api_key') fields = ('email', 'username', 'imdb_api_key', 'send_email')

View File

@ -11,4 +11,5 @@ class RegisterForm(UserCreationForm):
'email', 'email',
'password1', 'password1',
'password2', 'password2',
'imdb_api_key') 'imdb_api_key',
'send_email')

View File

@ -5,9 +5,14 @@ from django.contrib.auth.models import AbstractUser
class User(AbstractUser): class User(AbstractUser):
first_name = None first_name = None
last_name = None last_name = None
email = models.EmailField('email', unique=True, blank=True, null=True) email = models.EmailField('email', null=True, blank=True, unique=True)
imdb_api_key = models.CharField(max_length=15, blank=False, null=False) imdb_api_key = models.CharField(max_length=15, blank=False, null=False)
email_notification_is_active = models.BooleanField(default=False) email_is_verified = models.BooleanField(default=False)
send_email = models.BooleanField(default=False)
def clean(self):
if self.email == '':
self.email = None
class Meta: class Meta:
db_table = 'user' db_table = 'user'

View File

@ -2,19 +2,33 @@
{% load static %} {% load static %}
{% block title %} Profile {% endblock title %} {% block title %} Profile {% endblock title %}
{% block content %} {% block content %}
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<h5>Username: <small>{{profile.username}}</small></h5> <h5>Username: <small>{{profile.username}}</small></h5>
{% if profile.email_notification_is_active %} {% if profile.email %}
<h5>Email: <small style="margin-right: 0.7%;">{{profile.email|default:""}}</small> {% if profile.email_is_verified %}
<img src="{% static 'icons/verified.png' %}" alt="{% static 'icons/verified.png' %}" width="22px"> <h5>Email: <small style="margin-right: 0.7%;">{{profile.email|default:""}}</small>
</h5> <img src="{% static 'icons/verified.png' %}" alt="{% static 'icons/verified.png' %}" width="22px">
{% else %} </h5>
<h5>Email: <small>{{profile.email|default:""}}</small></h5> {% if profile.send_email %}
<h5>Send Email:
<img style="margin-left: 0.5%; margin-bottom: 0.18%;" src="{% static 'icons/true.png' %}" alt="{% static 'icons/true.png' %}" width="23px">
</h5>
{% else %}
<h5>Send Email:
<img style="margin-left: 0.6%; margin-bottom: 0.15%;" src="{% static 'icons/false.png' %}" alt="{% static 'icons/false.png' %}" width="18px">
</h5>
{% endif %}
{% else %}
<h5>Email: <small style="margin-right: 0.7%;">{{profile.email|default:""}}</small>
<a href="/account/send-otp" style="text-decoration: none; font-size: 16px;">
Verify
</a>
</h5>
{% endif %}
{% endif %} {% endif %}
<h5>IMDB API Key: <small>{{profile.imdb_api_key}}</small></h5> <h5>IMDB API Key: <small>{{profile.imdb_api_key}}</small></h5>
</div> </div>

View File

@ -9,7 +9,10 @@
<form enctype="multipart/form-data" method="POST"> <form enctype="multipart/form-data" method="POST">
{% csrf_token %} {% csrf_token %}
{{form|crispy}} {{form.username|as_crispy_field}} <br>
{{form.email|as_crispy_field}} <br>
{{form.imdb_api_key|as_crispy_field}} <br>
{{form.send_email|as_crispy_field}} <br>
<style> <style>
.btn:hover { .btn:hover {
background: #fff; background: #fff;

View File

@ -9,8 +9,13 @@
<form method="POST" style="margin-bottom: 5.5rem;"> <form method="POST" style="margin-bottom: 5.5rem;">
{% csrf_token %} {% csrf_token %}
{{form|crispy}} {{form.username|as_crispy_field}} <br>
<a href="https://imdb-api.com/Identity/Account/Register">Get API Key</a><br> {{form.email|as_crispy_field}} <br>
{{form.password1|as_crispy_field}} <br>
{{form.password2|as_crispy_field}} <br>
{{form.imdb_api_key|as_crispy_field}}
<a href="https://imdb-api.com/Identity/Account/Register">Get API Key</a> <br><br>
{{form.send_email|as_crispy_field}} <br>
<style> <style>
.btn:hover { .btn:hover {
background: #fff; background: #fff;
@ -21,5 +26,4 @@
<p class="mb-5">Already have an account? <a href="{% url 'login' %}">Login</a></p> <p class="mb-5">Already have an account? <a href="{% url 'login' %}">Login</a></p>
</form> </form>
{% endblock content %} {% endblock content %}

View File

@ -8,6 +8,7 @@ urlpatterns = [
template_name = 'login.html' template_name = 'login.html'
), name='login'), ), name='login'),
path('register', views.register_view, name='register'), path('register', views.register_view, name='register'),
path('send-otp', views.send_otp_view, name='send-otp'),
path('activate/<uidb64>/<token>/', views.activate_view, name='activate'), path('activate/<uidb64>/<token>/', views.activate_view, name='activate'),
path('logout', views.logout_view, name='logout'), path('logout', views.logout_view, name='logout'),
path('change-password', views.change_password_view, name='change-password'), path('change-password', views.change_password_view, name='change-password'),

View File

@ -1,6 +1,7 @@
from .register import (register_view, from .register import register_view
activate_view) from .email_activate import activate_view
from .logout import logout_view from .logout import logout_view
from .send_otp import send_otp_view
from .change_password import change_password_view from .change_password import change_password_view
from .profile_editing import profile_editing_view from .profile_editing import profile_editing_view
from .profile_detail import ProfileDetailView from .profile_detail import ProfileDetailView

View File

@ -0,0 +1,21 @@
from django.shortcuts import redirect
from django.contrib import messages
from account.models import User
from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_decode
def activate_view(request, uidb64, token):
try:
uid = urlsafe_base64_decode(uidb64).decode()
user = User._default_manager.get(pk=uid)
except(TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
if user is not None and default_token_generator.check_token(user, token):
user.email_is_verified = True
user.save()
messages.success(request, "Thank you for your email confirmation. You will receive notifications of new episodes. ")
return redirect('homepage')
else:
messages.info(request, "Activation link is invalid!")
return redirect('profile-editing')

View File

@ -4,12 +4,7 @@ from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
from account.forms import ProfileEditingForm from account.forms import ProfileEditingForm
from requests import get from requests import get
from django.contrib.auth.tokens import default_token_generator from .send_otp import send_otp_view
from django.contrib.sites.shortcuts import get_current_site
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.core.mail import EmailMessage
@login_required(login_url='/') @login_required(login_url='/')
@ -36,28 +31,40 @@ def profile_editing_view(request):
messages.info(request, f"IMDB API: {data['errorMessage']}") messages.info(request, f"IMDB API: {data['errorMessage']}")
return redirect('profile-editing') return redirect('profile-editing')
form.save()
if 'email' in form.changed_data: if 'email' in form.changed_data:
user = request.user
user.email_notification_is_active = False
user.save()
to_email = form.cleaned_data.get('email') to_email = form.cleaned_data.get('email')
if to_email is not None: if to_email is not None:
current_site = get_current_site(request) if form.cleaned_data.get('send_email'):
mail_subject = 'Activate your account.' form.add_error('send_email', 'You must verify the email before enabling the send email feature')
message = render_to_string('acc_active_email.html', { return render(request, 'profile_editing.html', context={"form": form})
'user': user, user = request.user
'domain': current_site.domain, user.email_is_verified = False
'uid': urlsafe_base64_encode(force_bytes(user.pk)), form.save()
'token': default_token_generator.make_token(user), user.save()
}) return send_otp_view(request=request)
email = EmailMessage(
mail_subject, message, to=[to_email]
)
email.send()
messages.warning(request, "Please confirm your email address to receive notifications of new episodes.")
return redirect('homepage')
elif 'send_email' in form.changed_data and form.cleaned_data.get('send_email'):
form.add_error('send_email', 'Email field is required to receive email notifications')
return render(request, 'profile_editing.html', context={"form": form})
user = request.user
user.email_is_verified = False
user.send_email = False
form.save()
user.save()
messages.success(request, 'Profile Updated')
return redirect('homepage')
elif form.cleaned_data.get('send_email'):
if form.cleaned_data.get('email') is None:
form.add_error('send_email', 'Email field is required to receive email notifications.')
return render(request, 'profile_editing.html', context={"form": form})
elif not request.user.email_is_verified:
form.add_error('send_email', 'You must first verify the email.')
return render(request, 'profile_editing.html', context={"form": form})
form.save()
messages.success(request, 'Profile Updated') messages.success(request, 'Profile Updated')
return redirect('homepage') return redirect('homepage')

View File

@ -5,17 +5,11 @@ from account.forms import RegisterForm
from django.contrib.auth import (login, from django.contrib.auth import (login,
authenticate) authenticate)
from requests import get from requests import get
from account.models import User
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMessage
from django.shortcuts import render from django.shortcuts import render
from django.template.loader import render_to_string from .send_otp import send_otp_view
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
def register_view(request): def register_view(request): # sourcery skip: extract-method
if request.method == 'POST': if request.method == 'POST':
form = RegisterForm(request.POST) form = RegisterForm(request.POST)
if form.is_valid(): if form.is_valid():
@ -38,26 +32,17 @@ def register_view(request):
return redirect('register') return redirect('register')
to_email = form.cleaned_data.get('email') to_email = form.cleaned_data.get('email')
if to_email is None and form.cleaned_data.get('send_email'):
form.add_error('send_email', 'Email field is required to receive email notifications.')
return render(request, 'register.html', context={"form": form})
form.save() form.save()
username = form.cleaned_data.get('username') username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password1') password = form.cleaned_data.get('password1')
user = authenticate(username=username, password=password) user = authenticate(username=username, password=password)
login(request, user) login(request, user)
if to_email is not None: if to_email is not None:
current_site = get_current_site(request) return send_otp_view(request=request)
mail_subject = 'Activate your account.'
message = render_to_string('acc_active_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': default_token_generator.make_token(user),
})
email = EmailMessage(
mail_subject, message, to=[to_email]
)
email.send()
messages.warning(request, "Please confirm your email address to receive notifications of new episodes.")
return redirect('homepage')
messages.success(request, 'Registration Successful') messages.success(request, 'Registration Successful')
return redirect('homepage') return redirect('homepage')
@ -65,20 +50,4 @@ def register_view(request):
else: else:
form = RegisterForm() form = RegisterForm()
return render(request, 'register.html', context={"form": form}) return render(request, 'register.html', context={"form": form})
def activate_view(request, uidb64, token):
try:
uid = urlsafe_base64_decode(uidb64).decode()
user = User._default_manager.get(pk=uid)
except(TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
if user is not None and default_token_generator.check_token(user, token):
user.email_notification_is_active = True
user.save()
messages.success(request, "Thank you for your email confirmation. You will receive notifications of new episodes. ")
return redirect('homepage')
else:
messages.info(request, "Activation link is invalid!")
return redirect('register')

View File

@ -0,0 +1,45 @@
from django.contrib.auth.decorators import login_required
from django.contrib.sites.shortcuts import get_current_site
from django.template.loader import render_to_string
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes
from django.contrib.auth.tokens import default_token_generator
from django.core.mail import EmailMessage
from django.contrib import messages
from django.shortcuts import redirect
from django.views.decorators.http import require_http_methods
from django_ratelimit.decorators import ratelimit
@ratelimit(key='user', rate='1/125s', block=False)
@login_required(login_url='/')
@require_http_methods(['GET', 'POST'])
def send_otp_view(request):
if getattr(request, 'limited', False):
messages.warning(request, 'You can only get 1 verification code every 2 minutes.')
return redirect('homepage')
user = request.user
if user.email is None:
messages.warning(request, 'you must add your email first')
return redirect('profile-editing')
if user.email_is_verified:
messages.warning(request, 'your email account is already verified')
return redirect('homepage')
current_site = get_current_site(request)
mail_subject = 'Activate your account.'
message = render_to_string('acc_active_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': default_token_generator.make_token(user),
})
email = EmailMessage(
mail_subject, message, to=[user.email]
)
email.send()
messages.warning(request, f"Verification message sent to {user.email}.")
return redirect('homepage')

View File

@ -48,7 +48,7 @@ def episode_counter(imdb_api_key, s, data):
@shared_task(name='send_emails') @shared_task(name='send_emails')
def send_feedback_email_task(): def send_feedback_email_task():
users = User.objects.filter(email_notification_is_active=True) users = User.objects.filter(email_is_verified=True)
for user in users: for user in users:
series = user.series.filter(show=True).order_by('-id') series = user.series.filter(show=True).order_by('-id')
series_new_episodes = [] series_new_episodes = []

BIN
src/static/icons/false.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/static/icons/true.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB