From 0d4eb81bf2d8a60cec301edc32d7c5f3971aa091 Mon Sep 17 00:00:00 2001
From: dcz2 <dcz@ipipan.waw.pl>
Date: Fri, 8 Apr 2022 22:14:41 +0200
Subject: [PATCH] Setting, resetting password, e-mail conf, standarize quotes

---
 .environment-dev                              |  15 ++-
 locale/en/LC_MESSAGES/django.mo               | Bin 34225 -> 35733 bytes
 locale/en/LC_MESSAGES/django.po               | 100 +++++++++++++++---
 locale/en/LC_MESSAGES/djangojs.po             |   2 +-
 shellvalier/settings.py                       |  10 +-
 users/forms.py                                |  37 +++++--
 .../commands/create_groups_and_permissions.py |  14 +--
 users/templates/{registration => }/login.html |   6 +-
 users/templates/new_user_email.html           |  11 ++
 users/templates/password_change.html          |  19 ++++
 .../{registration => }/password_reset.html    |   6 +-
 users/templates/password_reset_email.html     |  11 ++
 users/templates/password_reset_form.html      |  23 ++++
 users/templates/password_reset_requested.html |  17 +++
 users/templates/user_form.html                |   2 +-
 users/templates/user_list.html                |  14 +--
 users/templates/user_profile.html             |   2 +-
 users/urls.py                                 |  54 +++++++---
 users/utils.py                                |  14 +++
 users/views.py                                |   5 +-
 20 files changed, 298 insertions(+), 64 deletions(-)
 rename users/templates/{registration => }/login.html (71%)
 create mode 100644 users/templates/new_user_email.html
 create mode 100644 users/templates/password_change.html
 rename users/templates/{registration => }/password_reset.html (69%)
 create mode 100644 users/templates/password_reset_email.html
 create mode 100644 users/templates/password_reset_form.html
 create mode 100644 users/templates/password_reset_requested.html
 create mode 100644 users/utils.py

diff --git a/.environment-dev b/.environment-dev
index 335bee0..d92b941 100644
--- a/.environment-dev
+++ b/.environment-dev
@@ -1,9 +1,16 @@
-UWSGI_PROCESS_PER_CONTAINER=4
-DEBUG=true
 ALLOWED_HOSTS=localhost,127.0.0.1
-SECRET_KEY=Ixosoh1iemoh0Heloh1thee5akooboonu5veehae4aikoh2ohg
 DATABASE_HOST=shellvalier-postgresql
 DATABASE_NAME=shellvalier
-DATABASE_USER=shellvalier
 DATABASE_PASSWORD=shellvalier
 DATABASE_PORT=5432
+DATABASE_USER=shellvalier
+DEBUG=true
+EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
+EMAIL_HOST=
+EMAIL_PORT=25
+EMAIL_HOST_USER=
+EMAIL_HOST_PASSWORD=
+EMAIL_USE_TLS=false
+EMAIL_USE_SSL=false
+SECRET_KEY=Ixosoh1iemoh0Heloh1thee5akooboonu5veehae4aikoh2ohg
+UWSGI_PROCESS_PER_CONTAINER=4
diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo
index f7595614722432da63fc8ccf1e3efcd5f783fe8f..29f13eb298a0c8e679ff2df730c09217471e3bb9 100644
GIT binary patch
delta 12340
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

delta 10796
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

diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po
index 56413e0..2986a6f 100644
--- a/locale/en/LC_MESSAGES/django.po
+++ b/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-04-07 23:17+0200\n"
+"POT-Creation-Date: 2022-04-08 21:59+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -46,9 +46,8 @@ msgstr "Your profile"
 msgid "Wyloguj siÄ™"
 msgstr "Sign out"
 
-#: common/templates/base.html:89 users/forms.py:71
-#: users/templates/registration/login.html:6
-#: users/templates/registration/login.html:12
+#: common/templates/base.html:89 users/forms.py:74 users/templates/login.html:6
+#: users/templates/login.html:12
 msgid "Zaloguj siÄ™"
 msgstr "Sign in"
 
@@ -2290,28 +2289,100 @@ msgstr "distributive phrase"
 msgid "fraza posesywna"
 msgstr "possesive phrase"
 
-#: users/forms.py:10 users/templates/user_list.html:21
+#: users/forms.py:12 users/templates/user_list.html:21
 msgid "Grupa"
 msgstr "Group"
 
-#: users/forms.py:29 users/forms.py:59
+#: users/forms.py:31 users/forms.py:61
 msgid "Zapisz"
 msgstr "Save"
 
-#: users/forms.py:30 users/forms.py:60
+#: users/forms.py:32 users/forms.py:63 users/forms.py:84 users/forms.py:93
+#: users/forms.py:102
 msgid "Wróć"
 msgstr "Back"
 
-#: users/forms.py:84
+#: users/forms.py:62 users/templates/password_change.html:6
+#: users/templates/password_change.html:12
+msgid "Zmień swoje hasło"
+msgstr "Change your password"
+
+#: users/forms.py:75
+msgid "Nie pamiętam hasła"
+msgstr "I forgot my password"
+
+#: users/forms.py:83
 msgid "Zresetuj hasło"
 msgstr "Reset password"
 
-#: users/templates/registration/password_reset.html:6
-#: users/templates/registration/password_reset.html:12
+#: users/forms.py:92
+msgid "Zmień hasło"
+msgstr "Change password"
+
+#: users/forms.py:101
+msgid "Ustaw hasło"
+msgstr "Set password"
+
+#: users/templates/new_user_email.html:2
+#, python-format
+msgid "Otrzymujesz ten e-mail, ponieważ ktoś zaprosił Cię do %(site_name)s."
+msgstr ""
+"You're receiving this email because you have been invited to %(site_name)s."
+
+#: users/templates/new_user_email.html:4
+msgid "Przejdź na poniższą stronę i ustaw swoje hasło:"
+msgstr "Please go to the following page and set your password:"
+
+#: users/templates/new_user_email.html:8
+msgid "Twoja nazwa użytkownika"
+msgstr "Your username"
+
+#: users/templates/new_user_email.html:10
+#: users/templates/password_reset_email.html:10
+#, python-format
+msgid "Zespół %(site_name)s"
+msgstr "The %(site_name)s team"
+
+#: users/templates/password_reset.html:6 users/templates/password_reset.html:12
 msgid "Zresetuj swoje hasło"
 msgstr "Reset your password"
 
-#: users/templates/user_list.html:12 users/views.py:35
+#: users/templates/password_reset_email.html:2
+#, python-format
+msgid ""
+"Otrzymujesz ten e-mail w związku z prośbą o zmianę hasła Twojego użytkownia "
+"na %(site_name)s."
+msgstr ""
+"You're receiving this email because you requested a password reset for your "
+"user account at %(site_name)s."
+
+#: users/templates/password_reset_email.html:4
+msgid "Przejdź na poniższą stronę i ustaw swoje nowe hasło:"
+msgstr "Please go to the following page and choose a new password:"
+
+#: users/templates/password_reset_email.html:8
+msgid "Twoja nazwa użytkownia:"
+msgstr "Your username, in case you’ve forgotten:"
+
+#: users/templates/password_reset_form.html:6
+#: users/templates/password_reset_form.html:12
+msgid "Ustaw swoje nowe hasło"
+msgstr "Choose your new password"
+
+#: users/templates/password_reset_requested.html:6
+#: users/templates/password_reset_requested.html:12
+msgid "Sprawdź swoją skrzynkę e-mail"
+msgstr "Please check your e-mail inbox"
+
+#: users/templates/password_reset_requested.html:13
+msgid ""
+"Wysłaliśmy na Twój adres e-mail link, po kliknięciu którego uzyskasz "
+"możliwość wprowadzenia swojego nowego hasła."
+msgstr ""
+"We've sent you an e-mail containing a link you can use to set your new "
+"password."
+
+#: users/templates/user_list.html:12 users/views.py:38
 msgid "Dodaj użytkownika"
 msgstr "Add a user"
 
@@ -2335,7 +2406,12 @@ msgstr "Actions"
 msgid "Edytuj"
 msgstr "Edit"
 
-#: users/views.py:48
+#: users/utils.py:7
+#, python-format
+msgid "Zaproszenie do %s"
+msgstr "An invitation to %s"
+
+#: users/views.py:51
 msgid "Edytuj użytkownika"
 msgstr "Edit user"
 
diff --git a/locale/en/LC_MESSAGES/djangojs.po b/locale/en/LC_MESSAGES/djangojs.po
index fb7fa6f..e5f578e 100644
--- a/locale/en/LC_MESSAGES/djangojs.po
+++ b/locale/en/LC_MESSAGES/djangojs.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-04-07 23:17+0200\n"
+"POT-Creation-Date: 2022-04-08 21:59+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
diff --git a/shellvalier/settings.py b/shellvalier/settings.py
index 89ee949..4bd5bef 100644
--- a/shellvalier/settings.py
+++ b/shellvalier/settings.py
@@ -162,6 +162,14 @@ STATICFILES_DIRS = [
     os.path.join(BASE_DIR, 'common/static/'),
 ]
 
-LOGIN_URL = reverse_lazy("users:login")
+LOGIN_URL = reverse_lazy('users:login')
 LOGIN_REDIRECT_URL = reverse_lazy('dash')
 LOGOUT_REDIRECT_URL = reverse_lazy('dash')
+
+EMAIL_BACKEND = get_environment('EMAIL_BACKEND')
+EMAIL_HOST = get_environment('EMAIL_HOST')
+EMAIL_PORT = get_environment('EMAIL_PORT', mapper=int)
+EMAIL_HOST_USER = get_environment('EMAIL_HOST_USER')
+EMAIL_HOST_PASSWORD = get_environment('EMAIL_HOST_PASSWORD')
+EMAIL_USE_TLS = get_environment('EMAIL_USE_TLS', mapper=boolean_mapper)
+EMAIL_USE_SSL = get_environment('EMAIL_USE_SSL', mapper=boolean_mapper)
diff --git a/users/forms.py b/users/forms.py
index e0d98f1..054b6af 100644
--- a/users/forms.py
+++ b/users/forms.py
@@ -1,13 +1,15 @@
 from django import forms
 from django.contrib.auth.models import Group, User
-from django.utils.translation import gettext as _
+from django.urls import reverse_lazy
+from django.utils.text import format_lazy
+from django.utils.translation import gettext_lazy as _
 
 from crispy_forms.helper import FormHelper
 from crispy_forms.layout import HTML, Layout, Fieldset, ButtonHolder, Submit
 
 
 class UserForm(forms.ModelForm):
-    group = forms.ModelChoiceField(queryset=Group.objects.all(), label=_("Grupa"))
+    group = forms.ModelChoiceField(queryset=Group.objects.all(), label=_('Grupa'))
 
     class Meta:
         model = User
@@ -27,7 +29,7 @@ class UserForm(forms.ModelForm):
             Fieldset('', 'first_name', 'last_name', 'username', 'email', 'group', 'is_active'),
             ButtonHolder(
                 Submit('submit', _('Zapisz'), css_class='btn btn-sm btn-success'),
-                HTML('''<a class="btn btn-sm btn-light" href="{% url 'users:user_list' %}">''' + _('Wróć') + '</a>'),
+                HTML(format_lazy('<a class="btn btn-sm btn-light" href="{}">{}</a>', reverse_lazy('users:user_list'), _('Wróć'))),
             ),
         )
         for field in ['first_name', 'last_name', 'email']:
@@ -57,7 +59,8 @@ class UserProfileForm(forms.ModelForm):
             Fieldset('', 'first_name', 'last_name', 'email'),
             ButtonHolder(
                 Submit('submit', _('Zapisz'), css_class='btn btn-sm btn-success'),
-                HTML('''<a class="btn btn-sm btn-light" href="{% url 'dash' %}">''' + _('Wróć') + '</a>'),
+                HTML(format_lazy('<a class="btn btn-sm btn-light" href="{}">{}</a>', reverse_lazy('users:password_change'), _('Zmień swoje hasło'))),
+                HTML(format_lazy('<a class="btn btn-sm btn-light" href="{}">{}</a>', reverse_lazy('dash'), _('Wróć'))),
             ),
         )
         for field in ['first_name', 'last_name', 'email']:
@@ -69,11 +72,7 @@ login_form_helper.layout = Layout(
     Fieldset('', 'username', 'password'),
     ButtonHolder(
         Submit('submit', _('Zaloguj siÄ™'), css_class='btn btn-sm btn-success'),
-        HTML(
-            '''<a class="btn btn-sm btn-light" href="{% url 'users:password_reset' %}">'''
-            f"{_('Nie pamiętam hasła')}"
-            '</a>'
-        ),
+        HTML(format_lazy('<a class="btn btn-sm btn-light" href="{}">{}</a>', reverse_lazy('users:password_reset'), _('Nie pamiętam hasła'))),
     ),
 )
 
@@ -82,6 +81,24 @@ password_reset_form_helper.layout = Layout(
     Fieldset('', 'email'),
     ButtonHolder(
         Submit('submit', _('Zresetuj hasło'), css_class='btn btn-sm btn-success'),
-        HTML('''<a class="btn btn-sm btn-light" href="{% url 'users:login' %}">''' f"{_('Wróć')}" '</a>'),
+        HTML(format_lazy('<a class="btn btn-sm btn-light" href="{}">{}</a>', reverse_lazy('users:login'), _('Wróć'))),
+    )
+)
+
+password_change_form_helper = FormHelper()
+password_change_form_helper.layout = Layout(
+    Fieldset('', 'old_password', 'new_password1', 'new_password2'),
+    ButtonHolder(
+        Submit('submit', _('Zmień hasło'), css_class='btn btn-sm btn-success'),
+        HTML(format_lazy('<a class="btn btn-sm btn-light" href="{}">{}</a>', reverse_lazy('users:user_profile'), _("Wróć"))),
+    )
+)
+
+password_reset_set_password_form_helper = FormHelper()
+password_reset_set_password_form_helper.layout = Layout(
+    Fieldset('', 'new_password1', 'new_password2'),
+    ButtonHolder(
+        Submit('submit', _('Ustaw hasło'), css_class='btn btn-sm btn-success'),
+        HTML(format_lazy('<a class="btn btn-sm btn-light" href="{}">{}</a>', reverse_lazy('dash'), _("Wróć"))),
     )
 )
diff --git a/users/management/commands/create_groups_and_permissions.py b/users/management/commands/create_groups_and_permissions.py
index 195d2d1..a4361ca 100644
--- a/users/management/commands/create_groups_and_permissions.py
+++ b/users/management/commands/create_groups_and_permissions.py
@@ -5,18 +5,18 @@ from django.core.management.base import BaseCommand
 
 class Command(BaseCommand):
     def handle(self, **options):
-        admins, __ = Group.objects.get_or_create(name="Admini")
+        admins, __ = Group.objects.get_or_create(name='Admini')
         admins.permissions.add(
-            Permission.objects.get(codename="view_user", content_type=ContentType.objects.get_for_model(User)),
-            Permission.objects.get(codename="add_user", content_type=ContentType.objects.get_for_model(User)),
-            Permission.objects.get(codename="change_user", content_type=ContentType.objects.get_for_model(User)),
-            Permission.objects.get(codename="delete_user", content_type=ContentType.objects.get_for_model(User)),
+            Permission.objects.get(codename='view_user', content_type=ContentType.objects.get_for_model(User)),
+            Permission.objects.get(codename='add_user', content_type=ContentType.objects.get_for_model(User)),
+            Permission.objects.get(codename='change_user', content_type=ContentType.objects.get_for_model(User)),
+            Permission.objects.get(codename='delete_user', content_type=ContentType.objects.get_for_model(User)),
         )
-        lexicographs, __ = Group.objects.get_or_create(name="Leksykografowie")
+        lexicographs, __ = Group.objects.get_or_create(name='Leksykografowie')
         lexicographs.permissions.add(
             # TODO
         )
-        super_lexicographs, __ = Group.objects.get_or_create(name="Super Leksykografowie")
+        super_lexicographs, __ = Group.objects.get_or_create(name='Super Leksykografowie')
         super_lexicographs.permissions.add(
             # TODO
         )
diff --git a/users/templates/registration/login.html b/users/templates/login.html
similarity index 71%
rename from users/templates/registration/login.html
rename to users/templates/login.html
index 10834c7..d184db3 100644
--- a/users/templates/registration/login.html
+++ b/users/templates/login.html
@@ -1,15 +1,15 @@
-{% extends "base.html" %}
+{% extends 'base.html' %}
 
 {% load i18n %}
 {% load crispy_forms_filters %}
 
-{% block title %}{% trans "Zaloguj siÄ™" %}{% endblock %}
+{% block title %}{% trans 'Zaloguj siÄ™' %}{% endblock %}
 
 {% block content %}
     <div class="row m-0">
         <div class="col-lg-4 col-md-6 m-auto">
             <div class="bg-white mt-4 p-3 clearfix">
-                <h5 class="mt-2 mb-4">{% trans "Zaloguj siÄ™" %}</h5>
+                <h5 class="mt-2 mb-4">{% trans 'Zaloguj siÄ™' %}</h5>
                 <form action="." method="post">
                     {% crispy form helper %}
                 </form>
diff --git a/users/templates/new_user_email.html b/users/templates/new_user_email.html
new file mode 100644
index 0000000..1d4ca57
--- /dev/null
+++ b/users/templates/new_user_email.html
@@ -0,0 +1,11 @@
+{% load i18n %}{% autoescape off %}
+{% blocktranslate %}Otrzymujesz ten e-mail, ponieważ ktoś zaprosił Cię do {{ site_name }}.{% endblocktranslate %}
+
+{% translate 'Przejdź na poniższą stronę i ustaw swoje hasło:' %}
+{% block reset_link %}
+{{ protocol }}://{{ domain }}{% url 'users:password_reset' %}
+{% endblock %}
+{% translate 'Twoja nazwa użytkownika' %}: {{ user.get_username }}
+
+{% blocktranslate %}Zespół {{ site_name }}{% endblocktranslate %}
+{% endautoescape %}
diff --git a/users/templates/password_change.html b/users/templates/password_change.html
new file mode 100644
index 0000000..33ac0d7
--- /dev/null
+++ b/users/templates/password_change.html
@@ -0,0 +1,19 @@
+{% extends 'base.html' %}
+
+{% load i18n %}
+{% load crispy_forms_filters %}
+
+{% block title %}{% trans 'Zmień swoje hasło' %}{% endblock %}
+
+{% block content %}
+    <div class="row m-0">
+        <div class="col-lg-4 col-md-6 m-auto">
+            <div class="bg-white mt-4 p-3 clearfix">
+                <h5 class="mt-2 mb-4">{% trans 'Zmień swoje hasło' %}</h5>
+                <form action="." method="post">
+                    {% crispy form helper %}
+                </form>
+            </div>
+        </div>
+    </div>
+{% endblock %}
diff --git a/users/templates/registration/password_reset.html b/users/templates/password_reset.html
similarity index 69%
rename from users/templates/registration/password_reset.html
rename to users/templates/password_reset.html
index 7d37fa9..195135c 100644
--- a/users/templates/registration/password_reset.html
+++ b/users/templates/password_reset.html
@@ -1,15 +1,15 @@
-{% extends "base.html" %}
+{% extends 'base.html' %}
 
 {% load i18n %}
 {% load crispy_forms_filters %}
 
-{% block title %}{% trans "Zresetuj swoje hasło" %}{% endblock %}
+{% block title %}{% trans 'Zresetuj swoje hasło' %}{% endblock %}
 
 {% block content %}
     <div class="row m-0">
         <div class="col-lg-4 col-md-6 m-auto">
             <div class="bg-white mt-4 p-3 clearfix">
-                <h5 class="mt-2 mb-4">{% trans "Zresetuj swoje hasło" %}</h5>
+                <h5 class="mt-2 mb-4">{% trans 'Zresetuj swoje hasło' %}</h5>
                 <form action="." method="post">
                     {% crispy form helper %}
                 </form>
diff --git a/users/templates/password_reset_email.html b/users/templates/password_reset_email.html
new file mode 100644
index 0000000..32d2ae9
--- /dev/null
+++ b/users/templates/password_reset_email.html
@@ -0,0 +1,11 @@
+{% load i18n %}{% autoescape off %}
+{% blocktranslate %}Otrzymujesz ten e-mail w związku z prośbą o zmianę hasła Twojego użytkownia na {{ site_name }}.{% endblocktranslate %}
+
+{% translate 'Przejdź na poniższą stronę i ustaw swoje nowe hasło:' %}
+{% block reset_link %}
+{{ protocol }}://{{ domain }}{% url 'users:password_reset_confirm' uidb64=uid token=token %}
+{% endblock %}
+{% translate 'Twoja nazwa użytkownia:' %} {{ user.get_username }}
+
+{% blocktranslate %}Zespół {{ site_name }}{% endblocktranslate %}
+{% endautoescape %}
diff --git a/users/templates/password_reset_form.html b/users/templates/password_reset_form.html
new file mode 100644
index 0000000..83e179e
--- /dev/null
+++ b/users/templates/password_reset_form.html
@@ -0,0 +1,23 @@
+{% extends 'base.html' %}
+
+{% load i18n %}
+{% load crispy_forms_filters %}
+
+{% block title %}{% trans 'Ustaw swoje nowe hasło' %}{% endblock %}
+
+{% block content %}
+    <div class="row m-0">
+        <div class="col-lg-4 col-md-6 m-auto">
+            <div class="bg-white mt-4 p-3 clearfix">
+                <h5 class="mt-2 mb-4">{% trans 'Ustaw swoje nowe hasło' %}</h5>
+                <form action="." method="post">
+                    {% if form %}
+                        {% crispy form helper %}
+                    {% else %}
+                        <div class="alert alert-danger">{{ title }}</div>
+                    {% endif %}
+                </form>
+            </div>
+        </div>
+    </div>
+{% endblock %}
diff --git a/users/templates/password_reset_requested.html b/users/templates/password_reset_requested.html
new file mode 100644
index 0000000..84a64e3
--- /dev/null
+++ b/users/templates/password_reset_requested.html
@@ -0,0 +1,17 @@
+{% extends 'base.html' %}
+
+{% load i18n %}
+{% load crispy_forms_filters %}
+
+{% block title %}{% trans 'Sprawdź swoją skrzynkę e-mail' %}{% endblock %}
+
+{% block content %}
+    <div class="row m-0">
+        <div class="col-lg-4 col-md-6 m-auto">
+            <div class="bg-white mt-4 p-3 clearfix">
+                <h5 class="mt-2 mb-4">{% trans 'Sprawdź swoją skrzynkę e-mail' %}</h5>
+                <p>{% trans 'Wysłaliśmy na Twój adres e-mail link, po kliknięciu którego uzyskasz możliwość wprowadzenia swojego nowego hasła.' %}</p>
+            </div>
+        </div>
+    </div>
+{% endblock %}
diff --git a/users/templates/user_form.html b/users/templates/user_form.html
index 82fbbf3..25d2a46 100644
--- a/users/templates/user_form.html
+++ b/users/templates/user_form.html
@@ -1,4 +1,4 @@
-{% extends "base-margins.html" %}
+{% extends 'base-margins.html' %}
 
 {% load i18n %}
 
diff --git a/users/templates/user_list.html b/users/templates/user_list.html
index c3b0051..4d84c66 100644
--- a/users/templates/user_list.html
+++ b/users/templates/user_list.html
@@ -1,4 +1,4 @@
-{% extends "base-margins.html" %}
+{% extends 'base-margins.html' %}
 
 {% load i18n %}
 
@@ -16,11 +16,11 @@
 <table class="table">
     <thead>
         <tr>
-            <th>{% trans "ImiÄ™ i nazwisko" %}</th>
-            <th>{% trans "Nazwa użytkownika" %}</th>
-            <th>{% trans "Grupa" %}</th>
-            <th>{% trans "Aktywny" %}</th>
-            <th>{% trans "Akcje" %}</th>
+            <th>{% trans 'ImiÄ™ i nazwisko' %}</th>
+            <th>{% trans 'Nazwa użytkownika' %}</th>
+            <th>{% trans 'Grupa' %}</th>
+            <th>{% trans 'Aktywny' %}</th>
+            <th>{% trans 'Akcje' %}</th>
         </tr>
     </thead>
     <tbody>
@@ -28,7 +28,7 @@
         <tr>
             <td>{{ user.get_full_name }}</td>
             <td>{{ user.username }}</td>
-            <td>{{ user.groups.all|join:", " }}</td>
+            <td>{{ user.groups.all|join:', ' }}</td>
             <td>{{ user.is_active|yesno }}</td>
             <td><a href="{% url 'users:user_edit' pk=user.pk %}" class="btn btn-xs btn-outline-dark">{% trans 'Edytuj' %}</a></td>
         </tr>
diff --git a/users/templates/user_profile.html b/users/templates/user_profile.html
index 35db9c2..e29608c 100644
--- a/users/templates/user_profile.html
+++ b/users/templates/user_profile.html
@@ -1,4 +1,4 @@
-{% extends "base-margins.html" %}
+{% extends 'base-margins.html' %}
 
 {% load i18n %}
 
diff --git a/users/urls.py b/users/urls.py
index 76ae1fa..03b60f1 100644
--- a/users/urls.py
+++ b/users/urls.py
@@ -1,29 +1,57 @@
-from django.urls import include, path, reverse_lazy
-
 from django.contrib.auth import views as auth_views
+from django.urls import include, path, reverse_lazy
+from django.views.generic import TemplateView
 
 from . import views
-from .forms import login_form_helper, password_reset_form_helper
+from . import forms
 
 app_name = 'users'
 
 urlpatterns = [
-    path('', views.user_list, name="user_list"),
-    path('add/', views.user_add, name="user_add"),
-    path('<int:pk>/edit/', views.user_edit, name="user_edit"),
-    path('profile/', views.user_profile, name="user_profile"),
+    path('', views.user_list, name='user_list'),
+    path('add/', views.user_add, name='user_add'),
+    path('<int:pk>/edit/', views.user_edit, name='user_edit'),
+    path('profile/', views.user_profile, name='user_profile'),
     path(
         'login/',
-        auth_views.LoginView.as_view(extra_context={"helper": login_form_helper}, success_url=reverse_lazy('dash')),
-        name="login",
+        auth_views.LoginView.as_view(
+            template_name='login.html',
+            success_url=reverse_lazy('dash'),
+            extra_context={'helper': forms.login_form_helper},
+        ),
+        name='login',
     ),
-    path('logout/', auth_views.LogoutView.as_view(), name="logout"),
+    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
+    path(
+        'psasword-change/',
+        auth_views.PasswordChangeView.as_view(
+            template_name='password_change.html',
+            success_url=reverse_lazy('users:user_profile'),
+            extra_context={'helper': forms.password_change_form_helper},
+        ),
+        name='password_change'),
     path(
         'password-reset/',
         auth_views.PasswordResetView.as_view(
-            template_name="registration/password_reset.html",
-            extra_context={"helper": password_reset_form_helper},
+            template_name='password_reset.html',
+            email_template_name='password_reset_email.html',
+            success_url=reverse_lazy('users:password_reset_requested'),
+            extra_context={'helper': forms.password_reset_form_helper},
+        ),
+        name='password_reset',
+    ),
+    path(
+        'password-reset/check-your-inbox/',
+        TemplateView.as_view(template_name='password_reset_requested.html'),
+        name='password_reset_requested',
+    ),
+    path(
+        'password-reset/<uidb64>/<token>/',
+        auth_views.PasswordResetConfirmView.as_view(
+            template_name='password_reset_form.html',
+            success_url=reverse_lazy('users:login'),
+            extra_context={'helper': forms.password_reset_set_password_form_helper},
         ),
-        name="password_reset",
+        name='password_reset_confirm'
     ),
 ]
diff --git a/users/utils.py b/users/utils.py
new file mode 100644
index 0000000..ae63178
--- /dev/null
+++ b/users/utils.py
@@ -0,0 +1,14 @@
+from django.core.mail import send_mail
+from django.utils.translation import gettext as _
+from django.template import loader
+
+
+def send_new_user_email(site, user, use_https):
+    subject = _('Zaproszenie do %s') % (site.name, )
+    body = loader.render_to_string('new_user_email.html', {
+        'site_name': site.name,
+        'protocol': 'https' if use_https else 'http',
+        'domain': site.domain,
+        'user': user,
+    })
+    send_mail(subject, body, from_email=None, recipient_list=[user.email])
diff --git a/users/views.py b/users/views.py
index 97b3b5a..9e9a6a8 100644
--- a/users/views.py
+++ b/users/views.py
@@ -1,9 +1,11 @@
 from django.contrib.auth.decorators import login_required, permission_required
 from django.contrib.auth.models import User
+from django.contrib.sites.shortcuts import get_current_site
 from django.shortcuts import get_object_or_404, render, redirect
 from django.utils.translation import gettext_lazy as _
 
 from users.forms import UserForm, UserProfileForm
+from users.utils import send_new_user_email
 
 
 @permission_required('users.view_user')
@@ -28,7 +30,8 @@ def user_add(request):
     if request.method == 'POST':
         form = UserForm(instance=User(), data=request.POST)
         if form.is_valid():
-            form.save()
+            user = form.save()
+            send_new_user_email(site=get_current_site(request), user=user, use_https=request.is_secure())
             return redirect('users:user_list')
     else:
         form = UserForm(instance=User())
-- 
GitLab