diff --git a/README.md b/README.md index e8d8487..d21a16b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ https://ayxan.pythonanywhere.com **pip install -r requirements.txt
cd src/
** -add SECRET_KEY to .env.example file
+add SECRET_KEY, EMAIL_HOST_PASSWORD to .env.example file
rename .env.example to .env
**python3 manage.py makemigrations account --settings=config.settings.development
diff --git a/src/.env.example b/src/.env.example index bce366d..154bb73 100644 --- a/src/.env.example +++ b/src/.env.example @@ -1,4 +1,5 @@ SECRET_KEY= DB_NAME= DB_USER= -DB_PASSWORD= \ No newline at end of file +DB_PASSWORD= +EMAIL_HOST_PASSWORD= \ No newline at end of file diff --git a/src/account/admin.py b/src/account/admin.py index 7eb755d..ed2c911 100644 --- a/src/account/admin.py +++ b/src/account/admin.py @@ -5,9 +5,21 @@ from .models import User @admin.register(User) class CustomAdmin(UserAdmin): - list_display = ('username', 'email') - fieldsets = UserAdmin.fieldsets + ( - ('IMDB API Key Field', { - 'fields': ['imdb_api_key'] + fieldsets = ( + (None, {'fields': ('username', 'email', 'password')}), + (('Permissions'), { + 'fields': ('is_active', 'email_notification_is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions'), + }), + (('Important dates'), {'fields': ('last_login', 'date_joined')}), + ) + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email', 'password1', 'password2'), }), ) + list_display = ('username', 'email', 'is_staff') + list_filter = ('is_staff', 'email_notification_is_active', 'is_superuser', 'is_active', 'groups') + search_fields = ('email',) + ordering = ('email',) + filter_horizontal = ('groups', 'user_permissions',) \ No newline at end of file diff --git a/src/account/models.py b/src/account/models.py index fd8ad45..b75dd8f 100644 --- a/src/account/models.py +++ b/src/account/models.py @@ -3,7 +3,11 @@ 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) imdb_api_key = models.CharField(max_length=15, blank=False, null=False) + email_notification_is_active = models.BooleanField(default=False) class Meta: db_table = 'user' diff --git a/src/account/templates/acc_active_email.html b/src/account/templates/acc_active_email.html new file mode 100644 index 0000000..eddbd4c --- /dev/null +++ b/src/account/templates/acc_active_email.html @@ -0,0 +1,6 @@ +{% autoescape off %} +Hi {{ user.username }}, +Please click on the link to confirm your registration, +http://{{ domain }}{% url 'activate' uidb64=uid token=token %} +If you think, it's not you, then just ignore this email. +{% endautoescape %} \ No newline at end of file diff --git a/src/account/urls.py b/src/account/urls.py index 8ba8230..1a53bfe 100644 --- a/src/account/urls.py +++ b/src/account/urls.py @@ -8,8 +8,9 @@ urlpatterns = [ template_name = 'login.html' ), name='login'), path('register', views.register_view, name='register'), + path('activate///', views.activate_view, name='activate'), path('logout', views.logout_view, name='logout'), path('change-password', views.change_password_view, name='change-password'), path('profile-editing', views.profile_editing_view, name='profile-editing'), - path('profile', views.ProfileDetailView.as_view(), name='profile') + path('profile', views.ProfileDetailView.as_view(), name='profile'), ] diff --git a/src/account/views/__init__.py b/src/account/views/__init__.py index d0a044b..5fc3e49 100644 --- a/src/account/views/__init__.py +++ b/src/account/views/__init__.py @@ -1,4 +1,5 @@ -from .register import register_view +from .register import (register_view, + activate_view) from .logout import logout_view from .change_password import change_password_view from .profile_editing import profile_editing_view diff --git a/src/account/views/profile_editing.py b/src/account/views/profile_editing.py index 921631f..1e4b94d 100644 --- a/src/account/views/profile_editing.py +++ b/src/account/views/profile_editing.py @@ -1,8 +1,15 @@ -from django.shortcuts import render, redirect +from django.shortcuts import (render, + redirect) 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 @login_required(login_url='/') @@ -10,25 +17,46 @@ def profile_editing_view(request): if request.method == 'POST': form = ProfileEditingForm(request.POST, instance=request.user) if form.is_valid(): - raw_data = get(f"https://imdb-api.com/en/API/Title/{request.POST['imdb_api_key']}/tt0110413") + if 'imdb_api_key' in form.changed_data: + raw_data = get(f"https://imdb-api.com/en/API/Title/{request.POST['imdb_api_key']}/tt0110413") - if raw_data.status_code != 200: - messages.info(request, 'Account not created. Please try again later') - return redirect('profile-editing') - data = raw_data.json() + if raw_data.status_code != 200: + messages.info(request, 'Account not created. Please try again later') + return redirect('profile-editing') + data = raw_data.json() - if data['errorMessage']: - if 'Maximum usage' in data['errorMessage']: + if data['errorMessage']: + if 'Maximum usage' in data['errorMessage']: + messages.info(request, f"IMDB API: {data['errorMessage']}") + return redirect('profile-editing') + + elif data['errorMessage'] == 'Invalid API Key': + form.add_error('imdb_api_key', 'Invalid API Key') + return render(request, 'profile_editing.html', context={"form": form}) messages.info(request, f"IMDB API: {data['errorMessage']}") return redirect('profile-editing') - elif data['errorMessage'] == 'Invalid API Key': - form.add_error('imdb_api_key', 'Invalid API Key') - return render(request, 'profile_editing.html', context={"form": form}) - 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') + 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') + messages.success(request, 'Profile Updated') return redirect('homepage') diff --git a/src/account/views/register.py b/src/account/views/register.py index cc380a4..1c447da 100644 --- a/src/account/views/register.py +++ b/src/account/views/register.py @@ -1,8 +1,18 @@ -from django.shortcuts import render, redirect +from django.shortcuts import (render, + redirect) from django.contrib import messages from account.forms import RegisterForm -from django.contrib.auth import login, authenticate +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 def register_view(request): @@ -27,16 +37,48 @@ def register_view(request): messages.info(request, f"IMDB API: {data['errorMessage']}") return redirect('register') + to_email = form.cleaned_data.get('email') 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') messages.success(request, 'Registration Successful') return redirect('homepage') - else: + else: form = RegisterForm() - return render(request, 'register.html', context={"form": form}) \ No newline at end of file + 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') \ No newline at end of file diff --git a/src/config/settings/base.py b/src/config/settings/base.py index 7ca7697..4fb1002 100644 --- a/src/config/settings/base.py +++ b/src/config/settings/base.py @@ -81,4 +81,12 @@ USE_I18N = True USE_TZ = True -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' \ No newline at end of file +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +EMAIL_BACKEND = 'django_smtp_ssl.SSLEmailBackend' +EMAIL_USE_TLS = True +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_HOST_USER = 'series.notification@gmail.com' +EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") +DEFAULT_FROM_EMAIL = 'series.notification@gmail.com' +EMAIL_PORT = 465 \ No newline at end of file