diff --git a/tracker/forms.py b/tracker/forms.py index c862b47..3ab0890 100644 --- a/tracker/forms.py +++ b/tracker/forms.py @@ -35,3 +35,10 @@ class SettingsForm(forms.ModelForm): class Meta: model = Setting fields = ('timezone', 'locale', 'duration_format', 'timestamp_rounding', 'allow_parallel_tracking') + + +class ChangePasswordForm(forms.Form): + password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control'}), + help_text='Password must be at least 8 characters long and must contain' + ' at least one letter and one number') + confirm_password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control'})) diff --git a/tracker/templates/tracker/user/account.html b/tracker/templates/tracker/user/account.html new file mode 100644 index 0000000..1fc284d --- /dev/null +++ b/tracker/templates/tracker/user/account.html @@ -0,0 +1,54 @@ +{% extends "tracker/base.html" %} + +{% load django_bootstrap_breadcrumbs %} + +{% block breadcrumbs %} + {% breadcrumb_raw "Home" "tracker:index" %} + {% breadcrumb_raw user.get_full_name "/user/profile" %} + {% breadcrumb_raw "Account" "tracker:/user/account" %} +{% endblock %} + +{% block content %} +
+
+
+
+ {% csrf_token %} + + {% for field in change_password_form %} +
+ + {{ field }} + {% if field.help_text %} + {{ field.help_text|safe }} + {% endif %} +
+ {% endfor %} + +
+
+
+ +
+
+ +
+ {% csrf_token %} + +
+
+
+
+{% endblock %} diff --git a/tracker/urls.py b/tracker/urls.py index 549fd43..5acf5b6 100644 --- a/tracker/urls.py +++ b/tracker/urls.py @@ -33,5 +33,9 @@ path(f'{reports_base}monthly_distribution', views.user.monthly_distribution, name='user/reports/monthly_distribution'), path(f'{reports_base}weekly_time', views.user.weekly_time, name='user/reports/weekly_time'), path(f'{reports_base}weekly_worktime', views.user.weekly_worktime_report, name='user/reports/weekly_worktime'), + path('settings', views.user.settings, name='user/settings'), + path('account', views.user.account, name='user/account'), + path('account/change_password', views.user.change_password, name='user/account/change_password'), + path('account/clear_password', views.user.clear_password, name='user/account/clear_password'), ] \ No newline at end of file diff --git a/tracker/views/user.py b/tracker/views/user.py index 72685f5..2158cdb 100644 --- a/tracker/views/user.py +++ b/tracker/views/user.py @@ -6,11 +6,11 @@ import pytz from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.shortcuts import render +from django.shortcuts import render, redirect from django.utils import formats from django.utils.translation import activate as tl_activate -from tracker.forms import SettingsForm +from tracker.forms import SettingsForm, ChangePasswordForm from tracker.models import TimeRecord, Project, Setting @@ -222,6 +222,59 @@ def settings(request): return render(request, 'tracker/user/settings.html', context={'form': form}) +@login_required +def account(request): + return render(request, 'tracker/user/account.html', context={ + 'change_password_form': ChangePasswordForm() + }) + + +@login_required +def change_password(request): + if request.method != 'POST': + messages.error(request, 'Invalid request method') + return redirect('tracker:user/account') + + password = request.POST['password'] + confirmation = request.POST['confirm_password'] + + if password != confirmation: + messages.error(request, 'Passwords do not match') + return redirect('tracker:user/account') + + min_len = 8 + if len(password) < min_len: + messages.error(request, f'Passwords must be at least {min_len} characters long') + return redirect('tracker:user/account') + + if not any(char.isdigit() for char in password): + messages.error(request, 'Password must contain at least 1 digit.') + return redirect('tracker:user/account') + + if not any(char.isalpha() for char in password): + messages.error(request, 'Password must contain at least 1 letter.') + return redirect('tracker:user/account') + + request.user.set_password(password) + request.user.save() + + messages.success(request, 'Password successfully changed') + return redirect('tracker:user/account') + + +@login_required +def clear_password(request): + if request.method != 'POST': + messages.error(request, 'Invalid request method') + return redirect('tracker:user/account') + + request.user.set_unusable_password() + request.user.save() + + messages.success(request, 'Password successfully cleared') + return redirect('tracker:user/account') + + def build_sum_for_dates(setting, dates, matrix): init = 0 if setting.duration_format == Setting.DURATION_FORMAT_DECIMAL else timedelta() return [sum([p['duration'][i] for p in matrix], init) for i in range(len(dates))] diff --git a/trrrakk/templates/trrrakk/navigation.html b/trrrakk/templates/trrrakk/navigation.html index d4ead05..a97792a 100644 --- a/trrrakk/templates/trrrakk/navigation.html +++ b/trrrakk/templates/trrrakk/navigation.html @@ -33,6 +33,9 @@ Settings + + Account + Sign out