Skip to content

Restructure project #1025

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 4 additions & 54 deletions project/accounts/authentication.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.template.response import TemplateResponse # TODO: move this out to views
from django.utils.crypto import salted_hmac
from django.utils.encoding import force_bytes, force_text
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.utils.http import int_to_base36
from django.template.loader import render_to_string


from accounts.utils import send_email
from accounts.models import Profile
from .forms import PasswordResetForm, RecoverUserForm


class ProfileActivationTokenGenerator(PasswordResetTokenGenerator):
"""Token Generator for Email Confirmation"""


key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"

def _make_token_with_timestamp(self, user, timestamp):
def _make_token_with_timestamp(self, user, timestamp, legacy=False):
""" Token function pulled from Django 1.11 """
ts_b36 = int_to_base36(timestamp)

Expand All @@ -36,7 +33,7 @@ def _make_token_with_timestamp(self, user, timestamp):
def send_activation_email(user, domain):
uid = urlsafe_base64_encode(force_bytes(user.pk))
token = account_activation_token.make_token(user)
base_url = "http://{domain}/auth/activate_account/{uid}/{token}/"
base_url = "http://{domain}/activate_account/{uid}/{token}/"
url_with_code = base_url.format(domain=domain, uid=uid, token=token)
# Send Email Verification Message
# TODO: Move this to string templates
Expand All @@ -61,53 +58,6 @@ def send_activation_email(user, domain):
)


def activate_view(request, uidb64, token):
"""
This shows different views to the user when they are verifying
their account based on whether they are already verified or not.
"""

User = get_user_model()

try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)

except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None

if user is not None and account_activation_token.check_token(user, token):
account = Profile.objects.get(user=user)
if account.is_verified:
redirect_link = {"href": "/", "label": "Back to Main"}
template_var = {
"title": "Email Already Verified",
"content": "You have already verified your email",
"link": redirect_link,
}
return TemplateResponse(request, "general-message.html", template_var)
else:
account.is_verified = True
account.save()

redirect_link = {"href": "/", "label": "Back to Main"}
template_var = {
"title": "Email Verification Successful",
"content": "Thank you for verifying your email with CiviWiki",
"link": redirect_link,
}
return TemplateResponse(request, "general-message.html", template_var)
else:
# invalid link
redirect_link = {"href": "/", "label": "Back to Main"}
template_var = {
"title": "Email Verification Error",
"content": "Email could not be verified",
"link": redirect_link,
}
return TemplateResponse(request, "general-message.html", template_var)


def recover_user():
"""
USAGE:
Expand Down
4 changes: 2 additions & 2 deletions project/accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,14 @@ def save(
)


class UpdateProfile(forms.ModelForm):
class ProfileEditForm(forms.ModelForm):
"""
Form for updating Profile data
"""

def __init__(self, *args, **kwargs):
readonly = kwargs.pop("readonly", False)
super(UpdateProfile, self).__init__(*args, **kwargs)
super(ProfileEditForm, self).__init__(*args, **kwargs)
if readonly:
self.disable_fields()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% autoescape off %}
To initiate the password reset process for your OpenCiviWiki Account, click the link below:

{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{{ protocol }}://{{ domain }}{% url 'accounts_password_reset_confirm' uidb64=uid token=token %}

If clicking the link above doesn't work, please copy and paste the URL in a new browser
window instead.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
To initiate the password reset process for your {{ user.get_username }} OpenCiviWiki Account,
click the link below:

{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{{ protocol }}://{{ domain }}{% url 'accounts_password_reset_confirm' uidb64=uid token=token %}

If clicking the link above doesn't work, please copy and paste the URL in a new browser
window instead.
Expand Down
74 changes: 74 additions & 0 deletions project/accounts/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,77 @@ def test_register_view_redirects_on_success(self):
"email": "[email protected]",
'password': "password123"})
self.assertRedirects(response, expected_url=reverse('base'), status_code=302, target_status_code=200)


class SettingsViewTests(BaseTestCase):
"""A class to test settings view"""

def setUp(self) -> None:
super(SettingsViewTests, self).setUp()
self.profile.first_name = "Gorkem"
self.profile.last_name = "Arslan"
self.profile.save()
self.client.login(username=self.user.username, password="password123")
self.url = reverse('accounts_settings')
self.response = self.client.get(self.url)

def test_template_name(self):
"""Whether the correct template is used"""

self.assertTemplateUsed(self.response, 'accounts/utils/update_settings.html')

def test_contains_existing_data(self):
"""Whether the existing data is available"""

self.assertContains(self.response, "Gorkem")
self.assertContains(self.response, "Arslan")

def test_anonymous_users_are_redirected_to_login_page(self):
"""Whether anonymous users are redirected to the login page"""

self.client.logout()
self.response = self.client.get(self.url)
expected_url = reverse('accounts_login') + '?next=' + reverse('accounts_settings')
self.assertRedirects(response=self.response, expected_url=expected_url,
status_code=302, target_status_code=200, msg_prefix='',
fetch_redirect_response=True)


class ProfileActivationViewTests(TestCase):
"""A class to test profile activation view"""

def setUp(self) -> None:
self.response = self.client.post(reverse('accounts_register'),
{'username': "newuser",
"email": "[email protected]",
'password': "password123"})
self.user = get_user_model().objects.get(username="newuser")
self.profile = Profile.objects.get(user=self.user)
self.activation_link = self.response.context[0]['link']

def test_activation_link(self):
"""Whether the activation link works as expected"""

self.assertFalse(self.profile.is_verified)
response = self.client.get(self.activation_link)
self.profile.refresh_from_db()
self.assertTrue(self.profile.is_verified)
self.assertTemplateUsed(response, "general-message.html")
self.assertContains(response, "Email Verification Successful")

def test_activation_link_with_a_verified_user(self):
"""Whether a verified user is welcomed by already verified page"""

self.client.get(self.activation_link)
response = self.client.get(self.activation_link)
self.assertTemplateUsed(response, "general-message.html")
self.assertContains(response, "Email Already Verified")

def test_invalid_action_link(self):
"""Whether a verified user is welcomed by verification error page"""

invalid_link = self.activation_link[:-10] + '12345/'
response = self.client.get(invalid_link)
self.assertFalse(self.profile.is_verified)
self.assertTemplateUsed(response, "general-message.html")
self.assertContains(response, "Email Verification Error")
9 changes: 3 additions & 6 deletions project/accounts/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.conf.urls import url
from django.urls import path
from django.contrib.auth import views as auth_views
from accounts.views import RegisterView
from accounts.views import RegisterView, SettingsView, ProfileActivationView
from . import authentication

urlpatterns = [
Expand All @@ -12,11 +12,8 @@
),
path('logout/', auth_views.LogoutView.as_view(), name='accounts_logout'),
path('register/', RegisterView.as_view(), name='accounts_register'),
url(
r"^activate_account/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$",
authentication.activate_view,
name="activate_account",
),
path('settings/', SettingsView.as_view(), name='accounts_settings'),
path('activate_account/<uidb64>/<token>/', ProfileActivationView.as_view(), name='accounts_activate'),
url(
r"^forgot/$",
auth_views.PasswordResetView.as_view(),
Expand Down
111 changes: 78 additions & 33 deletions project/accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,26 @@
"""

from django.conf import settings
from django.views.generic.edit import FormView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import FormView, UpdateView
from django.views import View
from django.contrib.auth import views as auth_views
from django.contrib.auth import login
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.contrib.sites.shortcuts import get_current_site
from django.utils.encoding import force_bytes
from django.utils.http import int_to_base36
from django.utils.crypto import salted_hmac
from django.utils.http import urlsafe_base64_encode
from django.urls import reverse_lazy
from django.template.response import TemplateResponse
from django.contrib.auth import get_user_model
from django.utils.encoding import force_str
from django.utils.http import urlsafe_base64_decode
from django.template.response import TemplateResponse

from accounts.models import Profile
from core.custom_decorators import login_required

from accounts.forms import UserRegistrationForm, UpdateProfile
from accounts.forms import UserRegistrationForm, ProfileEditForm

from .authentication import send_activation_email
from accounts.authentication import send_activation_email, account_activation_token


class ProfileActivationTokenGenerator(PasswordResetTokenGenerator):
Expand All @@ -35,10 +36,8 @@ def _make_token_with_timestamp(self, user, timestamp):
"""Token function pulled from Django 1.11"""
ts_b36 = int_to_base36(timestamp)

hash = salted_hmac(self.key_salt, str(user.pk) + str(timestamp)).hexdigest()[
::2
]
return "%s-%s" % (ts_b36, hash)
hash_string = salted_hmac(self.key_salt, str(user.pk) + str(timestamp)).hexdigest()[::2]
return "%s-%s" % (ts_b36, hash_string)


class RegisterView(FormView):
Expand Down Expand Up @@ -97,26 +96,72 @@ class PasswordResetCompleteView(auth_views.PasswordResetCompleteView):
template_name = "accounts/users/password_reset_complete.html"


@login_required
def settings_view(request):
profile = request.user.profile_set.first()
if request.method == "POST":
instance = Profile.objects.get(user=request.user)
form = UpdateProfile(
request.POST,
initial={"username": request.user.username, "email": request.user.email},
instance=instance,
)
if form.is_valid():
form.save()
else:
form = UpdateProfile(
initial={
"username": request.user.username,
"email": request.user.email,
"first_name": profile.first_name or None,
"last_name": profile.last_name or None,
"about_me": profile.about_me or None,
class SettingsView(LoginRequiredMixin, UpdateView):
"""A form view to edit Profile"""

login_url = 'accounts_login'
form_class = ProfileEditForm
success_url = reverse_lazy('accounts_settings')
template_name = 'accounts/utils/update_settings.html'

def get_object(self, queryset=None):
return Profile.objects.get(user=self.request.user)

def get_initial(self):
profile = Profile.objects.get(user=self.request.user)
self.initial.update({
"username": profile.user.username,
"email": profile.user.email,
"first_name": profile.first_name or None,
"last_name": profile.last_name or None,
"about_me": profile.about_me or None,
})
return super(SettingsView, self).get_initial()


class ProfileActivationView(View):
"""
This shows different views to the user when they are verifying
their account based on whether they are already verified or not.
"""

def get(self, request, uidb64, token):

User = get_user_model()
try:
uid = force_str(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)

except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None

if user is not None and account_activation_token.check_token(user, token):
profile = Profile.objects.get(user=user)
if profile.is_verified:
redirect_link = {"href": "/", "label": "Back to Main"}
template_var = {
"title": "Email Already Verified",
"content": "You have already verified your email",
"link": redirect_link,
}
return TemplateResponse(request, "general-message.html", template_var)
else:
profile.is_verified = True
profile.save()

redirect_link = {"href": "/", "label": "Back to Main"}
template_var = {
"title": "Email Verification Successful",
"content": "Thank you for verifying your email with CiviWiki",
"link": redirect_link,
}
return TemplateResponse(request, "general-message.html", template_var)
else:
# invalid link
redirect_link = {"href": "/", "label": "Back to Main"}
template_var = {
"title": "Email Verification Error",
"content": "Email could not be verified",
"link": redirect_link,
}
)
return TemplateResponse(request, "accounts/utils/update_settings.html", {"form": form})
return TemplateResponse(request, "general-message.html", template_var)
Loading