diff --git a/project/accounts/authentication.py b/project/accounts/authentication.py
index 1219f8793..5d71efe3f 100644
--- a/project/accounts/authentication.py
+++ b/project/accounts/authentication.py
@@ -1,29 +1,17 @@
from django.conf import settings
-from django.contrib.auth import authenticate, logout, login
from django.contrib.auth import get_user_model
from django.contrib.auth.tokens import PasswordResetTokenGenerator
-from django.contrib.sites.shortcuts import get_current_site
-from django.http import (
- JsonResponse,
- HttpResponse,
- HttpResponseServerError,
- HttpResponseRedirect,
- HttpResponseBadRequest,
-)
-from django.shortcuts import get_object_or_404
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.http import int_to_base36
-from django.views.decorators.debug import sensitive_post_parameters
from django.template.loader import render_to_string
from accounts.utils import send_email
from accounts.models import Profile
-from .forms import ProfileRegistrationForm, PasswordResetForm, RecoverUserForm
-from core.custom_decorators import require_post_params
+from .forms import PasswordResetForm, RecoverUserForm
User = get_user_model()
@@ -76,103 +64,6 @@ def send_activation_email(user, domain):
)
-@sensitive_post_parameters("password")
-@require_post_params(params=["username", "password"])
-def cw_login(request):
- """
- USAGE:
- This is used to authenticate the user and log them in.
-
- :returns (200, ok) (400, Inactive User) (400, Invalid username or password)
- """
-
- username = request.POST.get("username", "")
- password = request.POST.get("password", "")
- remember = request.POST.get("remember", "false")
-
- user = authenticate(username=username, password=password)
- if user is not None:
- if remember == "false":
- request.session.set_expiry(0)
-
- login(request, user)
-
- if user.is_active:
-
- account = get_object_or_404(Profile, user=user)
- request.session["login_user_firstname"] = account.first_name
- request.session["login_user_image"] = account.profile_image_thumb_url
-
- return HttpResponse()
- else:
- response = {"message": "Inactive user", "error": "USER_INACTIVE"}
- return JsonResponse(response, status=400)
- else:
- # Return an 'invalid login' error message.
- response = {"message": "Invalid username or password", "error": "INVALID_LOGIN"}
- return JsonResponse(response, status=400)
-
-
-def cw_logout(request):
- """Use this to logout the current user """
-
- logout(request)
- return HttpResponseRedirect("/")
-
-
-@sensitive_post_parameters("password")
-@require_post_params(params=["username", "password", "email"])
-def cw_register(request):
- """
- USAGE:
- This is used to register new users to civiwiki
-
- PROCESS:
- - Gets new users username and password
- - Sets the user to active
- - Then creates a new user verification link and emails it to the new user
-
- Return:
- (200, ok) (500, Internal Error)
- """
- form = ProfileRegistrationForm(request.POST or None)
- if request.method == "POST":
- # Form Validation
- if form.is_valid():
- username = form.clean_username()
- password = form.clean_password()
- email = form.clean_email()
-
- # Create a New Profile
- try:
- user = User.objects.create_user(username, email, password)
-
- account = Profile(user=user)
- account.save()
-
- user.is_active = True
- user.save()
-
- domain = get_current_site(request).domain
-
- send_activation_email(user, domain)
-
- login(request, user)
- return HttpResponse()
-
- except Exception as e:
- return HttpResponseServerError(reason=str(e))
-
- else:
- response = {
- "success": False,
- "errors": [error[0] for error in form.errors.values()],
- }
- return JsonResponse(response, status=400)
- else:
- return HttpResponseBadRequest(reason="POST Method Required")
-
-
def activate_view(request, uidb64, token):
"""
This shows different views to the user when they are verifying
diff --git a/project/accounts/forms.py b/project/accounts/forms.py
index b3a879604..383441953 100644
--- a/project/accounts/forms.py
+++ b/project/accounts/forms.py
@@ -2,13 +2,11 @@
from django.core.files.images import get_image_dimensions
from django import forms
from django.contrib.auth.forms import (
- UserCreationForm,
SetPasswordForm,
PasswordResetForm as AuthRecoverUserForm,
)
from django.forms.models import ModelForm
from django.contrib.auth import get_user_model
-from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
from django.utils.encoding import force_bytes
@@ -24,9 +22,9 @@
User = get_user_model()
-class ProfileRegistrationForm(ModelForm):
+class UserRegistrationForm(ModelForm):
"""
- This class is used to register new account in Civiwiki
+ This class is used to register a new user in Civiwiki
Components:
- Email - from registration form
diff --git a/project/accounts/templates/accounts/login.html b/project/accounts/templates/accounts/login.html
deleted file mode 100644
index 6cc1b08d3..000000000
--- a/project/accounts/templates/accounts/login.html
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- {% load static %}
- {% include "base/links.html" %}
- {% include "base/less_headers/login_less.html" %}
- CiviWiki
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/project/accounts/tests.py b/project/accounts/tests.py
deleted file mode 100644
index 7ce503c2d..000000000
--- a/project/accounts/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/project/accounts/tests/__init__.py b/project/accounts/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/project/accounts/tests/test_forms.py b/project/accounts/tests/test_forms.py
new file mode 100644
index 000000000..77bc14bbc
--- /dev/null
+++ b/project/accounts/tests/test_forms.py
@@ -0,0 +1,65 @@
+from django.contrib.auth import get_user_model
+from django.test import TestCase
+from accounts.forms import UserRegistrationForm
+
+
+class UserRegistrationFormTest(TestCase):
+ """A class to test user registration form"""
+
+ def setUp(self) -> None:
+ self.data = {
+ "username": "testuser",
+ "email": "test@test.com",
+ "password": "password123"
+ }
+
+ def test_user_creation_form_with_success(self):
+ """Whether form works as expected for the valid inputs"""
+
+ form = UserRegistrationForm(self.data)
+ self.assertTrue(form.is_valid())
+ self.assertEqual(form.errors, {})
+ form.save()
+ self.assertTrue(get_user_model().objects.count(), 1)
+
+ def test_form_is_unsuccessful_for_short_password(self):
+ """Whether a user does not have a short password"""
+
+ self.data['password'] = "123"
+ form = UserRegistrationForm(self.data)
+ self.assertFalse(form.is_valid())
+ self.assertNotEqual(form.errors, {})
+ self.assertTrue(form.has_error('password'))
+ self.assertEqual(get_user_model().objects.count(), 0)
+
+ def test_form_is_unsuccessful_for_only_digit_password(self):
+ """Whether a user does not have an only-digit password"""
+
+ self.data['password'] = "12345678"
+ form = UserRegistrationForm(self.data)
+ self.assertFalse(form.is_valid())
+ self.assertNotEqual(form.errors, {})
+ self.assertTrue(form.has_error('password'))
+ self.assertEqual(get_user_model().objects.count(), 0)
+
+ def test_form_is_unsuccessful_for_invalid_username(self):
+ """Whether a user does not have an invalid username"""
+
+ self.data['username'] = "......."
+ form = UserRegistrationForm(self.data)
+ self.assertFalse(form.is_valid())
+ self.assertNotEqual(form.errors, {})
+ self.assertTrue(form.has_error('username'))
+ self.assertEqual(get_user_model().objects.count(), 0)
+
+ def test_form_is_unsuccessful_for_existing_username_and_email(self):
+ """Whether a user does not have an existing username and email"""
+
+ form = UserRegistrationForm(self.data)
+ form.save()
+ form = UserRegistrationForm(self.data)
+ self.assertFalse(form.is_valid())
+ self.assertNotEqual(form.errors, {})
+ self.assertTrue(form.has_error('username'))
+ self.assertTrue(form.has_error('email'))
+ self.assertEqual(get_user_model().objects.count(), 1)
diff --git a/project/accounts/tests/test_models.py b/project/accounts/tests/test_models.py
new file mode 100644
index 000000000..a6004682c
--- /dev/null
+++ b/project/accounts/tests/test_models.py
@@ -0,0 +1,74 @@
+from django.contrib.auth import get_user_model
+from django.test import TestCase
+from accounts.models import Profile
+
+
+class BaseTestCase(TestCase):
+ """Base test class to set up test cases"""
+
+ def setUp(self) -> None:
+ user = get_user_model().objects.create_user(username="testuser", email="test@test.com", password="password123")
+ self.test_profile = Profile.objects.create(user=user, first_name="Test", last_name="User", about_me="About Me")
+
+
+class ProfileModelTests(BaseTestCase):
+ """A class to test Profile model"""
+
+ def test_profile_creation(self):
+ """Whether the fields of created Profile instance is correct"""
+
+ self.assertEqual(self.test_profile.first_name, "Test")
+ self.assertEqual(self.test_profile.last_name, "User")
+ self.assertEqual(self.test_profile.about_me, "About Me")
+ self.assertEqual(self.test_profile.full_name, "Test User")
+
+ def test_profile_has_default_image_url(self):
+ """Whether a profile has a default image"""
+
+ self.assertEqual(self.test_profile.profile_image_url, '/static/img/no_image_md.png')
+
+
+class ProfileManagerTests(BaseTestCase):
+ """A class to test ProfileManager"""
+
+ def test_profile_summarize_with_no_history_no_followers_no_following(self):
+ """Whether profile summarize is correct without history, followers, and followings"""
+
+ data = {
+ "username": self.test_profile.user.username,
+ "first_name": self.test_profile.first_name,
+ "last_name": self.test_profile.last_name,
+ "about_me": self.test_profile.about_me,
+ "history": [],
+ "profile_image": self.test_profile.profile_image_url,
+ "followers": [],
+ "following": [],
+ }
+ self.assertEqual(Profile.objects.summarize(self.test_profile), data)
+
+ def test_profile_chip_summarize(self):
+ """Whether profile chip summarize is correct"""
+
+ data = {
+ "username": self.test_profile.user.username,
+ "first_name": self.test_profile.first_name,
+ "last_name": self.test_profile.last_name,
+ "profile_image": self.test_profile.profile_image_url,
+ }
+ self.assertEqual(Profile.objects.chip_summarize(self.test_profile), data)
+
+ def test_profile_card_summarize(self):
+ """Whether profile card summarize is correct"""
+
+ data = {
+ "id": self.test_profile.user.id,
+ "username": self.test_profile.user.username,
+ "first_name": self.test_profile.first_name,
+ "last_name": self.test_profile.last_name,
+ "about_me": self.test_profile.about_me,
+ "profile_image": self.test_profile.profile_image_url,
+ "follow_state": False,
+ "request_profile": self.test_profile.first_name,
+ }
+ self.assertEqual(
+ Profile.objects.card_summarize(self.test_profile, Profile.objects.get(user=self.test_profile.user)), data)
diff --git a/project/accounts/tests/test_views.py b/project/accounts/tests/test_views.py
new file mode 100644
index 000000000..81a273f42
--- /dev/null
+++ b/project/accounts/tests/test_views.py
@@ -0,0 +1,95 @@
+from django.contrib.auth import get_user_model
+from django.test import TestCase
+from django.urls import reverse, resolve
+from django.contrib.auth import views as auth_views
+from accounts.models import Profile
+from accounts.views import RegisterView
+
+
+class BaseTestCase(TestCase):
+ """Base test class to set up test cases"""
+
+ def setUp(self) -> None:
+ self.user = get_user_model().objects.create_user(username="newuser",
+ email="test@test.com",
+ password="password123")
+ self.profile = Profile.objects.create(user=self.user)
+
+
+class LoginViewTests(BaseTestCase):
+ """A class to test login view"""
+
+ def setUp(self) -> None:
+ super(LoginViewTests, self).setUp()
+ url = reverse('accounts_login')
+ self.response = self.client.get(url)
+
+ def test_login_template(self):
+ """Whether login view uses the correct template"""
+
+ self.assertEqual(self.response.status_code, 200)
+ self.assertTemplateUsed(self.response, 'accounts/register/login.html')
+ self.assertTemplateNotUsed(self.response, 'accounts/login.html')
+ self.assertContains(self.response, 'Log In')
+ self.assertNotContains(self.response, 'Wrong Content!')
+
+ def test_login_url_matches_with_login_view(self):
+ """Whether login URL matches with the correct view"""
+
+ view = resolve('/login/')
+ self.assertEqual(view.func.__name__, auth_views.LoginView.__name__)
+
+ def test_the_user_with_the_correct_credentials_login(self):
+ """Whether a user with the correct credentials can login"""
+
+ self.assertTrue(self.client.login(username="newuser", password="password123"))
+
+ def test_login_view_redirects_on_success(self):
+ """Whether login view redirects to the base view after the successive try"""
+
+ response = self.client.post(reverse('accounts_login'),
+ {'username': "newuser",
+ 'password': "password123"})
+ self.assertRedirects(response, expected_url=reverse('base'), status_code=302, target_status_code=200)
+
+
+class RegisterViewTests(TestCase):
+ """A class to test register view"""
+
+ def setUp(self):
+ self.url = reverse('accounts_register')
+
+ def test_register_template(self):
+ """Whether register view uses the correct template"""
+
+ self.response = self.client.get(self.url)
+ self.assertEqual(self.response.status_code, 200)
+ self.assertTemplateUsed(self.response, 'accounts/register/register.html')
+ self.assertTemplateNotUsed(self.response, 'accounts/register.html')
+ self.assertContains(self.response, 'Register')
+ self.assertNotContains(self.response, 'Wrong Content!')
+
+ def test_register_url_matches_with_register_view(self):
+ """Whether register URL matches with the correct view"""
+
+ view = resolve('/register/')
+ self.assertEqual(view.func.__name__, RegisterView.__name__)
+
+ def test_register_view_creates_a_user_successfully(self):
+ """Whether register view creates a new user with success"""
+
+ user_count = get_user_model().objects.count()
+ self.client.post(reverse('accounts_register'),
+ {'username': "newuser",
+ "email": "newuser@email.com",
+ 'password': "password123"})
+ self.assertEqual(get_user_model().objects.count(), user_count + 1)
+
+ def test_register_view_redirects_on_success(self):
+ """Whether register view redirects to the base view after the successive try"""
+
+ response = self.client.post(reverse('accounts_register'),
+ {'username': "newuser",
+ "email": "newuser@email.com",
+ 'password': "password123"})
+ self.assertRedirects(response, expected_url=reverse('base'), status_code=302, target_status_code=200)
diff --git a/project/accounts/urls.py b/project/accounts/urls.py
index 726933769..32a955879 100644
--- a/project/accounts/urls.py
+++ b/project/accounts/urls.py
@@ -1,11 +1,17 @@
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 . import authentication
urlpatterns = [
- url(r"^login", authentication.cw_login, name="login"),
- url(r"^logout", authentication.cw_logout, name="logout"),
- url(r"^register", authentication.cw_register, name="register"),
+ path(
+ 'login/',
+ auth_views.LoginView.as_view(template_name='accounts/register/login.html'),
+ name='accounts_login',
+ ),
+ path('logout/', auth_views.LogoutView.as_view(), name='accounts_logout'),
+ path('register/', RegisterView.as_view(), name='accounts_register'),
url(
r"^activate_account/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$",
authentication.activate_view,
diff --git a/project/accounts/views.py b/project/accounts/views.py
index 18e6dd33b..09961dfbb 100644
--- a/project/accounts/views.py
+++ b/project/accounts/views.py
@@ -7,7 +7,7 @@
from django.conf import settings
from django.views.generic.edit import FormView
from django.contrib.auth import views as auth_views
-from django.contrib.auth import authenticate, login
+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
@@ -16,12 +16,13 @@
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 accounts.models import Profile
from core.custom_decorators import login_required
-from .forms import ProfileRegistrationForm, UpdateProfile
-from .models import User
+from accounts.forms import UserRegistrationForm, UpdateProfile
+
from .authentication import send_activation_email
@@ -46,7 +47,7 @@ class RegisterView(FormView):
"""
template_name = "accounts/register/register.html"
- form_class = ProfileRegistrationForm
+ form_class = UserRegistrationForm
success_url = "/"
def _create_user(self, form):
@@ -54,13 +55,8 @@ def _create_user(self, form):
password = form.cleaned_data["password"]
email = form.cleaned_data["email"]
- user = User.objects.create_user(username, email, password)
-
- account = Profile(user=user)
- account.save()
-
- user.is_active = True
- user.save()
+ user = get_user_model().objects.create_user(username, email, password)
+ Profile.objects.create(user=user)
return user
@@ -103,7 +99,7 @@ class PasswordResetCompleteView(auth_views.PasswordResetCompleteView):
@login_required
def settings_view(request):
- account = request.user.account_set.first()
+ profile = request.user.profile_set.first()
if request.method == "POST":
instance = Profile.objects.get(user=request.user)
form = UpdateProfile(
@@ -118,9 +114,9 @@ def settings_view(request):
initial={
"username": request.user.username,
"email": request.user.email,
- "first_name": account.first_name or None,
- "last_name": account.last_name or None,
- "about_me": account.about_me or None,
+ "first_name": profile.first_name or None,
+ "last_name": profile.last_name or None,
+ "about_me": profile.about_me or None,
}
)
return TemplateResponse(request, "accounts/utils/update_settings.html", {"form": form})
diff --git a/project/core/settings.py b/project/core/settings.py
index e98d231b6..c2352ba90 100644
--- a/project/core/settings.py
+++ b/project/core/settings.py
@@ -55,7 +55,6 @@
CORS_ORIGIN_ALLOW_ALL = True
ROOT_URLCONF = "core.urls"
-LOGIN_URL = "/login"
# SSL Setup
if DJANGO_HOST != "LOCALHOST":
@@ -172,11 +171,12 @@
# Custom User model
AUTH_USER_MODEL = 'accounts.User'
-APPEND_SLASH = False
-
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
+# Login Logout URLS
+LOGIN_URL = "login/"
LOGIN_REDIRECT_URL = '/'
+LOGOUT_REDIRECT_URL = '/'
AUTH_PASSWORD_VALIDATORS = [
{
diff --git a/project/core/urls.py b/project/core/urls.py
index acaee9ca5..ffef4b152 100644
--- a/project/core/urls.py
+++ b/project/core/urls.py
@@ -1,49 +1,36 @@
"""civiwiki URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
- https://docs.djangoproject.com/en/1.8/topics/http/urls/
+ https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
- 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
- 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
- 1. Add an import: from blog import urls as blog_urls
- 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls))
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
-import django.contrib.auth.views as auth_views
-from django.conf.urls import include, url
+from django.conf.urls import url
+from django.urls import path, include
from django.contrib import admin
from django.conf import settings
-from django.urls import path
from django.views.static import serve
from django.views.generic.base import RedirectView
from api import urls as api
-from accounts import urls as accounts_urls
-from accounts.views import (RegisterView, PasswordResetView, PasswordResetDoneView,
+from accounts.views import (PasswordResetView, PasswordResetDoneView,
PasswordResetConfirmView, PasswordResetCompleteView, settings_view)
from frontend_views import urls as frontend_views
-
urlpatterns = [
path("admin/", admin.site.urls),
+ path("", include('accounts.urls')),
url(r"^api/", include(api)),
- url(r"^auth/", include(accounts_urls)),
-
- # New accounts paths. These currently implement user registration/authentication in
- # parallel to the current authentication.
- path('accounts/register', RegisterView.as_view(), name='accounts_register'),
- path(
- 'accounts/login',
- auth_views.LoginView.as_view(template_name='accounts/register/login.html'),
- name='accounts_login',
- ),
-
path(
'accounts/password_reset',
PasswordResetView.as_view(),
diff --git a/project/frontend_views/urls.py b/project/frontend_views/urls.py
index 900ff5d8e..9b8319831 100644
--- a/project/frontend_views/urls.py
+++ b/project/frontend_views/urls.py
@@ -2,7 +2,6 @@
from . import views as v
urlpatterns = [
- url(r"^login$", v.login_view, name="login"),
url(r"^about$", v.about_view, name="about"),
url(r"^support_us$", v.support_us_view, name="support us"),
url(r"^howitworks$", v.how_it_works_view, name="how it works"),
diff --git a/project/frontend_views/views.py b/project/frontend_views/views.py
index c9fc73061..0d5aba503 100644
--- a/project/frontend_views/views.py
+++ b/project/frontend_views/views.py
@@ -182,14 +182,6 @@ def create_group(request):
return TemplateResponse(request, "newgroup.html", {})
-def login_view(request):
- if request.user.is_authenticated:
- if request.user.is_active:
- return HttpResponseRedirect("/")
-
- return TemplateResponse(request, "login.html", {})
-
-
def declaration(request):
return TemplateResponse(request, "declaration.html", {})
diff --git a/project/setup.cfg b/project/setup.cfg
index 42641fd6c..baee56619 100644
--- a/project/setup.cfg
+++ b/project/setup.cfg
@@ -1,4 +1,13 @@
[flake8]
-ignore = E128,E265,E261,E126,E501,E302,E262,E127,E303,E226,E231,E201,E202,E121,E203,E123,W293,W391,E122,W292,F403,E401,E131,W503,E731,E266
+ignore = E128,E265,E261,E126,E501,E302,E262,E127,E226,E231,E201,E202,E121,E203,E123,W293,W391,E122,W292,F403,E401,E131,W503,E731,E266
max-line-length = 160
exclude = api/migrations, __init__.py
+
+[coverage:run]
+omit =
+ */env/*,
+ */venv/*,
+ *tests*,
+ */migrations/*,
+ manage.py,
+ *__init__.py,
\ No newline at end of file
diff --git a/project/threads/templates/threads/partials/utils/global_nav.html b/project/threads/templates/threads/partials/utils/global_nav.html
index 99213dc84..3777628cc 100644
--- a/project/threads/templates/threads/partials/utils/global_nav.html
+++ b/project/threads/templates/threads/partials/utils/global_nav.html
@@ -29,7 +29,7 @@
My Profile
Settings
- Sign Out
+ Sign Out