diff options
| author | Bobby <[email protected]> | 2024-06-13 19:31:04 +0000 |
|---|---|---|
| committer | Bobby <[email protected]> | 2024-06-13 19:31:04 +0000 |
| commit | c27b2930170dbc69d5b2c302bff2eba6b97a5525 (patch) | |
| tree | 2c29f50b15926c41de662791182091fab1a2d2dc | |
| parent | 77275c2c688aa1f337659d98255582627450d43f (diff) | |
| download | thatcomputerscientist-c27b2930170dbc69d5b2c302bff2eba6b97a5525.tar.xz thatcomputerscientist-c27b2930170dbc69d5b2c302bff2eba6b97a5525.zip | |
Ability to Reset Passwords and Better Email Templates
| -rw-r--r-- | blog/urls.py | 64 | ||||
| -rw-r--r-- | blog/views.py | 879 | ||||
| -rw-r--r-- | static/css/login-area.css | 131 | ||||
| -rw-r--r-- | templates/blog/partials/sidebar.html | 1 | ||||
| -rw-r--r-- | templates/blog/resetpass.html | 24 | ||||
| -rw-r--r-- | templates/blog/resetpass_input.html | 23 | ||||
| -rw-r--r-- | users/accountFunctions.py | 10 | ||||
| -rw-r--r-- | users/forms.py | 262 | ||||
| -rw-r--r-- | users/mail_send.py | 34 | ||||
| -rw-r--r-- | users/templates/email_change_verification_email.html | 37 | ||||
| -rw-r--r-- | users/templates/reset_password_email.html | 25 | ||||
| -rw-r--r-- | users/templates/verification_email.html | 36 | ||||
| -rw-r--r-- | users/urls.py | 37 | ||||
| -rw-r--r-- | users/views.py | 299 |
14 files changed, 1290 insertions, 572 deletions
diff --git a/blog/urls.py b/blog/urls.py index 57e5d08d..5f4ebf88 100644 --- a/blog/urls.py +++ b/blog/urls.py @@ -3,30 +3,44 @@ from django.urls import path from . import views from .feed import RSSFeed -app_name = 'blog' +app_name = "blog" urlpatterns = [ - path('', views.home, name='home'), - path('account', views.account, name='account'), - path('register', views.register, name='register'), - path('search', views.search, name='search'), - path('weblog', views.articles, name='articles'), - path('weblog/<str:slug>', views.post, name='post'), - path('weblog/<str:slug>/comment', views.comment, name='comment'), - path('weblog/<str:slug>/anon_comment', views.anon_comment, name='anon_comment'), - path('weblog/<str:slug>/edit_comment', views.edit_comment, name='edit_comment'), - path('weblog/<str:slug>/anon_edit_comment', views.anon_edit_comment, name='anon_edit_comment'), - path('weblog/<str:slug>/delete_comment/<int:comment_id>', views.delete_comment, name='delete_comment'), - path('weblog/<str:slug>/anon_delete_comment/<int:comment_id>', views.anon_delete_comment, name='anon_delete_comment'), - path('archives', views.archives, name='archives'), - path('archives/<str:date>', views.articles, name='archive_posts'), - path('categories', views.categories, name='categories'), - path('categories/<str:cg>', views.articles, name='category_posts'), - path('tags', views.tags, name='tags'), - path('tags/<str:tag_slug>', views.tag_posts, name='tag_posts'), - path('~<str:username>', views.user_activity, name='user_activity'), - path('policy', views.policy, name='policy'), - path('socialify', views.socialify, name='socialify'), - path('rss/', RSSFeed(), name='rss_feed'), - path('anidata', views.anidata, name='anidata'), - path('anilist', views.anilist, name='anilist'), + path("", views.home, name="home"), + path("account", views.account, name="account"), + path("register", views.register, name="register"), + path("forgotpassword", views.forgotpassword, name="forgotpassword"), + path('forgotpassword/reset?uid=<str:uid>&token=<str:token>', views.resetpassword, name='resetpassword'), + path("search", views.search, name="search"), + path("weblog", views.articles, name="articles"), + path("weblog/<str:slug>", views.post, name="post"), + path("weblog/<str:slug>/comment", views.comment, name="comment"), + path("weblog/<str:slug>/anon_comment", views.anon_comment, name="anon_comment"), + path("weblog/<str:slug>/edit_comment", views.edit_comment, name="edit_comment"), + path( + "weblog/<str:slug>/anon_edit_comment", + views.anon_edit_comment, + name="anon_edit_comment", + ), + path( + "weblog/<str:slug>/delete_comment/<int:comment_id>", + views.delete_comment, + name="delete_comment", + ), + path( + "weblog/<str:slug>/anon_delete_comment/<int:comment_id>", + views.anon_delete_comment, + name="anon_delete_comment", + ), + path("archives", views.archives, name="archives"), + path("archives/<str:date>", views.articles, name="archive_posts"), + path("categories", views.categories, name="categories"), + path("categories/<str:cg>", views.articles, name="category_posts"), + path("tags", views.tags, name="tags"), + path("tags/<str:tag_slug>", views.tag_posts, name="tag_posts"), + path("~<str:username>", views.user_activity, name="user_activity"), + path("policy", views.policy, name="policy"), + path("socialify", views.socialify, name="socialify"), + path("rss/", RSSFeed(), name="rss_feed"), + path("anidata", views.anidata, name="anidata"), + path("anilist", views.anilist, name="anilist"), ] diff --git a/blog/views.py b/blog/views.py index ec4d9376..ac086ad1 100644 --- a/blog/views.py +++ b/blog/views.py @@ -23,36 +23,51 @@ from haystack.query import SearchQuerySet from user_agents import parse from announcements.models import Announcement -from users.forms import RegisterForm, UpdateUserDetailsForm +from users.accountFunctions import verify_token +from users.forms import RegisterForm, ResetPasswordForm, UpdateUserDetailsForm, ForgotPasswordForm from users.models import UserProfile from users.tokens import CaptchaTokenGenerator -from .context_processors import (add_excerpt, add_num_comments, avatar_list, - check_spam, comment_processor, - highlight_code_blocks, recent_posts) +from .context_processors import ( + add_excerpt, + add_num_comments, + avatar_list, + check_spam, + comment_processor, + highlight_code_blocks, + recent_posts, +) from .models import AnonymousCommentUser, Category, Comment, Post, Tag from .recommender import next_read load_dotenv() + def atoi(text): return int(text) if text.isdigit() else text + def natural_keys(text): - ''' + """ alist.sort(key=natural_keys) sorts in human order http://nedbatchelder.com/blog/200712/human_sorting.html (See Toothy's implementation in the comments) - ''' - return [ atoi(c) for c in re.split(r'(\d+)', text) ] + """ + return [atoi(c) for c in re.split(r"(\d+)", text)] # Create your views here. + def home(request): - announcements = Announcement.objects.filter(is_public=True).order_by('-created_at') + announcements = Announcement.objects.filter(is_public=True).order_by("-created_at") announcements = announcements if len(announcements) > 0 else None - return render(request, 'blog/home.html', {'title': 'Home', 'posts': recent_posts(), 'announcements': announcements}) + return render( + request, + "blog/home.html", + {"title": "Home", "posts": recent_posts(), "announcements": announcements}, + ) + def tags(request): tags = Tag.objects.all() @@ -63,33 +78,47 @@ def tags(request): tag.pxs = min(tag.pxs, 36) tags = sorted(tags, key=lambda x: x.count, reverse=True) tags = [tag for tag in tags if tag.count > 0] - return render(request, 'blog/tags.html', {'title': 'Tags', 'tags': tags}) + return render(request, "blog/tags.html", {"title": "Tags", "tags": tags}) + def tag_posts(request, tag_slug): try: tag = Tag.objects.get(slug=tag_slug) except Tag.DoesNotExist: tag = { - 'name': tag_slug, - 'slug': tag_slug, - 'count': 0, + "name": tag_slug, + "slug": tag_slug, + "count": 0, } - return render(request, 'blog/tagged.html', {'title': 'Posts Tagged With: ' + tag_slug, 'posts': None, 'tag': tag}) - posts = Post.objects.filter(tags__name__in=[tag.name], is_public=True).order_by('views') + return render( + request, + "blog/tagged.html", + {"title": "Posts Tagged With: " + tag_slug, "posts": None, "tag": tag}, + ) + posts = Post.objects.filter(tags__name__in=[tag.name], is_public=True).order_by( + "views" + ) for post in posts: post.excerpt = add_excerpt(post) post.num_comments = add_num_comments(post) - return render(request, 'blog/tagged.html', {'title': 'Posts Tagged With: ' + tag.name, 'posts': posts, 'tag': tag}) + return render( + request, + "blog/tagged.html", + {"title": "Posts Tagged With: " + tag.name, "posts": posts, "tag": tag}, + ) + def account(request): user = request.user avatarlist = avatar_list() for key in avatarlist: - avatarlist[key] = [re.sub(r'\.gif$', '', string) for string in avatarlist[key]] + avatarlist[key] = [re.sub(r"\.gif$", "", string) for string in avatarlist[key]] avatarlist[key].sort(key=natural_keys) avatarlist = {k: avatarlist[k] for k in sorted(avatarlist)} - blinkies = [re.sub(r'\.gif$', '', string) for string in os.listdir('static/images/blinkies')] + blinkies = [ + re.sub(r"\.gif$", "", string) for string in os.listdir("static/images/blinkies") + ] blinkies.sort(key=natural_keys) if user.is_authenticated: @@ -99,97 +128,209 @@ def account(request): # Set a random avatar avatar_dir = choice(list(avatarlist.keys())) avatar_file = choice(avatarlist[avatar_dir]) - user_profile.avatar_url = avatar_dir + '/' + avatar_file + user_profile.avatar_url = avatar_dir + "/" + avatar_file user_profile.save() except UserProfile.DoesNotExist: # Create a new user profile and set a random avatar user_profile = UserProfile.objects.create(user=user) avatar_dir = choice(list(avatarlist.keys())) avatar_file = choice(avatarlist[avatar_dir]) - user_profile.avatar_url = avatar_dir + '/' + avatar_file + user_profile.avatar_url = avatar_dir + "/" + avatar_file user_profile.save() - if request.GET.get('tab') == 'details': - update_form = UpdateUserDetailsForm(user=user, initial={'first_name': user.first_name, 'last_name': user.last_name, 'bio': user_profile.bio, 'is_public': user_profile.is_public, 'email_public': user_profile.email_public, 'location': user_profile.location}) + if request.GET.get("tab") == "details": + update_form = UpdateUserDetailsForm( + user=user, + initial={ + "first_name": user.first_name, + "last_name": user.last_name, + "bio": user_profile.bio, + "is_public": user_profile.is_public, + "email_public": user_profile.email_public, + "location": user_profile.location, + }, + ) else: update_form = None - return render(request, 'blog/account.html', {'title': 'Account', 'user_profile': user_profile, 'avatarlist': avatarlist, 'update_form': update_form, 'blinkies': blinkies}) + return render( + request, + "blog/account.html", + { + "title": "Account", + "user_profile": user_profile, + "avatarlist": avatarlist, + "update_form": update_form, + "blinkies": blinkies, + }, + ) else: # Redirect to login page - return redirect('blog:home') + return redirect("blog:home") + def register(request): user = request.user if user.is_authenticated: - return redirect('blog:account') + return redirect("blog:account") else: - random_string = ''.join([choice(ascii_letters + digits) for n in range(6)]) + random_string = "".join([choice(ascii_letters + digits) for n in range(6)]) captcha = CaptchaTokenGenerator().encrypt(random_string) - if request.method == 'POST': - expected_captcha = CaptchaTokenGenerator().decrypt(request.POST.get('expected_captcha')) + if request.method == "POST": + expected_captcha = CaptchaTokenGenerator().decrypt( + request.POST.get("expected_captcha") + ) form = RegisterForm(request.POST, expected_captcha=expected_captcha) if form.is_valid(): form.save(request=request) - messages.success(request, 'Account was created! Please check your email to verify your account.', extra_tags='accountCreated') - return redirect('blog:register') + messages.success( + request, + "Account was created! Please check your email to verify your account.", + extra_tags="accountCreated", + ) + return redirect("blog:register") else: - return render(request, 'blog/register.html', {'title': 'Register', 'form': form, 'captcha': captcha}) + return render( + request, + "blog/register.html", + {"title": "Register", "form": form, "captcha": captcha}, + ) else: form = RegisterForm(expected_captcha=random_string) - return render(request, 'blog/register.html', {'title': 'Register', 'form': form, 'captcha': captcha}) + return render( + request, + "blog/register.html", + {"title": "Register", "form": form, "captcha": captcha}, + ) + + +def forgotpassword(request): + user = request.user + if user.is_authenticated: + return redirect("blog:account") + else: + if request.method == "POST": + form = ForgotPasswordForm(request.POST) + if form.is_valid(): + form.save(request) + messages.success( + request, + "An email has been sent to you with instructions on how to reset your password.", + extra_tags="passwordReset", + ) + return redirect("blog:forgotpassword") + else: + return render( + request, + "blog/resetpass.html", + {"title": "Forgot Password", "form": form}, + ) + else: + form = ForgotPasswordForm() + return render( + request, + "blog/resetpass.html", + {"title": "Forgot Password", "form": form}, + ) + +def resetpassword(request, uid, token): + user = request.user + if user.is_authenticated: + return redirect("blog:account") + else: + if request.method == "POST": + form = ResetPasswordForm(request.POST) + if form.is_valid(): + token_object = verify_token('resetpassword', uid, token) + if token_object is not None and token_object.verified: + user = User.objects.get(pk=token_object.user_id) + form.save(user) + messages.success( + request, + "Your password has been reset. You can now login with your new password.", + extra_tags="passwordReset", + ) + token_object.delete() + return redirect("blog:resetpassword", uid=uid, token=token) + else: + messages.error( + request, + "Invalid or expired reset password link. Please try again.", + extra_tags="passwordReset", + ) + return redirect("blog:forgotpassword") + else: + return render( + request, + "blog/resetpass_input.html", + {"title": "Reset Password", "form": form}, + ) + else: + form = ResetPasswordForm() + return render( + request, + "blog/resetpass_input.html", + {"title": "Reset Password", "form": form}, + ) + def post(request, slug): try: post = Post.objects.get(slug=slug) # Get the number of views for this post - x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') + x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") if x_forwarded_for: - ip = x_forwarded_for.split(',')[0] + ip = x_forwarded_for.split(",")[0] else: - ip = request.META.get('REMOTE_ADDR') - user_agent_string = request.META.get('HTTP_USER_AGENT', '') + ip = request.META.get("REMOTE_ADDR") + user_agent_string = request.META.get("HTTP_USER_AGENT", "") user_agent = parse(user_agent_string) - user_identifier = f'{ip}_{user_agent.browser.family}_{user_agent.browser.version_string}_{user_agent.os.family}_{user_agent.os.version_string}' - cache_key = f'post_view_count_{slug}_{user_identifier}' + user_identifier = f"{ip}_{user_agent.browser.family}_{user_agent.browser.version_string}_{user_agent.os.family}_{user_agent.os.version_string}" + cache_key = f"post_view_count_{slug}_{user_identifier}" view_count = cache.get(cache_key, 0) if view_count == 0: post.views += 1 post.save() - cache.set(cache_key, 1, 60 * 60 * 24 * 7) # 1 week + cache.set(cache_key, 1, 60 * 60 * 24 * 7) # 1 week # code stored in .ql-syntax class - soup = BeautifulSoup(post.body, 'html.parser') - code_blocks = soup.find_all('pre') + soup = BeautifulSoup(post.body, "html.parser") + code_blocks = soup.find_all("pre") for code_block in code_blocks: - data_language = code_block.get('data-language') - if data_language == 'true': + data_language = code_block.get("data-language") + if data_language == "true": data_language = None - code_block.replace_with(BeautifulSoup(highlight_code_blocks(code_block, language=data_language), 'html.parser')) + code_block.replace_with( + BeautifulSoup( + highlight_code_blocks(code_block, language=data_language), + "html.parser", + ) + ) # float: right every other image - images = soup.find_all('img') + images = soup.find_all("img") for i in range(len(images)): if i % 2 != 0: - images[i]['style'] = 'float: right; margin-right: 0px; margin-left: 11px;' + images[i][ + "style" + ] = "float: right; margin-right: 0px; margin-left: 11px;" # remove all paragraphs which are: "<p class="ql-align-justify"><br></p>" - for p in soup.find_all('p', class_='ql-align-justify'): - if p.find('br') is not None: + for p in soup.find_all("p", class_="ql-align-justify"): + if p.find("br") is not None: p.decompose() # separate the body in two parts -> the first paragraph and the rest - first_paragraph = soup.find('p') + first_paragraph = soup.find("p") if first_paragraph is not None: first_paragraph = str(first_paragraph) - first_paragraph = first_paragraph.replace('<p>', '<p class="subhead">') - soup.find('p').decompose() + first_paragraph = first_paragraph.replace("<p>", '<p class="subhead">') + soup.find("p").decompose() post.first_paragraph = first_paragraph post.body = str(soup) - post.views = '{:,}'.format(post.views) - + post.views = "{:,}".format(post.views) tags = post.tags.all() comments = Comment.objects.filter(post=post) @@ -206,189 +347,302 @@ def post(request, slug): if post.is_public: # modify request.meta description (only text) and image - request.meta['description'] = BeautifulSoup(first_paragraph, 'html.parser').get_text() - request.meta['image'] = 'https://shi.foo/ignis/post_image/720/' + str(post.id) + '.gif' + request.meta["description"] = BeautifulSoup( + first_paragraph, "html.parser" + ).get_text() + request.meta["image"] = ( + "https://shi.foo/ignis/post_image/720/" + str(post.id) + ".gif" + ) read_next = next_read(post) - return render(request, 'blog/post.html', {'title': post.title, 'post': post, 'tags': tags, 'comments': comments, 'view_count': view_count, 'read_next': read_next}) + return render( + request, + "blog/post.html", + { + "title": post.title, + "post": post, + "tags": tags, + "comments": comments, + "view_count": view_count, + "read_next": read_next, + }, + ) else: - if request.user.is_authenticated and request.user.is_superuser or request.user.is_staff: - return render(request, 'blog/post.html', {'title': post.title, 'post': post, 'tags': tags, 'comments': comments, 'view_count': view_count}) + if ( + request.user.is_authenticated + and request.user.is_superuser + or request.user.is_staff + ): + return render( + request, + "blog/post.html", + { + "title": post.title, + "post": post, + "tags": tags, + "comments": comments, + "view_count": view_count, + }, + ) else: raise Http404 except Post.DoesNotExist: raise Http404 + def comment(request, slug): - if request.method == 'POST': + if request.method == "POST": if request.user.is_authenticated: try: - print(request.POST.get('comment')) - r_spam = check_spam(comment=request.POST.get('comment'), post=Post.objects.get(slug=slug)) - if r_spam != 'N': - messages.error(request, r_spam, extra_tags='spam') - return redirect(reverse('blog:post', kwargs={'slug': slug}) + '#new-comment') - + print(request.POST.get("comment")) + r_spam = check_spam( + comment=request.POST.get("comment"), + post=Post.objects.get(slug=slug), + ) + if r_spam != "N": + messages.error(request, r_spam, extra_tags="spam") + return redirect( + reverse("blog:post", kwargs={"slug": slug}) + "#new-comment" + ) + # then we continue post = Post.objects.get(slug=slug) if post.is_public: - comment = Comment.objects.create(user=request.user, post=post, body=request.POST.get('comment')) - return redirect(reverse('blog:post', kwargs={'slug': slug}) + '#comment-' + str(comment.id)) + comment = Comment.objects.create( + user=request.user, post=post, body=request.POST.get("comment") + ) + return redirect( + reverse("blog:post", kwargs={"slug": slug}) + + "#comment-" + + str(comment.id) + ) else: - if request.user.is_authenticated and request.user.is_superuser or request.user.is_staff: - Comment.objects.create(user=request.user, post=post, body=request.POST.get('comment')) - return redirect(reverse('blog:post', kwargs={'slug': slug}) + '#comment-' + str(comment.id)) + if ( + request.user.is_authenticated + and request.user.is_superuser + or request.user.is_staff + ): + Comment.objects.create( + user=request.user, + post=post, + body=request.POST.get("comment"), + ) + return redirect( + reverse("blog:post", kwargs={"slug": slug}) + + "#comment-" + + str(comment.id) + ) else: - return HttpResponse('Post not found!', status=404) + return HttpResponse("Post not found!", status=404) except Post.DoesNotExist: - return HttpResponse('Post not found!', status=404) + return HttpResponse("Post not found!", status=404) else: - return redirect('blog:home') + return redirect("blog:home") else: - return redirect('blog:home') - + return redirect("blog:home") + + def anon_comment(request, slug): - if request.method == 'POST': + if request.method == "POST": if request.user.is_authenticated: # not allowed this is anonymous comment form - return redirect(reverse('blog:post', kwargs={'slug': slug})) + return redirect(reverse("blog:post", kwargs={"slug": slug})) else: - anonymous_name = request.POST.get('anonymous-name') - anonymous_email = request.POST.get('anonymous-email') - anonymous_token, at = request.POST.get('anonymous-token'), request.POST.get('anonymous-token') - new_anonymous_token = request.POST.get('new-anonymous-token') - anonymous_comment = request.POST.get('anonymous-comment') - res_spam = check_spam(comment=anonymous_comment, post=Post.objects.get(slug=slug)) - if res_spam != 'N': - messages.error(request, res_spam, extra_tags='spam') - return redirect(reverse('blog:post', kwargs={'slug': slug}) + '#new-comment') + anonymous_name = request.POST.get("anonymous-name") + anonymous_email = request.POST.get("anonymous-email") + anonymous_token, at = request.POST.get("anonymous-token"), request.POST.get( + "anonymous-token" + ) + new_anonymous_token = request.POST.get("new-anonymous-token") + anonymous_comment = request.POST.get("anonymous-comment") + res_spam = check_spam( + comment=anonymous_comment, post=Post.objects.get(slug=slug) + ) + if res_spam != "N": + messages.error(request, res_spam, extra_tags="spam") + return redirect( + reverse("blog:post", kwargs={"slug": slug}) + "#new-comment" + ) # now continue with the comment if not anonymous_name: - messages.error(request, 'Please enter a name!') - return redirect(reverse('blog:post', kwargs={'slug': slug})) + messages.error(request, "Please enter a name!") + return redirect(reverse("blog:post", kwargs={"slug": slug})) if not anonymous_comment: - messages.error(request, 'Please enter a comment!') - return redirect(reverse('blog:post', kwargs={'slug': slug})) + messages.error(request, "Please enter a comment!") + return redirect(reverse("blog:post", kwargs={"slug": slug})) if not anonymous_email: - anonymous_email = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + '@anonymous.shi.foo' + anonymous_email = ( + "".join(random.choice(string.ascii_lowercase) for i in range(10)) + + "@anonymous.shi.foo" + ) if not anonymous_token: - anonymous_token = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + anonymous_token = "".join( + random.choice(string.ascii_lowercase) for i in range(10) + ) at = anonymous_token # generate a random avatar for the anonymous user avatarlist = avatar_list() for key in avatarlist: - avatarlist[key] = [re.sub(r'\.gif$', '', string) for string in avatarlist[key]] + avatarlist[key] = [ + re.sub(r"\.gif$", "", string) for string in avatarlist[key] + ] avatarlist[key].sort(key=natural_keys) avatarlist = {k: avatarlist[k] for k in sorted(avatarlist)} avatar_dir = choice(list(avatarlist.keys())) avatar_file = choice(avatarlist[avatar_dir]) - anonymous_avatar = avatar_dir + '/' + avatar_file - anonymous_token = hashlib.sha256(anonymous_token.encode('utf-8')).hexdigest() + anonymous_avatar = avatar_dir + "/" + avatar_file + anonymous_token = hashlib.sha256( + anonymous_token.encode("utf-8") + ).hexdigest() try: - anonymous_user = AnonymousCommentUser.objects.get(email=anonymous_email, token=anonymous_token) + anonymous_user = AnonymousCommentUser.objects.get( + email=anonymous_email, token=anonymous_token + ) except AnonymousCommentUser.DoesNotExist: - anonymous_user = AnonymousCommentUser.objects.create(email=anonymous_email, token=anonymous_token, - avatar=anonymous_avatar) + anonymous_user = AnonymousCommentUser.objects.create( + email=anonymous_email, + token=anonymous_token, + avatar=anonymous_avatar, + ) if new_anonymous_token: at = new_anonymous_token - new_anonymous_token = hashlib.sha256(new_anonymous_token.encode('utf-8')).hexdigest() + new_anonymous_token = hashlib.sha256( + new_anonymous_token.encode("utf-8") + ).hexdigest() anonymous_user.token = new_anonymous_token anonymous_user.save() - + # update the anonymous user's name if it has changed if anonymous_user.name != anonymous_name: anonymous_user.name = anonymous_name anonymous_user.save() - - comment = Comment.objects.create(anonymous_user=anonymous_user, post=Post.objects.get(slug=slug), body=anonymous_comment) + + comment = Comment.objects.create( + anonymous_user=anonymous_user, + post=Post.objects.get(slug=slug), + body=anonymous_comment, + ) # redirect to the post with the comment but set the anonymous user cookie - response = redirect(reverse('blog:post', kwargs={'slug': slug}) + '#comment-' + str(comment.id)) - response.set_cookie('anonymous_name', anonymous_user.name, max_age=60*60*24*365) - response.set_cookie('anonymous_email', anonymous_user.email, max_age=60*60*24*365) - response.set_cookie('anonymous_token', at, max_age=60*60*24*365) + response = redirect( + reverse("blog:post", kwargs={"slug": slug}) + + "#comment-" + + str(comment.id) + ) + response.set_cookie( + "anonymous_name", anonymous_user.name, max_age=60 * 60 * 24 * 365 + ) + response.set_cookie( + "anonymous_email", anonymous_user.email, max_age=60 * 60 * 24 * 365 + ) + response.set_cookie("anonymous_token", at, max_age=60 * 60 * 24 * 365) return response else: - return redirect('blog:home') + return redirect("blog:home") + def edit_comment(request, slug): - if request.method == 'POST': + if request.method == "POST": if request.user.is_authenticated: try: - comment = Comment.objects.get(id=request.POST.get('comment_id')) + comment = Comment.objects.get(id=request.POST.get("comment_id")) # check for spam first - user_ip = request.META.get('HTTP_X_FORWARDED_FOR') + user_ip = request.META.get("HTTP_X_FORWARDED_FOR") if user_ip: - user_ip = user_ip.split(',')[0] + user_ip = user_ip.split(",")[0] else: - user_ip = request.META.get('REMOTE_ADDR') - user_agent_string = request.META.get('HTTP_USER_AGENT', '') + user_ip = request.META.get("REMOTE_ADDR") + user_agent_string = request.META.get("HTTP_USER_AGENT", "") user_agent = parse(user_agent_string) # if check_spam(user_ip=user_ip, user_agent=user_agent, comment=request.POST.get('body'), author=request.user.username - res_spam = check_spam(comment=request.POST.get('body'), post=comment.post) - if res_spam != 'N': - messages.error(request, request.POST.get('body'), extra_tags='spam') - return redirect(reverse('blog:post', kwargs={'slug': slug}) + '#comment-' + str(comment.id)) + res_spam = check_spam( + comment=request.POST.get("body"), post=comment.post + ) + if res_spam != "N": + messages.error(request, request.POST.get("body"), extra_tags="spam") + return redirect( + reverse("blog:post", kwargs={"slug": slug}) + + "#comment-" + + str(comment.id) + ) if comment.user == request.user: - comment.body = request.POST.get('body') + comment.body = request.POST.get("body") comment.edited = True comment.edited_at = datetime.now() comment.save() - return redirect(reverse('blog:post', kwargs={'slug': slug}) + '#comment-' + str(comment.id)) + return redirect( + reverse("blog:post", kwargs={"slug": slug}) + + "#comment-" + + str(comment.id) + ) else: - return HttpResponse('Unauthorized!', status=401) + return HttpResponse("Unauthorized!", status=401) except Comment.DoesNotExist: - return HttpResponse('Comment not found!', status=404) + return HttpResponse("Comment not found!", status=404) else: - return redirect('blog:home') + return redirect("blog:home") else: - return redirect('blog:home') - + return redirect("blog:home") + + def anon_edit_comment(request, slug): - if request.method == 'POST': + if request.method == "POST": if request.user.is_authenticated: # not allowed this is anonymous comment form - return redirect(reverse('blog:post', kwargs={'slug': slug})) + return redirect(reverse("blog:post", kwargs={"slug": slug})) else: - anonymous_token = request.COOKIES.get('anonymous_token') + anonymous_token = request.COOKIES.get("anonymous_token") if not anonymous_token: - return HttpResponse('Unauthorized!', status=401) + return HttpResponse("Unauthorized!", status=401) try: - anonymous_token = hashlib.sha256(anonymous_token.encode('utf-8')).hexdigest() - comment = Comment.objects.get(id=request.POST.get('comment_id')) + anonymous_token = hashlib.sha256( + anonymous_token.encode("utf-8") + ).hexdigest() + comment = Comment.objects.get(id=request.POST.get("comment_id")) # check for spam first - user_ip = request.META.get('HTTP_X_FORWARDED_FOR') + user_ip = request.META.get("HTTP_X_FORWARDED_FOR") if user_ip: - user_ip = user_ip.split(',')[0] + user_ip = user_ip.split(",")[0] else: - user_ip = request.META.get('REMOTE_ADDR') - user_agent_string = request.META.get('HTTP_USER_AGENT', '') + user_ip = request.META.get("REMOTE_ADDR") + user_agent_string = request.META.get("HTTP_USER_AGENT", "") user_agent = parse(user_agent_string) - res_spam = check_spam(comment=request.POST.get('body'), post=comment.post) - if res_spam != 'N': - # if check_spam(user_ip=user_ip, user_agent=user_agent, comment=request.POST.get('body'), author=comment.anonymous_user.name): - messages.error(request, request.POST.get('body'), extra_tags='spam') - return redirect(reverse('blog:post', kwargs={'slug': slug}) + '#comment-' + str(comment.id)) + res_spam = check_spam( + comment=request.POST.get("body"), post=comment.post + ) + if res_spam != "N": + # if check_spam(user_ip=user_ip, user_agent=user_agent, comment=request.POST.get('body'), author=comment.anonymous_user.name): + messages.error(request, request.POST.get("body"), extra_tags="spam") + return redirect( + reverse("blog:post", kwargs={"slug": slug}) + + "#comment-" + + str(comment.id) + ) if comment.anonymous_user.token == anonymous_token: - comment.body = request.POST.get('body') + comment.body = request.POST.get("body") comment.edited = True comment.edited_at = datetime.now() comment.save() - return redirect(reverse('blog:post', kwargs={'slug': slug}) + '#comment-' + str(comment.id)) + return redirect( + reverse("blog:post", kwargs={"slug": slug}) + + "#comment-" + + str(comment.id) + ) else: - return HttpResponse('Unauthorized!', status=401) + return HttpResponse("Unauthorized!", status=401) except Comment.DoesNotExist: - return HttpResponse('Comment not found!', status=404) + return HttpResponse("Comment not found!", status=404) else: - return redirect('blog:home') + return redirect("blog:home") + def delete_comment(request, slug, comment_id): if request.user.is_authenticated: @@ -396,92 +650,142 @@ def delete_comment(request, slug, comment_id): comment = Comment.objects.get(id=comment_id) if comment.user == request.user: comment.delete() - return redirect(reverse('blog:post', kwargs={'slug': slug}) + '#comments') + return redirect( + reverse("blog:post", kwargs={"slug": slug}) + "#comments" + ) else: - return HttpResponse('Unauthorized!', status=401) + return HttpResponse("Unauthorized!", status=401) except Comment.DoesNotExist: - return HttpResponse('Comment not found!', status=404) + return HttpResponse("Comment not found!", status=404) else: - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) + def anon_delete_comment(request, slug, comment_id): if request.user.is_authenticated: # not allowed this is anonymous comment form - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - anonymous_token = request.COOKIES.get('anonymous_token') + anonymous_token = request.COOKIES.get("anonymous_token") if not anonymous_token: - return HttpResponse('Unauthorized!', status=401) - anonymous_token = hashlib.sha256(anonymous_token.encode('utf-8')).hexdigest() + return HttpResponse("Unauthorized!", status=401) + anonymous_token = hashlib.sha256(anonymous_token.encode("utf-8")).hexdigest() try: - comment = Comment.objects.get(id=comment_id, anonymous_user__token=anonymous_token) + comment = Comment.objects.get( + id=comment_id, anonymous_user__token=anonymous_token + ) comment.delete() - return redirect(reverse('blog:post', kwargs={'slug': slug}) + '#comments') + return redirect(reverse("blog:post", kwargs={"slug": slug}) + "#comments") except Comment.DoesNotExist: - return HttpResponse('Comment not found!', status=404) + return HttpResponse("Comment not found!", status=404) + def search(request): - query = request.GET.get('q') - search_in = request.GET.getlist('search_in') if request.GET.get('search_in') else ['posts'] - sort_by = request.GET.get('sort_by') if request.GET.get('sort_by') else 'relevance' - order = request.GET.get('order') if request.GET.get('order') else 'ascending' - date_range = request.GET.get('date_range') if request.GET.get('date_range') else 'any' + query = request.GET.get("q") + search_in = ( + request.GET.getlist("search_in") if request.GET.get("search_in") else ["posts"] + ) + sort_by = request.GET.get("sort_by") if request.GET.get("sort_by") else "relevance" + order = request.GET.get("order") if request.GET.get("order") else "ascending" + date_range = ( + request.GET.get("date_range") if request.GET.get("date_range") else "any" + ) search_model_map = { - 'posts': Post, - 'users': User, - 'comments': Comment, + "posts": Post, + "users": User, + "comments": Comment, } now = timezone.now() if query: search_results = SearchQuerySet().filter(content=query) if search_in: - search_results = search_results.models(*[search_model_map[model] for model in search_in]) + search_results = search_results.models( + *[search_model_map[model] for model in search_in] + ) # search_results = [result.object for result in search_results] - posts = [result.object for result in search_results if isinstance(result.object, Post)] - users = [result.object for result in search_results if isinstance(result.object, User)] - comments = [result.object for result in search_results if isinstance(result.object, Comment)] + posts = [ + result.object + for result in search_results + if isinstance(result.object, Post) + ] + users = [ + result.object + for result in search_results + if isinstance(result.object, User) + ] + comments = [ + result.object + for result in search_results + if isinstance(result.object, Comment) + ] # match-case sort_by - if sort_by == 'relevance' and order == 'ascending': + if sort_by == "relevance" and order == "ascending": posts = sorted(posts, key=lambda post: post.views) users = sorted(users, key=lambda user: user.username) comments = sorted(comments, key=lambda comment: comment.id) - elif sort_by == 'relevance' and order == 'descending': + elif sort_by == "relevance" and order == "descending": posts = sorted(posts, key=lambda post: post.views, reverse=True) users = sorted(users, key=lambda user: user.username, reverse=True) comments = sorted(comments, key=lambda comment: comment.id, reverse=True) - elif sort_by == 'date' and order == 'ascending': + elif sort_by == "date" and order == "ascending": posts = sorted(posts, key=lambda post: post.date) users = sorted(users, key=lambda user: user.date_joined) comments = sorted(comments, key=lambda comment: comment.created_at) - elif sort_by == 'date' and order == 'descending': + elif sort_by == "date" and order == "descending": posts = sorted(posts, key=lambda post: post.date, reverse=True) users = sorted(users, key=lambda user: user.date_joined, reverse=True) - comments = sorted(comments, key=lambda comment: comment.created_at, reverse=True) + comments = sorted( + comments, key=lambda comment: comment.created_at, reverse=True + ) # filter by date_range - if date_range == 'past_day': + if date_range == "past_day": posts = [post for post in posts if post.date >= now - timedelta(days=1)] - users = [user for user in users if user.date_joined >= now - timedelta(days=1)] - comments = [comment for comment in comments if comment.created_at >= now - timedelta(days=1)] - elif date_range == 'past_week': + users = [ + user for user in users if user.date_joined >= now - timedelta(days=1) + ] + comments = [ + comment + for comment in comments + if comment.created_at >= now - timedelta(days=1) + ] + elif date_range == "past_week": posts = [post for post in posts if post.date >= now - timedelta(days=7)] - users = [user for user in users if user.date_joined >= now - timedelta(days=7)] - comments = [comment for comment in comments if comment.created_at >= now - timedelta(days=7)] - elif date_range == 'past_month': + users = [ + user for user in users if user.date_joined >= now - timedelta(days=7) + ] + comments = [ + comment + for comment in comments + if comment.created_at >= now - timedelta(days=7) + ] + elif date_range == "past_month": posts = [post for post in posts if post.date >= now - timedelta(days=30)] - users = [user for user in users if user.date_joined >= now - timedelta(days=30)] - comments = [comment for comment in comments if comment.created_at >= now - timedelta(days=30)] - elif date_range == 'past_year': + users = [ + user for user in users if user.date_joined >= now - timedelta(days=30) + ] + comments = [ + comment + for comment in comments + if comment.created_at >= now - timedelta(days=30) + ] + elif date_range == "past_year": posts = [post for post in posts if post.date >= now - timedelta(days=365)] - users = [user for user in users if user.date_joined >= now - timedelta(days=365)] - comments = [comment for comment in comments if comment.created_at >= now - timedelta(days=365)] - elif date_range == 'any': + users = [ + user for user in users if user.date_joined >= now - timedelta(days=365) + ] + comments = [ + comment + for comment in comments + if comment.created_at >= now - timedelta(days=365) + ] + elif date_range == "any": # no need to filter pass - + search_results = len(posts) + len(users) + len(comments) else: search_results = 0 @@ -494,15 +798,31 @@ def search(request): for comment in comments: comment.body = comment_processor(comment.body) - return render(request, 'blog/search.html', {'title': f"Search results for '{query}'", 'query': query, 'search_results': search_results, 'search_in': search_in, 'sort_by': sort_by, 'order': order, 'date_range': date_range, 'posts': posts, 'users': users, 'comments': comments}) + return render( + request, + "blog/search.html", + { + "title": f"Search results for '{query}'", + "query": query, + "search_results": search_results, + "search_in": search_in, + "sort_by": sort_by, + "order": order, + "date_range": date_range, + "posts": posts, + "users": users, + "comments": comments, + }, + ) + def articles(request, date=None, cg=None): - type = 'articles' - page = request.GET.get('page') if request.GET.get('page') else 1 - order_by = request.GET.get('order_by') if request.GET.get('order_by') else 'date' - direction = request.GET.get('direction') if request.GET.get('direction') else 'desc' + type = "articles" + page = request.GET.get("page") if request.GET.get("page") else 1 + order_by = request.GET.get("order_by") if request.GET.get("order_by") else "date" + direction = request.GET.get("direction") if request.GET.get("direction") else "desc" categories = Category.objects.all() - category = request.GET.get('category') + category = request.GET.get("category") try: page = int(page) except: @@ -511,28 +831,35 @@ def articles(request, date=None, cg=None): posts = Post.objects.filter(is_public=True) if date: - date_month = date.split('_')[0] # month name like 'Decemeber' - date_year = date.split('_')[1] # year like '2019' - date_m = datetime.strptime(date_month, '%B').month # convert month name to month number - posts = Post.objects.filter(is_public=True, date__month=date_m, date__year=date_year) - type = 'articles-archive' - date = date_month + ' ' + date_year - + date_month = date.split("_")[0] # month name like 'Decemeber' + date_year = date.split("_")[1] # year like '2019' + date_m = datetime.strptime( + date_month, "%B" + ).month # convert month name to month number + posts = Post.objects.filter( + is_public=True, date__month=date_m, date__year=date_year + ) + type = "articles-archive" + date = date_month + " " + date_year + if cg: cg = str.lower(cg) - if category and cg != category and category != 'all': - return redirect(reverse('blog:categories') + '/{}'.format(category)) + if category and cg != category and category != "all": + return redirect(reverse("blog:categories") + "/{}".format(category)) category = cg posts = Post.objects.filter(is_public=True, category__slug=cg) - type = 'articles-category' - - - posts = posts.order_by('-' + order_by) if direction == 'desc' else Post.objects.filter(is_public=True).order_by(order_by) - if category and category != 'all': + type = "articles-category" + + posts = ( + posts.order_by("-" + order_by) + if direction == "desc" + else Post.objects.filter(is_public=True).order_by(order_by) + ) + if category and category != "all": posts = posts.filter(category__slug=category) category_name = Category.objects.get(slug=category).name else: - category = 'all' + category = "all" posts = Paginator(posts, 10) num_pages = posts.num_pages try: @@ -543,99 +870,159 @@ def articles(request, date=None, cg=None): for post in posts: post.excerpt = add_excerpt(post) post.num_comments = add_num_comments(post) - return render(request, 'blog/articles.html', {'title': 'Articles', 'posts': posts, 'num_pages': num_pages, 'page': page, 'order_by': order_by, 'direction': direction, 'categories': categories, 'category': category, 'category_name': category_name if category != 'all' else '', 'type': type, 'date': date if date else '', 'cg': cg if cg else ''}) + return render( + request, + "blog/articles.html", + { + "title": "Articles", + "posts": posts, + "num_pages": num_pages, + "page": page, + "order_by": order_by, + "direction": direction, + "categories": categories, + "category": category, + "category_name": category_name if category != "all" else "", + "type": type, + "date": date if date else "", + "cg": cg if cg else "", + }, + ) + def user_activity(request, username): try: user = User.objects.get(username__iexact=username) user_profile = UserProfile.objects.get(user=user) if user_profile.is_public or user == request.user: - recent_comments = Comment.objects.filter(user=user).order_by('-created_at')[:5] + recent_comments = Comment.objects.filter(user=user).order_by("-created_at")[ + :5 + ] else: recent_comments = [] if user_profile.email_public: user_email = user.email else: - user_email = '' + user_email = "" for comment in recent_comments: comment.body = comment_processor(comment.body) - return render(request, 'blog/activity.html', {'title': 'User Activity', 'activity_user': user, 'activity_user_profile': user_profile, 'activity_recent_comments': recent_comments, 'activity_user_email': user_email}) + return render( + request, + "blog/activity.html", + { + "title": "User Activity", + "activity_user": user, + "activity_user_profile": user_profile, + "activity_recent_comments": recent_comments, + "activity_user_email": user_email, + }, + ) except User.DoesNotExist: # return default 404 page raise Http404 + def archives(request): - archives = Post.objects.filter(is_public=True).dates('date', 'month', order='DESC') - return render(request, 'blog/archives.html', {'title': 'Archives', 'archives': archives}) + archives = Post.objects.filter(is_public=True).dates("date", "month", order="DESC") + return render( + request, "blog/archives.html", {"title": "Archives", "archives": archives} + ) + def categories(request): categories = Category.objects.all() - return render(request, 'blog/categories.html', {'title': 'Categories', 'categories': categories}) + return render( + request, + "blog/categories.html", + {"title": "Categories", "categories": categories}, + ) + def policy(request): - return render(request, 'blog/site_policy.html', {'title': 'Site Policy'}) + return render(request, "blog/site_policy.html", {"title": "Site Policy"}) + def socialify(request): - url = request.GET.get('url') if request.GET.get('url') else None + url = request.GET.get("url") if request.GET.get("url") else None if url: # convert Github URL to repo owner/name - if 'github.com' in url: - url = url.split('github.com/')[1] - url = url.split('/') - url = url[0] + '/' + url[1] + if "github.com" in url: + url = url.split("github.com/")[1] + url = url.split("/") + url = url[0] + "/" + url[1] socialify_options = { - 'theme': 'Dark' if not request.GET.get('theme') else request.GET.get('theme'), - 'font': 'Inter' if not request.GET.get('font') else request.GET.get('font'), - 'description': 0 if not request.GET.get('description') else request.GET.get('description'), - 'forks': 0 if not request.GET.get('forks') else request.GET.get('forks'), - 'issues': 0 if not request.GET.get('issues') else request.GET.get('issues'), - 'language_1': 0 if not request.GET.get('language_1') else request.GET.get('language_1'), - 'language_2': 0 if not request.GET.get('language_2') else request.GET.get('language_2'), - 'name': 0 if not request.GET.get('name') else request.GET.get('name'), - 'owner': 1 if not request.GET.get('owner') else request.GET.get('owner'), - 'stargazers': 0 if not request.GET.get('stargazers') else request.GET.get('stargazers'), - 'pulls': 0 if not request.GET.get('pulls') else request.GET.get('pulls'), - 'pattern': 'Plus' if not request.GET.get('pattern') else request.GET.get('pattern'), + "theme": "Dark" if not request.GET.get("theme") else request.GET.get("theme"), + "font": "Inter" if not request.GET.get("font") else request.GET.get("font"), + "description": ( + 0 if not request.GET.get("description") else request.GET.get("description") + ), + "forks": 0 if not request.GET.get("forks") else request.GET.get("forks"), + "issues": 0 if not request.GET.get("issues") else request.GET.get("issues"), + "language_1": ( + 0 if not request.GET.get("language_1") else request.GET.get("language_1") + ), + "language_2": ( + 0 if not request.GET.get("language_2") else request.GET.get("language_2") + ), + "name": 0 if not request.GET.get("name") else request.GET.get("name"), + "owner": 1 if not request.GET.get("owner") else request.GET.get("owner"), + "stargazers": ( + 0 if not request.GET.get("stargazers") else request.GET.get("stargazers") + ), + "pulls": 0 if not request.GET.get("pulls") else request.GET.get("pulls"), + "pattern": ( + "Plus" if not request.GET.get("pattern") else request.GET.get("pattern") + ), } for key, value in socialify_options.items(): - if value == 'on': + if value == "on": socialify_options[key] = 1 - elif value == 'off': + elif value == "off": socialify_options[key] = 0 - return render(request, 'blog/socialify.html', {'title': 'Socialify', 'options': socialify_options, 'url': url}) + return render( + request, + "blog/socialify.html", + {"title": "Socialify", "options": socialify_options, "url": url}, + ) + def anilist(request): - return render(request, 'blog/anilist.html', {'title': 'My Anime List'}) + return render(request, "blog/anilist.html", {"title": "My Anime List"}) + @xframe_options_sameorigin def anidata(request): - malURL = 'https://myanimelist.net/animelist/crvs' + malURL = "https://myanimelist.net/animelist/crvs" MAL = requests.get(malURL) MALContent = MAL.content MALStatus = MAL.status_code - + if MALStatus != 200: MALContent = '<html><head><link rel="stylesheet" href="/static/css/styles.css"><style>img { width: 75%; display: block; margin: -20px auto 20px auto; } h1 { text-align: center; } p { text-align: center; } body {background: transparent !important;} </style></head><body><img src="/static/images/site/sad-failure.gif" alt="Sad Failure"><h1>MyAnimeList does not seem to respond at the moment.</h1><p>Maybe, we go <a href="https://myanimelist.net/animelist/crvs" target="_blank">knock on their door</a> instead?</p></body></html>' else: - MALContent = MALContent.decode('utf-8') - MALParsed = BeautifulSoup(MALContent, 'html.parser') + MALContent = MALContent.decode("utf-8") + MALParsed = BeautifulSoup(MALContent, "html.parser") # remove script tags - for tag in MALParsed(['script', 'meta', 'noscript']): + for tag in MALParsed(["script", "meta", "noscript"]): tag.extract() # add myanimelist.net to relative links - for link in MALParsed.find_all('a'): - if link.get('href') and link.get('href')[0] == '/': - link['href'] = 'https://myanimelist.net' + link['href'] + for link in MALParsed.find_all("a"): + if link.get("href") and link.get("href")[0] == "/": + link["href"] = "https://myanimelist.net" + link["href"] # make all links open in new tab - link['target'] = '_blank' + link["target"] = "_blank" MALContent = MALParsed.prettify() - return render(request, 'blog/anidata.html', {'title': 'My Anime List', 'MALContent': MALContent}) + return render( + request, + "blog/anidata.html", + {"title": "My Anime List", "MALContent": MALContent}, + ) diff --git a/static/css/login-area.css b/static/css/login-area.css index ceae4de3..a707919c 100644 --- a/static/css/login-area.css +++ b/static/css/login-area.css @@ -1,101 +1,116 @@ #login-area { - width: 250px; - height: 350px; - background: url('../images/backgrounds/login-area.png') no-repeat; - background-size: 250px 350px; - margin: auto; - padding: 0px; - border: 0px; - position: relative; + width: 250px; + height: 350px; + background: url("../images/backgrounds/login-area.png") no-repeat; + background-size: 250px 350px; + margin: auto; + padding: 0px; + border: 0px; + position: relative; } #login-form { - display: block; - padding-top: 151px; - padding-left: 20px; - padding-right: 20px; + display: block; + padding-top: 151px; + padding-left: 20px; + padding-right: 20px; } -#login-form input[type=text], #login-form input[type=password] { - display: block; - margin: 0px auto 18px auto; - width: 180px; - font-size: 12px; - padding: 4px 8px; - background: transparent; - color: #fff; - border-radius: 4px; +#login-form input[type="text"], +#login-form input[type="password"] { + display: block; + margin: 0px auto 18px auto; + width: 180px; + font-size: 12px; + padding: 4px 8px; + background: transparent; + color: #fff; + border-radius: 4px; } /* Reset auto fill */ #login-form input:-webkit-autofill, -#login-form input:-webkit-autofill:hover, -#login-form input:-webkit-autofill:focus, -#login-form input:-webkit-autofill:active{ - transition: background-color 1s ease-in 2000s; +#login-form input:-webkit-autofill:hover, +#login-form input:-webkit-autofill:focus, +#login-form input:-webkit-autofill:active { + transition: background-color 1s ease-in 2000s; } #login-form input::placeholder { - color: #dcdcdc; + color: #dcdcdc; } -#login-form input[type=submit] { - width: 131px; - height: 26px; - cursor: pointer; - position: absolute; - bottom: 55px; - left: 61px; - border-radius: 2px; - background: transparent; +#login-form input[type="submit"] { + width: 131px; + height: 26px; + cursor: pointer; + position: absolute; + bottom: 55px; + left: 61px; + border-radius: 2px; + background: transparent; } #login-area > #register-now-button { - display: block; - width: 60px; - height: 12px; - cursor: pointer; - border-radius: 2px; - position: absolute; - bottom: 30px; - right: 40px; - background: transparent; + display: block; + width: 60px; + height: 12px; + cursor: pointer; + border-radius: 2px; + position: absolute; + bottom: 30px; + right: 40px; + background: transparent; +} + +#login-area > #forgot-password-button { + display: block; + width: 80px; + height: 12px; + cursor: pointer; + border-radius: 2px; + position: absolute; + bottom: 106px; + right: 26px; + background: transparent; } #login-error { - position: relative; + position: relative; } #login-error > .RFEERR { - background: url('../images/backgrounds/login-messages/RFEERR.png') no-repeat; + background: url("../images/backgrounds/login-messages/RFEERR.png") no-repeat; } #login-error > .IUOPERR { - background: url('../images/backgrounds/login-messages/IUOPERR.png') no-repeat; + background: url("../images/backgrounds/login-messages/IUOPERR.png") no-repeat; } #login-error > .ENVERR { - background: url('../images/backgrounds/login-messages/ENVERR.png') no-repeat; + background: url("../images/backgrounds/login-messages/ENVERR.png") no-repeat; } #login-error > .VESENDERR { - background: url('../images/backgrounds/login-messages/VESENDERR.png') no-repeat; + background: url("../images/backgrounds/login-messages/VESENDERR.png") + no-repeat; } #login-error > .VESENT { - background: url('../images/backgrounds/login-messages/VESENT.png') no-repeat; + background: url("../images/backgrounds/login-messages/VESENT.png") no-repeat; } #login-error > .VESUCCESS { - background: url('../images/backgrounds/login-messages/VESUCCESS.png') no-repeat; + background: url("../images/backgrounds/login-messages/VESUCCESS.png") + no-repeat; } #login-error > .messageBox { - position: absolute; - background-size: 250px 166px; - width: 250px; - height: 166px; - top: -100px; - left: -140px; - z-index: 2; + position: absolute; + background-size: 250px 166px; + width: 250px; + height: 166px; + top: -100px; + left: -140px; + z-index: 2; } diff --git a/templates/blog/partials/sidebar.html b/templates/blog/partials/sidebar.html index d638f3b4..6f50ed56 100644 --- a/templates/blog/partials/sidebar.html +++ b/templates/blog/partials/sidebar.html @@ -34,6 +34,7 @@ <input type="submit" value=""> </form> <a href="{% url 'blog:register' %}" id="register-now-button"></a> + <a href="{% url 'blog:forgotpassword' %}" id="forgot-password-button"></a> </div> {% endif %} diff --git a/templates/blog/resetpass.html b/templates/blog/resetpass.html new file mode 100644 index 00000000..7dbdab05 --- /dev/null +++ b/templates/blog/resetpass.html @@ -0,0 +1,24 @@ +{% extends 'blog/partials/base.html' %} {% block content %} +<p> + Forgot your password? No problem! Just enter your email address below, (the + email you registered with) and I will send you a link to reset your password. +</p> +<hr /> +<form method="post"> + <input + type="hidden" + name="csrfmiddlewaretoken" + value="{{ csrf_token }}" + style="display: none" + /> + <table id="resetpass_form"> + {{ form.as_table }} + </table> + <br /> + <input type="submit" value="Reset password" class="button button-special" /> +</form> +<br /><br /> +<p><i>Anonymous users cannot reset their password.</i></p> +{% for message in messages %} {% if 'passwordReset' in message.tags %} +<p><small class="success">{{ message.message }}</small></p> +{% endif %} {% endfor %} {% endblock content %} diff --git a/templates/blog/resetpass_input.html b/templates/blog/resetpass_input.html new file mode 100644 index 00000000..7adc54d6 --- /dev/null +++ b/templates/blog/resetpass_input.html @@ -0,0 +1,23 @@ +{% extends 'blog/partials/base.html' %} {% block content %} +<p> + Enter a new password for your account. Your password must be at least 8 + characters long. +</p> +<hr /> +<form method="post"> + <input + type="hidden" + name="csrfmiddlewaretoken" + value="{{ csrf_token }}" + style="display: none" + /> + <table id="resetpass_form"> + {{ form.as_table }} + </table> + <br /> + <input type="submit" value="Reset password" class="button button-special" /> +</form> +<br /><br /> +{% for message in messages %} {% if 'passwordReset' in message.tags %} +<p><small class="success">{{ message.message }}</small></p> +{% endif %} {% endfor %} {% endblock content %} diff --git a/users/accountFunctions.py b/users/accountFunctions.py index f5d77d72..38bc7099 100644 --- a/users/accountFunctions.py +++ b/users/accountFunctions.py @@ -28,12 +28,18 @@ def store_token(token_type, user, email=None): token_store.save() return uid, token -def verify_token(token_type, uid, token): +def verify_token(token_type, uid, token, hold_verification=False): try: token_store = TokenStore.objects.get(token_type=token_type, uid=uid, token=token) if token_store.expires > timezone.now() and not token_store.verified and token_store.token_type == token_type and token_store.uid == uid and token_store.token == token: + + if hold_verification: + return token_store token_store.verified = True - UserProfile.objects.filter(user=token_store.user).update(email_verified=True) + + if token_type == "verifyemail": + UserProfile.objects.filter(user=token_store.user).update(email_verified=True) + token_store.save() return token_store diff --git a/users/forms.py b/users/forms.py index e618cec8..016ad6bb 100644 --- a/users/forms.py +++ b/users/forms.py @@ -16,109 +16,221 @@ from .mail_send import send_email class RegisterForm(forms.Form): - username = forms.CharField(label='Username', max_length=30, min_length=4) - email = forms.EmailField(label='Email') - password1 = forms.CharField(label='Password', widget=forms.PasswordInput, min_length=8) - password2 = forms.CharField(label='Password (again)', widget=forms.PasswordInput, min_length=8) - captcha = forms.CharField(label='Captcha', max_length=6) + username = forms.CharField(label="Username", max_length=30, min_length=4) + email = forms.EmailField(label="Email") + password1 = forms.CharField( + label="Password", widget=forms.PasswordInput, min_length=8 + ) + password2 = forms.CharField( + label="Password (again)", widget=forms.PasswordInput, min_length=8 + ) + captcha = forms.CharField(label="Captcha", max_length=6) expected_captcha = None protected_usernames = [ - 'admin', - 'administrator', - 'root', - 'thatcomputerscientist', - 'skippy', - 'system', - 'test', - 'user', - 'webmaster', - 'www', - 'postmaster', - 'hostmaster', - 'info', - 'support', - 'anonymous', - 'guest', - 'nobody', - 'someone', - 'moderator', - 'moderators', - 'mods', - 'crvs' + "admin", + "administrator", + "root", + "thatcomputerscientist", + "skippy", + "system", + "test", + "user", + "webmaster", + "www", + "postmaster", + "hostmaster", + "info", + "support", + "anonymous", + "guest", + "nobody", + "someone", + "moderator", + "moderators", + "mods", + "crvs", ] - allowed_chars = string.ascii_letters + string.digits + allowed_chars = string.ascii_letters + string.digits def __init__(self, *args, **kwargs): - if 'expected_captcha' in kwargs: - self.expected_captcha = kwargs.pop('expected_captcha') + if "expected_captcha" in kwargs: + self.expected_captcha = kwargs.pop("expected_captcha") super().__init__(*args, **kwargs) def clean(self): cleaned_data = super().clean() - password1 = cleaned_data.get('password1') - password2 = cleaned_data.get('password2') - captcha = cleaned_data.get('captcha') + password1 = cleaned_data.get("password1") + password2 = cleaned_data.get("password2") + captcha = cleaned_data.get("captcha") if password1 and password2: if password1 != password2: - raise forms.ValidationError('Passwords do not match.') + raise forms.ValidationError("Passwords do not match.") if len(password1) < 8: - raise forms.ValidationError('Password must be at least 8 characters long.') + raise forms.ValidationError("Password must be at least 8 characters long.") if str.lower(captcha) != str.lower(self.expected_captcha): - raise forms.ValidationError('Captcha does not match.') - if User.objects.filter(username=cleaned_data.get('username')).exists(): - raise forms.ValidationError('Username not available. Please choose another.') - if cleaned_data.get('username').lower() in self.protected_usernames: - raise forms.ValidationError('Username not available. Please choose another.') - for char in cleaned_data.get('username'): + raise forms.ValidationError("Captcha does not match.") + if User.objects.filter(username=cleaned_data.get("username")).exists(): + raise forms.ValidationError( + "Username not available. Please choose another." + ) + if cleaned_data.get("username").lower() in self.protected_usernames: + raise forms.ValidationError( + "Username not available. Please choose another." + ) + for char in cleaned_data.get("username"): if char not in self.allowed_chars: - raise forms.ValidationError('Username contains invalid characters. Only A-Z, a-z, and 0-9 are allowed.') - if User.objects.filter(email=cleaned_data.get('email')).exists(): - raise forms.ValidationError('Email already exists. Please login if this account is yours.') + raise forms.ValidationError( + "Username contains invalid characters. Only A-Z, a-z, and 0-9 are allowed." + ) + if User.objects.filter(email=cleaned_data.get("email")).exists(): + raise forms.ValidationError( + "Email already exists. Please login if this account is yours." + ) return cleaned_data def save(self, request): user = User.objects.create_user( - username=self.cleaned_data.get('username').lower(), - email=self.cleaned_data.get('email').lower(), - password=self.cleaned_data.get('password1'), + username=self.cleaned_data.get("username").lower(), + email=self.cleaned_data.get("email").lower(), + password=self.cleaned_data.get("password1"), ) user.save() user_profile = UserProfile.objects.create(user=user) avatar_dir = choice(list(avatar_list().keys())) avatar_file = choice(avatar_list()[avatar_dir]) - user_profile.avatar_url = avatar_dir + '/' + avatar_file.replace('.gif', '') + user_profile.avatar_url = avatar_dir + "/" + avatar_file.replace(".gif", "") user_profile.save() - uid, token = store_token(token_type='verifyemail', user=user, email=user.email) + uid, token = store_token(token_type="verifyemail", user=user, email=user.email) # Send verification email - subject = 'Verify your email address' - message = render_to_string('verification_email.html', { - 'user': user.username if user.first_name is None else user.first_name, - 'site_name': 'Shifoo', - 'uid': uid, - 'token': token, - 'protocol': 'https://' if request.is_secure() else 'http://', - 'domain': request.get_host(), - }) - message = strip_tags(message) + subject = "Verify your email address" + message = render_to_string( + "verification_email.html", + { + "user": user.username if user.first_name is None else user.first_name, + "site_name": "Shifoo", + "uid": uid, + "token": token, + "protocol": "https://" if request.is_secure() else "http://", + "domain": request.get_host(), + }, + ) + # message = strip_tags(message) # send_mail(subject, message, 'Shifoo <' + settings.EMAIL_HOST_USER + '>', [user.email], fail_silently=False) - if (send_email(sender='[email protected]', sender_name='Shifoo', recipient=user.email, subject=subject, body_html=message, body_text=message)): + if send_email( + sender="[email protected]", + sender_name="Shifoo", + recipient=user.email, + subject=subject, + body_html=message, + body_text=message, + ): return user else: return user -class UpdateUserDetailsForm(forms.Form): - first_name = forms.CharField(label='First name', max_length=30, required=False, widget=forms.TextInput(attrs={'placeholder': 'First name'})) - last_name = forms.CharField(label='Last name', max_length=30, required=False, widget=forms.TextInput(attrs={'placeholder': 'Last name'})) - location = forms.CharField(label='Location', max_length=30, required=False, widget=forms.TextInput(attrs={'placeholder': 'Location'})) - bio = forms.CharField(label='Bio', max_length=500, required=False, widget=forms.Textarea(attrs={'placeholder': 'Bio'})) - is_public = forms.ChoiceField(label='Activity Visibility', choices=((True, 'Public'), (False, 'Private')), widget=forms.RadioSelect) - email_public = forms.ChoiceField(label='Email Visibility', choices=((True, 'Public'), (False, 'Private')), widget=forms.RadioSelect) +class ForgotPasswordForm(forms.Form): + email = forms.EmailField(label="Email", required=True) + + def clean(self): + cleaned_data = super().clean() + return cleaned_data + + def save(self, request): + email = self.cleaned_data.get("email") + user = User.objects.get(email=email) + uid, token = store_token( + token_type="resetpassword", user=user, email=user.email + ) + subject = "Reset your password" + message = render_to_string( + "reset_password_email.html", + { + "user": user.username if user.first_name is None else user.first_name, + "site_name": "Shifoo", + "uid": uid, + "token": token, + "protocol": "https://" if request.is_secure() else "http://", + "domain": request.get_host(), + }, + ) + # message = strip_tags(message) + if send_email( + sender="[email protected]", + sender_name="Shifoo", + recipient=user.email, + subject=subject, + body_html=message, + body_text=message, + ): + return user + else: + raise forms.ValidationError("Failed to send email.") + +class ResetPasswordForm(forms.Form): + password1 = forms.CharField( + label="New Password", widget=forms.PasswordInput, min_length=8 + ) + password2 = forms.CharField( + label="New Password (again)", widget=forms.PasswordInput, min_length=8 + ) + + def clean(self): + cleaned_data = super().clean() + password1 = cleaned_data.get("password1") + password2 = cleaned_data.get("password2") + if password1 and password2: + if password1 != password2: + raise forms.ValidationError("Passwords do not match.") + if len(password1) < 8: + raise forms.ValidationError("Password must be at least 8 characters long.") + return cleaned_data + + def save(self, user): + user.set_password(self.cleaned_data.get("password1")) + user.save() + return user + +class UpdateUserDetailsForm(forms.Form): + first_name = forms.CharField( + label="First name", + max_length=30, + required=False, + widget=forms.TextInput(attrs={"placeholder": "First name"}), + ) + last_name = forms.CharField( + label="Last name", + max_length=30, + required=False, + widget=forms.TextInput(attrs={"placeholder": "Last name"}), + ) + location = forms.CharField( + label="Location", + max_length=30, + required=False, + widget=forms.TextInput(attrs={"placeholder": "Location"}), + ) + bio = forms.CharField( + label="Bio", + max_length=500, + required=False, + widget=forms.Textarea(attrs={"placeholder": "Bio"}), + ) + is_public = forms.ChoiceField( + label="Activity Visibility", + choices=((True, "Public"), (False, "Private")), + widget=forms.RadioSelect, + ) + email_public = forms.ChoiceField( + label="Email Visibility", + choices=((True, "Public"), (False, "Private")), + widget=forms.RadioSelect, + ) def __init__(self, *args, **kwargs): - self.user = kwargs.pop('user') + self.user = kwargs.pop("user") super().__init__(*args, **kwargs) def clean(self): @@ -126,15 +238,15 @@ class UpdateUserDetailsForm(forms.Form): return cleaned_data def save(self): - self.user.first_name = self.cleaned_data.get('first_name') - self.user.last_name = self.cleaned_data.get('last_name') + self.user.first_name = self.cleaned_data.get("first_name") + self.user.last_name = self.cleaned_data.get("last_name") self.user.save() user_profile = UserProfile.objects.get(user=self.user) - user_profile.location = self.cleaned_data.get('location') - user_profile.bio = self.cleaned_data.get('bio') - user_profile.is_public = self.cleaned_data.get('is_public') - user_profile.email_public = self.cleaned_data.get('email_public') + user_profile.location = self.cleaned_data.get("location") + user_profile.bio = self.cleaned_data.get("bio") + user_profile.is_public = self.cleaned_data.get("is_public") + user_profile.email_public = self.cleaned_data.get("email_public") user_profile.save() - return (self.user, user_profile)
\ No newline at end of file + return (self.user, user_profile) diff --git a/users/mail_send.py b/users/mail_send.py index be167eb1..df837a32 100644 --- a/users/mail_send.py +++ b/users/mail_send.py @@ -13,51 +13,55 @@ def send_email(sender, sender_name, recipient, subject, body_html, body_text): # this is the approved sender email SENDER = sender SENDERNAME = sender_name - + # Replace [email protected] with a "To" address. If your account # is still in the sandbox, this address must be verified. RECIPIENT = recipient - + # Replace the USERNAME_SMTP value with your Email Delivery SMTP username. USERNAME_SMTP = settings.USERNAME_SMTP - + # Put the PASSWORD value from your Email Delivery SMTP password into the following file. PASSWORD_SMTP = settings.PASSWORD_SMTP - + # If you're using Email Delivery in a different region, replace the HOST value with an appropriate SMTP endpoint. # Use port 25 or 587 to connect to the SMTP endpoint. HOST = settings.EMAIL_HOST PORT = settings.EMAIL_PORT - + # The subject line of the email. SUBJECT = subject - + # The email body for recipients with non-HTML email clients. BODY_TEXT = body_text - + # The HTML body of the email. BODY_HTML = body_html # create message container msg = EmailMessage() - msg['Subject'] = SUBJECT - msg['From'] = email.utils.formataddr((SENDERNAME, SENDER)) - msg['To'] = RECIPIENT + msg["Subject"] = SUBJECT + msg["From"] = email.utils.formataddr((SENDERNAME, SENDER)) + msg["To"] = RECIPIENT # make the message multi-part alternative, making the content the first part - msg.add_alternative(BODY_TEXT, subtype='text') + msg.add_alternative(BODY_TEXT, subtype="text") # this adds the additional part to the message # According to RFC 2046, the last part of a multipart message, in this case # the HTML message, is best and preferred. - msg.add_alternative(BODY_HTML, subtype='html') + msg.add_alternative(BODY_HTML, subtype="html") # Try to send the message. - try: + try: server = smtplib.SMTP(HOST, PORT) server.ehlo() # most python runtimes default to a set of trusted public CAs that will include the CA used by OCI Email Delivery. # However, on platforms lacking that default (or with an outdated set of CAs), customers may need to provide a capath that includes our public CA. - server.starttls(context=ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=None, capath=None)) + server.starttls( + context=ssl.create_default_context( + purpose=ssl.Purpose.SERVER_AUTH, cafile=None, capath=None + ) + ) # smtplib docs recommend calling ehlo() before & after starttls() server.ehlo() server.login(USERNAME_SMTP, PASSWORD_SMTP) @@ -68,4 +72,4 @@ def send_email(sender, sender_name, recipient, subject, body_html, body_text): except Exception as e: return e else: - return True
\ No newline at end of file + return True diff --git a/users/templates/email_change_verification_email.html b/users/templates/email_change_verification_email.html index 3db0a9d6..f6f9b127 100644 --- a/users/templates/email_change_verification_email.html +++ b/users/templates/email_change_verification_email.html @@ -1,12 +1,27 @@ -{% autoescape off %} -Hi {{ user }}, +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Change Email</title> + </head> + <body> + <h3>Change Your Current Email</h3> + <p>Hi {{ user }},</p> + <p> + We received a request to change your email address on {{ site_name }}. To + verify and change your email address, please click the link below. + </p> + <a + href="{{ protocol }}{{ domain }}{% url 'users:changeemail' 'changeemail' uid token %}" + >Change Email</a + > + <p>If the above link does not work, copy and paste the URL below into your browser:</p> + <a href="{{ protocol }}{{ domain }}{% url 'users:changeemail' 'changeemail' uid token %}">{{ protocol }}{{ domain }}{% url 'users:changeemail' 'changeemail' uid token %}</a> -We received a request to change you email address on {{ site_name }}. To verify and change your email address, please click the link below. -{{ protocol }}{{ domain }}{% url 'users:changeemail' 'changeemail' uid token %} - -Please ignore this email if you did not make this request. - -Thanks, -{{ site_name }} Team - -{% endautoescape %}
\ No newline at end of file + <p>Please ignore this email if you did not make this request.</p> + <p>Thanks,</p> + <p>Bobby from {{ site_name }}</p> + </body> +</html> diff --git a/users/templates/reset_password_email.html b/users/templates/reset_password_email.html new file mode 100644 index 00000000..f9651a13 --- /dev/null +++ b/users/templates/reset_password_email.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Reset Password</title> + </head> + <body> + <h3>Reset Your Current Password</h3> + <p>Hi {{ user }},</p> + <p> + We received a request to reset your password on {{ site_name }}. To reset + your password, please click the link below. + </p> + <a href="{{ protocol }}{{ domain }}{% url 'blog:resetpassword' uid token %}" + >Reset Password</a + > + <p>If the above link does not work, copy and paste the URL below into your browser:</p> + <a href="{{ protocol }}{{ domain }}{% url 'blog:resetpassword' uid token %}">{{ protocol }}{{ domain }}{% url 'blog:resetpassword' uid token %}</a> + <p>Please ignore this email if you did not make this request.</p> + <p>Thanks,</p> + <p>Bobby from {{ site_name }}</p> + </body> +</html> diff --git a/users/templates/verification_email.html b/users/templates/verification_email.html index cd85ab38..06e91a79 100644 --- a/users/templates/verification_email.html +++ b/users/templates/verification_email.html @@ -1,10 +1,26 @@ -{% autoescape off %} -Hi {{ user }}, - -Thanks for registering an account on {{ site_name }}. To verify your email address, please click the link below. -{{ protocol }}{{ domain }}{% url 'users:changeemail' 'verifyemail' uid token %} - -Thanks, -{{ site_name }} Team -{% endautoescape %} - +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Verify Email</title> + </head> + <body> + <h1>Verify Your New Account</h1> + <p>Hi {{ user }},</p> + <p> + Thanks for registering an account on {{ site_name }}. To verify your email + address, please click the link below. + </p> + <a + href="{{ protocol }}{{ domain }}{% url 'users:changeemail' 'verifyemail' uid token %}" + >Verify Email</a + > + <p>If the above link does not work, copy and paste the URL below into your browser:</p> + <a href="{{ protocol }}{{ domain }}{% url 'users:changeemail' 'verifyemail' uid token %}">{{ protocol }}{{ domain }}{% url 'users:changeemail' 'verifyemail' uid token %}</a> + <p>Please ignore this email if you did not make this request.</p> + <p>Thanks,</p> + <p>Bobby from {{ site_name }}</p> + </body> +</html> diff --git a/users/urls.py b/users/urls.py index 98dff57c..b7081e42 100644 --- a/users/urls.py +++ b/users/urls.py @@ -3,22 +3,29 @@ from django.urls import path from . import views -app_name = 'users' +app_name = "users" urlpatterns = [ - path('/login', views.login_user, name='login'), - path('/logout', views.logout_user, name='logout'), - path('/update', views.update_user, name='update'), - path('/changepassword', views.change_password, name='changepassword'), - path('/sendchangeuseremail', views.send_change_user_email, name='sendchangeuseremail'), - path('/sendverificationemail', views.send_verification_email, name='sendverificationemail'), - path('/updateavatar', views.update_avatar, name='updateavatar'), - path('/updateblinkies', views.update_blinkie, name='updateblinkie'), - path('/delete', views.delete_user, name='delete'), - path('/<mode>/<uid>/<token>', views.verify_email, name='verifyemail'), - path('/<mode>/<uid>/<token>', views.verify_email, name='changeemail'), + path("/login", views.login_user, name="login"), + path("/logout", views.logout_user, name="logout"), + path("/update", views.update_user, name="update"), + path("/changepassword", views.change_password, name="changepassword"), + path( + "/sendchangeuseremail", views.send_change_user_email, name="sendchangeuseremail" + ), + path( + "/sendverificationemail", + views.send_verification_email, + name="sendverificationemail", + ), + path("/updateavatar", views.update_avatar, name="updateavatar"), + path("/updateblinkies", views.update_blinkie, name="updateblinkie"), + path("/delete", views.delete_user, name="delete"), + path("/<mode>/<uid>/<token>", views.verify_email, name="verifyemail"), + path("/<mode>/<uid>/<token>", views.verify_email, name="changeemail"), + path("/resetpassword/<uid>/<token>", views.reset_password, name="resetpassword"), ] # Configure Admin Site -admin.site.site_header = 'Shifoo Administation' -admin.site.site_title = 'Shifoo' -admin.site.index_title = 'Administration Area' +admin.site.site_header = "Shifoo Administation" +admin.site.site_title = "Shifoo" +admin.site.index_title = "Administration Area" diff --git a/users/views.py b/users/views.py index 7c166911..5dba135b 100644 --- a/users/views.py +++ b/users/views.py @@ -1,6 +1,5 @@ from django.contrib import messages -from django.contrib.auth import (authenticate, login, logout, - update_session_auth_hash) +from django.contrib.auth import authenticate, login, logout, update_session_auth_hash from django.contrib.auth.models import User from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import redirect, reverse @@ -16,14 +15,14 @@ from .models import UserProfile # Create your views here. def login_user(request): # pass - next = request.POST.get('next', 'blog:home') - username = request.POST['username'] - password = request.POST['password'] - if username == '' or password == '' or username is None or password is None: + next = request.POST.get("next", "blog:home") + username = request.POST["username"] + password = request.POST["password"] + if username == "" or password == "" or username is None or password is None: # required fields are empty - messages.error(request, 'RFEERR', extra_tags='loginError') - return HttpResponseRedirect(next + '?username=' + username) - else: + messages.error(request, "RFEERR", extra_tags="loginError") + return HttpResponseRedirect(next + "?username=" + username) + else: # check if email is verified user = authenticate(request, username=username, password=password) if user is not None: @@ -37,195 +36,265 @@ def login_user(request): return HttpResponseRedirect(next) else: # email not verified - messages.error(request, 'ENVERR', extra_tags='loginError') - return HttpResponseRedirect(next + '?username=' + username) + messages.error(request, "ENVERR", extra_tags="loginError") + return HttpResponseRedirect(next + "?username=" + username) else: # invalid credentials - messages.error(request, 'IUOPERR', extra_tags='loginError') - return HttpResponseRedirect(next + '?username=' + username) + messages.error(request, "IUOPERR", extra_tags="loginError") + return HttpResponseRedirect(next + "?username=" + username) + def logout_user(request): logout(request) - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) + def update_user(request): user = request.user if user is not None: - if request.method == 'POST': + if request.method == "POST": form = UpdateUserDetailsForm(request.POST, user=user) if form.is_valid(): form.save() - messages.success(request, 'Profile was successfully updated!') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.success(request, "Profile was successfully updated!") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'Unable to update profile! Please try again later.') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.error( + request, "Unable to update profile! Please try again later." + ) + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'You must be logged in to update your profile!') - return redirect('blog:home') + messages.error(request, "You must be logged in to update your profile!") + return redirect("blog:home") + def delete_user(request): user = request.user if user is not None: - if request.method == 'POST': - password = request.POST['password'] + if request.method == "POST": + password = request.POST["password"] if user.check_password(password): # delete user, all comments, user profile details, and all posts user.delete() - messages.success(request, 'Your account was successfully deleted!') - return redirect('blog:home') + messages.success(request, "Your account was successfully deleted!") + return redirect("blog:home") else: - messages.error(request, 'Incorrect password!') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.error(request, "Incorrect password!") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'Unable to delete account! Please try again later.') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.error(request, "Unable to delete account! Please try again later.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'You must be logged in to delete your account!') - return redirect('blog:home') + messages.error(request, "You must be logged in to delete your account!") + return redirect("blog:home") + def update_avatar(request): user = request.user if user is not None: - if request.method == 'POST': + if request.method == "POST": user_profile = UserProfile.objects.get(user=user) - user_profile.avatar_url = request.POST['avatar'] + user_profile.avatar_url = request.POST["avatar"] user_profile.save() - messages.success(request, 'Avatar was successfully updated!') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.success(request, "Avatar was successfully updated!") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'Unable to update avatar! Please try again later.') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.error(request, "Unable to update avatar! Please try again later.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'You must be logged in to update your avatar!') - return redirect('blog:home') + messages.error(request, "You must be logged in to update your avatar!") + return redirect("blog:home") + def update_blinkie(request): - user = request.user + user = request.user if user is not None: - if request.method == 'POST': + if request.method == "POST": user_profile = UserProfile.objects.get(user=user) - user_profile.blinkie_url = request.POST['blinkie'] + user_profile.blinkie_url = request.POST["blinkie"] user_profile.save() - messages.success(request, 'Blinkie was successfully updated!') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.success(request, "Blinkie was successfully updated!") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'Unable to update blinkie! Please try again later.') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.error(request, "Unable to update blinkie! Please try again later.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'You must be logged in to update your blinkie!') - return redirect('blog:home') - + messages.error(request, "You must be logged in to update your blinkie!") + return redirect("blog:home") + + def change_password(request): username = request.user - old_password = request.POST['oldPassword'] - new_password = request.POST['newPassword'] - confirm_password = request.POST['confirmPassword'] + old_password = request.POST["oldPassword"] + new_password = request.POST["newPassword"] + confirm_password = request.POST["confirmPassword"] if username is not None: user = User.objects.get(username=username) if user.check_password(old_password): if new_password == confirm_password: if len(new_password) < 8: - messages.error(request, 'The new password must be at least 8 characters long!') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.error( + request, "The new password must be at least 8 characters long!" + ) + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) user.set_password(new_password) user.save() update_session_auth_hash(request, user) - messages.success(request, 'Password was successfully changed!') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.success(request, "Password was successfully changed!") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'The new password and confirmation password do not match!') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.error( + request, "The new password and confirmation password do not match!" + ) + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'Old password is incorrect!') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.error(request, "Old password is incorrect!") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'Unable to change password! Please try again later.') - return redirect('blog:home') + messages.error(request, "Unable to change password! Please try again later.") + return redirect("blog:home") + def send_change_user_email(request): user = request.user - new_email = request.POST['email'] + new_email = request.POST["email"] if user is not None: # Check if the new and the old email are the same if user.email == new_email: - messages.error(request, 'New email is the same as the old one!') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.error(request, "New email is the same as the old one!") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) # check if email is already in use if User.objects.filter(email=new_email).exists(): - messages.error(request, 'Email is already in use!') + messages.error(request, "Email is already in use!") # Redirect to referrer - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) # Send verification email - subject = 'Verify your email address' - uid, token = store_token(token_type='changeemail', user=user, email=new_email) - - message = render_to_string('email_change_verification_email.html', { - 'user': user.username if user.first_name is None else user.first_name, - 'site_name': 'Shifoo', - 'uid': uid, - 'token': token, - 'protocol': request.scheme + '://', - 'domain': request.get_host(), - }) - message = strip_tags(message) + subject = "Change your email address" + uid, token = store_token(token_type="changeemail", user=user, email=new_email) + + message = render_to_string( + "email_change_verification_email.html", + { + "user": user.username if user.first_name is None else user.first_name, + "site_name": "Shifoo", + "uid": uid, + "token": token, + "protocol": request.scheme + "://", + "domain": request.get_host(), + }, + ) + # message = strip_tags(message) # send_mail(subject, message, 'That Computer Scientist <' + settings.EMAIL_HOST_USER + '>', [new_email]) - if (send_email(sender='[email protected]', sender_name='Shifoo', recipient=new_email, subject=subject, body_html=message, body_text=message)): - messages.success(request, 'Verification email was sent! Please check your email.') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + if send_email( + sender="[email protected]", + sender_name="Shifoo", + recipient=new_email, + subject=subject, + body_html=message, + body_text=message, + ): + messages.success( + request, "Verification email was sent! Please check your email." + ) + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'Unable to change email! Please try again later.') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) - + messages.error(request, "Unable to change email! Please try again later.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) + else: - messages.error(request, 'Unable to change email! Please try again later.') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) - + messages.error(request, "Unable to change email! Please try again later.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) + + def send_verification_email(request): # this is a post only view - if request.method == 'POST': - username = request.POST.get('username') - subject = 'Verify your email address' + if request.method == "POST": + username = request.POST.get("username") + subject = "Verify your email address" user = User.objects.get(username=username) - uid, token = store_token(token_type='verifyemail', user=user, email=user.email) - - message = render_to_string('verification_email.html', { - 'user': user.username if user.first_name is None else user.first_name, - 'site_name': 'Shifoo', - 'uid': uid, - 'token': token, - 'protocol': 'https://' if request.is_secure() else 'http://', - 'domain': request.get_host(), - }) - message = strip_tags(message) - if (send_email(sender='[email protected]', sender_name='Shifoo', recipient=user.email, subject=subject, body_html=message, body_text=message)): - messages.success(request, 'VESENT', extra_tags='loginError') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + uid, token = store_token(token_type="verifyemail", user=user, email=user.email) + + message = render_to_string( + "verification_email.html", + { + "user": user.username if user.first_name is None else user.first_name, + "site_name": "Shifoo", + "uid": uid, + "token": token, + "protocol": "https://" if request.is_secure() else "http://", + "domain": request.get_host(), + }, + ) + # message = strip_tags(message) + if send_email( + sender="[email protected]", + sender_name="Shifoo", + recipient=user.email, + subject=subject, + body_html=message, + body_text=message, + ): + messages.success(request, "VESENT", extra_tags="loginError") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'VESENDERR', extra_tags='loginError') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.error(request, "VESENDERR", extra_tags="loginError") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) else: - messages.error(request, 'VESENDERR', extra_tags='loginError') - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.error(request, "VESENDERR", extra_tags="loginError") + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) + def verify_email(request, mode, uid, token): token_object = verify_token(mode, uid, token) - redirect_to = reverse('blog:account') + '?tab=email' if mode == 'changeemail' else 'blog:home' - success_message = 'Email was successfully changed!' if mode == 'changeemail' else 'VESUCCESS' - error_message = 'Unable to verify email! Please try again later.' + redirect_to = ( + reverse("blog:account") + "?tab=email" if mode == "changeemail" else "blog:home" + ) + success_message = ( + "Email was successfully changed!" if mode == "changeemail" else "VESUCCESS" + ) + error_message = "Unable to verify email! Please try again later." if token_object is not None and token_object.verified: user = User.objects.get(pk=token_object.user_id) user.email = token_object.email user.save() token_object.delete() - messages.success(request, success_message, extra_tags='loginError' if mode == 'verifyemail' else '') + messages.success( + request, + success_message, + extra_tags="loginError" if mode == "verifyemail" else "", + ) return redirect(redirect_to) else: messages.error(request, error_message) return redirect(redirect_to) -
\ No newline at end of file + + +def reset_password(request, uid, token): + mode = "resetpassword" + token_object = verify_token(mode, uid, token) + + # Token is not verified yet, but confirmed that it belongs to the user + # Now we send a form for the user to reset their password + if token_object is not None and token_object.verified: + print(token_object.user_id) + # redirect to forgotpassword/reset?uid=uid&token=token + return HttpResponseRedirect( + reverse("blog:resetpassword") + + "?uid=" + + token_object.user_id + + "&token=" + + token + ) + else: + # Token is invalid + messages.error( + request, + "Unable to reset password! Please try again later.", + extra_tags="passwordReset", + ) + return redirect("blog:forgotpassword") |
