mirror of
				https://github.com/aykhans/series-robot-web.git
				synced 2025-10-31 14:39:59 +00:00 
			
		
		
		
	Added 'send_email' feature
This commit is contained in:
		| @@ -10,25 +10,29 @@ click-didyoumean==0.3.0 | ||||
| click-plugins==1.1.1 | ||||
| click-repl==0.2.0 | ||||
| Deprecated==1.2.13 | ||||
| Django==4.1 | ||||
| Django==4.0.8 | ||||
| django-autoslug==1.9.8 | ||||
| django-compat==1.0.15 | ||||
| django-crispy-forms==1.14.0 | ||||
| django-environ==0.9.0 | ||||
| django-ratelimit==4.0.0 | ||||
| django-smtp-ssl==1.0 | ||||
| django-timezone-field==5.0 | ||||
| idna==3.3 | ||||
| kombu==5.2.4 | ||||
| packaging==21.3 | ||||
| prompt-toolkit==3.0.31 | ||||
| psycopg2==2.9.3 | ||||
| pyparsing==3.0.9 | ||||
| python-crontab==2.6.0 | ||||
| python-dateutil==2.8.2 | ||||
| pytz==2022.2.1 | ||||
| redis==4.3.4 | ||||
| requests==2.28.1 | ||||
| six==1.16.0 | ||||
| sqlparse==0.4.2 | ||||
| tzdata==2022.5 | ||||
| urllib3==1.26.12 | ||||
| vine==5.0.0 | ||||
| wcwidth==0.2.5 | ||||
| wrapt==1.14.1 | ||||
| gunicorn | ||||
| @@ -8,7 +8,7 @@ class CustomAdmin(UserAdmin): | ||||
|     fieldsets = ( | ||||
|         (None, {'fields': ('username', 'email', 'password')}), | ||||
|         (('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')}), | ||||
|     ) | ||||
| @@ -19,7 +19,7 @@ class CustomAdmin(UserAdmin): | ||||
|         }), | ||||
|     ) | ||||
|     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',) | ||||
|     ordering = ('email',) | ||||
|     filter_horizontal = ('groups', 'user_permissions',) | ||||
| @@ -7,4 +7,4 @@ class ProfileEditingForm(UserChangeForm): | ||||
|  | ||||
|     class Meta: | ||||
|         model = User | ||||
|         fields = ('email', 'username', 'imdb_api_key') | ||||
|         fields = ('email', 'username', 'imdb_api_key', 'send_email') | ||||
| @@ -11,4 +11,5 @@ class RegisterForm(UserCreationForm): | ||||
|                 'email', | ||||
|                 'password1', | ||||
|                 'password2', | ||||
|                 'imdb_api_key') | ||||
|                 'imdb_api_key', | ||||
|                 'send_email') | ||||
|   | ||||
| @@ -5,9 +5,14 @@ from django.contrib.auth.models import AbstractUser | ||||
| class User(AbstractUser): | ||||
|     first_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) | ||||
|     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: | ||||
|         db_table = 'user' | ||||
|   | ||||
| @@ -2,19 +2,33 @@ | ||||
| {% load static %} | ||||
|  | ||||
| {% block title %} Profile {% endblock title %} | ||||
|      | ||||
|  | ||||
| {% block content %} | ||||
|  | ||||
| <div class="card mb-3"> | ||||
|     <div class="card-body"> | ||||
|         <h5>Username: <small>{{profile.username}}</small></h5> | ||||
|         {% if profile.email_notification_is_active %} | ||||
|             <h5>Email: <small style="margin-right: 0.7%;">{{profile.email|default:""}}</small> | ||||
|                 <img src="{% static 'icons/verified.png' %}" alt="{% static 'icons/verified.png' %}" width="22px"> | ||||
|             </h5> | ||||
|         {% else %} | ||||
|             <h5>Email: <small>{{profile.email|default:""}}</small></h5> | ||||
|         {% if profile.email %} | ||||
|             {% if profile.email_is_verified %} | ||||
|                 <h5>Email: <small style="margin-right: 0.7%;">{{profile.email|default:""}}</small> | ||||
|                     <img src="{% static 'icons/verified.png' %}" alt="{% static 'icons/verified.png' %}" width="22px"> | ||||
|                 </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 %} | ||||
|         <h5>IMDB API Key: <small>{{profile.imdb_api_key}}</small></h5>  | ||||
|     </div> | ||||
|   | ||||
| @@ -9,7 +9,10 @@ | ||||
|  | ||||
| <form enctype="multipart/form-data" method="POST"> | ||||
|     {% 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> | ||||
|         .btn:hover { | ||||
|             background: #fff; | ||||
|   | ||||
| @@ -9,8 +9,13 @@ | ||||
|  | ||||
| <form method="POST" style="margin-bottom: 5.5rem;"> | ||||
|     {% csrf_token %} | ||||
|     {{form|crispy}} | ||||
|     <a href="https://imdb-api.com/Identity/Account/Register">Get API Key</a><br> | ||||
|     {{form.username|as_crispy_field}} <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> | ||||
|         .btn:hover { | ||||
|             background: #fff; | ||||
| @@ -21,5 +26,4 @@ | ||||
|     <p class="mb-5">Already have an account? <a href="{% url 'login' %}">Login</a></p> | ||||
| </form> | ||||
|  | ||||
| {% endblock content %} | ||||
|      | ||||
| {% endblock content %} | ||||
| @@ -8,6 +8,7 @@ urlpatterns = [ | ||||
|         template_name = 'login.html' | ||||
|         ), name='login'), | ||||
|     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('logout', views.logout_view, name='logout'), | ||||
|     path('change-password', views.change_password_view, name='change-password'), | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from .register import (register_view, | ||||
|                     activate_view) | ||||
| from .register import register_view | ||||
| from .email_activate import activate_view | ||||
| from .logout import logout_view | ||||
| from .send_otp import send_otp_view | ||||
| from .change_password import change_password_view | ||||
| from .profile_editing import profile_editing_view | ||||
| from .profile_detail import ProfileDetailView | ||||
							
								
								
									
										21
									
								
								src/account/views/email_activate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/account/views/email_activate.py
									
									
									
									
									
										Normal 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') | ||||
| @@ -4,12 +4,7 @@ from django.contrib.auth.decorators import login_required | ||||
| from django.contrib import messages | ||||
| from account.forms import ProfileEditingForm | ||||
| from requests import get | ||||
| from django.contrib.auth.tokens import default_token_generator | ||||
| 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 | ||||
| from .send_otp import send_otp_view | ||||
|  | ||||
|  | ||||
| @login_required(login_url='/') | ||||
| @@ -36,28 +31,40 @@ def profile_editing_view(request): | ||||
|                     messages.info(request, f"IMDB API: {data['errorMessage']}") | ||||
|                     return redirect('profile-editing') | ||||
|  | ||||
|             form.save() | ||||
|             if 'email' in form.changed_data: | ||||
|                 user = request.user | ||||
|                 user.email_notification_is_active = False | ||||
|                 user.save() | ||||
|                 to_email = form.cleaned_data.get('email') | ||||
|                 if to_email is not None: | ||||
|                     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=[to_email] | ||||
|                     ) | ||||
|                     email.send() | ||||
|                     messages.warning(request, "Please confirm your email address to receive notifications of new episodes.") | ||||
|                     return redirect('homepage') | ||||
|                     if form.cleaned_data.get('send_email'): | ||||
|                         form.add_error('send_email', 'You must verify the email before enabling the send email feature') | ||||
|                         return render(request, 'profile_editing.html', context={"form": form}) | ||||
|                     user = request.user | ||||
|                     user.email_is_verified = False | ||||
|                     form.save() | ||||
|                     user.save() | ||||
|                     return send_otp_view(request=request) | ||||
|  | ||||
|                 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') | ||||
|             return redirect('homepage') | ||||
|  | ||||
|   | ||||
| @@ -5,17 +5,11 @@ from account.forms import RegisterForm | ||||
| from django.contrib.auth import (login, | ||||
|                                 authenticate) | ||||
| 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.template.loader import render_to_string | ||||
| from django.utils.encoding import force_bytes | ||||
| from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode | ||||
| from .send_otp import send_otp_view | ||||
|  | ||||
|  | ||||
| def register_view(request): | ||||
| def register_view(request):  # sourcery skip: extract-method | ||||
|     if request.method == 'POST': | ||||
|         form = RegisterForm(request.POST) | ||||
|         if form.is_valid(): | ||||
| @@ -38,26 +32,17 @@ def register_view(request): | ||||
|                 return redirect('register') | ||||
|  | ||||
|             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() | ||||
|             username = form.cleaned_data.get('username') | ||||
|             password = form.cleaned_data.get('password1') | ||||
|             user = authenticate(username=username, password=password) | ||||
|             login(request, user) | ||||
|             if to_email is not None: | ||||
|                 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=[to_email] | ||||
|                 ) | ||||
|                 email.send() | ||||
|                 messages.warning(request, "Please confirm your email address to receive notifications of new episodes.") | ||||
|                 return redirect('homepage') | ||||
|                 return send_otp_view(request=request) | ||||
|  | ||||
|             messages.success(request, 'Registration Successful') | ||||
|             return redirect('homepage') | ||||
| @@ -65,20 +50,4 @@ def register_view(request): | ||||
|     else:  | ||||
|         form = RegisterForm() | ||||
|  | ||||
|     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') | ||||
|     return render(request, 'register.html', context={"form": form}) | ||||
							
								
								
									
										45
									
								
								src/account/views/send_otp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/account/views/send_otp.py
									
									
									
									
									
										Normal 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') | ||||
| @@ -48,7 +48,7 @@ def episode_counter(imdb_api_key, s, data): | ||||
|  | ||||
| @shared_task(name='send_emails') | ||||
| 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: | ||||
|         series = user.series.filter(show=True).order_by('-id') | ||||
|         series_new_episodes = [] | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								src/static/icons/false.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											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
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/static/icons/true.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.5 KiB | 
		Reference in New Issue
	
	Block a user
	 ayxan
					ayxan