diff --git a/logs_collector/account/admin.py b/logs_collector/account/admin.py
index 8c38f3f..3757feb 100644
--- a/logs_collector/account/admin.py
+++ b/logs_collector/account/admin.py
@@ -1,3 +1,6 @@
from django.contrib import admin
+from django.contrib.auth.admin import UserAdmin
+from .models import User
-# Register your models here.
+
+admin.site.register(User, UserAdmin)
diff --git a/logs_collector/account/forms.py b/logs_collector/account/forms.py
new file mode 100644
index 0000000..5d7941b
--- /dev/null
+++ b/logs_collector/account/forms.py
@@ -0,0 +1,37 @@
+from django import forms
+from django.utils.safestring import mark_safe
+from crispy_forms.helper import FormHelper
+from crispy_forms.layout import Layout, Submit, Div
+from crispy_forms.bootstrap import PrependedText
+
+from .models import User
+
+
+class UserProfileForm(forms.ModelForm):
+
+ class Meta:
+ model = User
+ fields = [
+ 'email',
+ 'first_name',
+ 'last_name',
+ ]
+
+ def __init__(self, *args, **kwargs):
+ super(UserProfileForm, self).__init__(*args, **kwargs)
+ self.helper = FormHelper(self)
+ self.helper.form_show_labels = False
+
+ self.helper.layout = Layout(
+ Div(
+ PrependedText(
+ 'email',
+ mark_safe(''),
+ placeholder="email"
+ ),
+ PrependedText('first_name', 'First name:'),
+ PrependedText('last_name', 'Last name:'),
+ css_class='col-lg-6'
+ ),
+ Submit('submit', 'Save', css_class='btn btn-primary'),
+ )
diff --git a/logs_collector/account/migrations/0001_initial.py b/logs_collector/account/migrations/0001_initial.py
new file mode 100644
index 0000000..9bf0131
--- /dev/null
+++ b/logs_collector/account/migrations/0001_initial.py
@@ -0,0 +1,44 @@
+# Generated by Django 4.2 on 2023-09-08 12:27
+
+import django.contrib.auth.models
+import django.contrib.auth.validators
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('auth', '0012_alter_user_first_name_max_length'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='User',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('password', models.CharField(max_length=128, verbose_name='password')),
+ ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
+ ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
+ ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
+ ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
+ ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
+ ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
+ ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
+ ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
+ ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
+ ],
+ options={
+ 'verbose_name': 'user',
+ 'verbose_name_plural': 'users',
+ 'abstract': False,
+ },
+ managers=[
+ ('objects', django.contrib.auth.models.UserManager()),
+ ],
+ ),
+ ]
diff --git a/logs_collector/account/models.py b/logs_collector/account/models.py
index 71a8362..6fd5cc5 100644
--- a/logs_collector/account/models.py
+++ b/logs_collector/account/models.py
@@ -1,3 +1,10 @@
-from django.db import models
+from django.urls import reverse
+from django.contrib.auth.models import AbstractUser
-# Create your models here.
+
+# using-a-custom-user-model-when-starting-a-project
+# https://docs.djangoproject.com/en/4.2/topics/auth/customizing/
+class User(AbstractUser):
+
+ def get_absolute_url(self):
+ return reverse('account:show_profile')
diff --git a/logs_collector/account/templates/account/base.html b/logs_collector/account/templates/account/base.html
new file mode 100644
index 0000000..3334f06
--- /dev/null
+++ b/logs_collector/account/templates/account/base.html
@@ -0,0 +1,30 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block account_head %}
+
{% block title %}{% endblock title %}
+{% endblock account_head %}
+
+{% block account_content %}
+
+
+ {% include 'includes/navigation.html' %}
+
+
+
+
+ {% block main %}{% endblock main %}
+
+
+
+{% endblock account_content %}
+
+{% block account_scripts %}
+
+ {% block bs %}{% endblock bs %}
+ {% block jquery %}{% endblock jquery %}
+{% endblock account_scripts %}
diff --git a/logs_collector/account/templates/account/includes/auth_credentials.html b/logs_collector/account/templates/account/includes/auth_credentials.html
new file mode 100644
index 0000000..1483f81
--- /dev/null
+++ b/logs_collector/account/templates/account/includes/auth_credentials.html
@@ -0,0 +1,39 @@
+
diff --git a/logs_collector/account/templates/account/includes/profile_credentials.html b/logs_collector/account/templates/account/includes/profile_credentials.html
new file mode 100644
index 0000000..de6dccd
--- /dev/null
+++ b/logs_collector/account/templates/account/includes/profile_credentials.html
@@ -0,0 +1,26 @@
+
diff --git a/logs_collector/account/templates/account/password_change.html b/logs_collector/account/templates/account/password_change.html
new file mode 100644
index 0000000..da2373a
--- /dev/null
+++ b/logs_collector/account/templates/account/password_change.html
@@ -0,0 +1,12 @@
+{% extends 'account/profile.html' %}
+{% load static %}
+{% load crispy_forms_tags %}
+{% block password_change %}
+
+{% endblock password_change %}
diff --git a/logs_collector/account/templates/account/password_change_done.html b/logs_collector/account/templates/account/password_change_done.html
new file mode 100644
index 0000000..4647a04
--- /dev/null
+++ b/logs_collector/account/templates/account/password_change_done.html
@@ -0,0 +1,11 @@
+{% extends 'account/profile_info.html' %}
+{% load static %}
+{% block profile_alerts %}
+
+
+
Password changed
+
+ Your password has been successfully changed.
+
+
+{% endblock profile_alerts %}
diff --git a/logs_collector/account/templates/account/profile.html b/logs_collector/account/templates/account/profile.html
new file mode 100644
index 0000000..424da06
--- /dev/null
+++ b/logs_collector/account/templates/account/profile.html
@@ -0,0 +1,17 @@
+{% extends 'account/base.html' %}
+{% load static %}
+{% block title %} {{ title }} {% endblock title %}
+{% block main %}
+
+
+
+
+ {% block profile_info %}{% endblock profile_info %}
+ {% block profile_update %}{% endblock profile_update %}
+ {% block password_change %}{% endblock password_change %}
+
+
+
+{% endblock main %}
diff --git a/logs_collector/account/templates/account/profile_info.html b/logs_collector/account/templates/account/profile_info.html
new file mode 100644
index 0000000..b5d61f0
--- /dev/null
+++ b/logs_collector/account/templates/account/profile_info.html
@@ -0,0 +1,7 @@
+{% extends 'account/profile.html' %}
+{% load static %}
+{% block profile_info %}
+ {% block profile_alerts %}{% endblock profile_alerts %}
+ {% include 'account/includes/auth_credentials.html' %}
+ {% include 'account/includes/profile_credentials.html' %}
+{% endblock profile_info %}
diff --git a/logs_collector/account/templates/account/profile_update.html b/logs_collector/account/templates/account/profile_update.html
new file mode 100644
index 0000000..29f6dcb
--- /dev/null
+++ b/logs_collector/account/templates/account/profile_update.html
@@ -0,0 +1,13 @@
+{% extends 'account/profile.html' %}
+{% load static %}
+{% load crispy_forms_tags %}
+{% block profile_update %}
+ {% include 'account/includes/auth_credentials.html' %}
+
+
Profile
+
+
+ {% crispy form %}
+
+
+{% endblock profile_update %}
diff --git a/logs_collector/account/tests.py b/logs_collector/account/tests.py
deleted file mode 100644
index 7ce503c..0000000
--- a/logs_collector/account/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/logs_collector/account/tests/__init__.py b/logs_collector/account/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/logs_collector/account/tests/test_urls.py b/logs_collector/account/tests/test_urls.py
new file mode 100644
index 0000000..3eb3ca4
--- /dev/null
+++ b/logs_collector/account/tests/test_urls.py
@@ -0,0 +1,36 @@
+from django.test import TestCase
+from django.urls import resolve, reverse
+from django.contrib.auth.views import (
+ LogoutView,
+ PasswordChangeView,
+ PasswordChangeDoneView
+)
+
+from account import views
+
+
+class TestUrls(TestCase):
+
+ # READ:
+ def test_account_logout_url_is_resolved(self):
+ url = reverse('account:logout')
+ self.assertEquals(resolve(url).func.view_class, LogoutView)
+
+ def test_account_show_url_is_resolved(self):
+ url = reverse('account:show_profile')
+ self.assertEquals(resolve(url).func.view_class, views.DetailProfile)
+
+ def test_password_change_done_url_is_resolved(self):
+ url = reverse('account:password_change_done')
+ self.assertEquals(
+ resolve(url).func.view_class, PasswordChangeDoneView
+ )
+
+ # UPDATE:
+ def test_password_change_url_is_resolved(self):
+ url = reverse('account:password_change')
+ self.assertEquals(resolve(url).func.view_class, PasswordChangeView)
+
+ def test_account_update_url_is_resolved(self):
+ url = reverse('account:update_profile')
+ self.assertEquals(resolve(url).func.view_class, views.UpdateProfile)
diff --git a/logs_collector/account/urls.py b/logs_collector/account/urls.py
index e900925..7f970d0 100644
--- a/logs_collector/account/urls.py
+++ b/logs_collector/account/urls.py
@@ -1,6 +1,10 @@
from django.conf import settings
-from django.urls import path
-from django.contrib.auth.views import LogoutView
+from django.urls import path, reverse_lazy
+from django.contrib.auth.views import (
+ LogoutView,
+ PasswordChangeView,
+ PasswordChangeDoneView
+)
from rest_framework_simplejwt.views import (
TokenObtainPairView,
@@ -8,6 +12,8 @@ from rest_framework_simplejwt.views import (
TokenVerifyView
)
+from . import views
+
app_name = 'account'
@@ -17,7 +23,35 @@ urlpatterns = [
'account/logout/',
LogoutView.as_view(next_page=settings.LOGOUT_REDIRECT_URL),
name='logout'
- )
+ ),
+ # CHANGE PASSWORD:
+ path(
+ 'account/password-change/',
+ PasswordChangeView.as_view(
+ template_name='account/password_change.html',
+ success_url=reverse_lazy('account:password_change_done'),
+ ),
+ name='password_change'
+ ),
+ path(
+ 'account/password-change/done/',
+ PasswordChangeDoneView.as_view(
+ template_name='account/password_change_done.html'
+ ),
+ name='password_change_done'
+ ),
+ # UPDATE:
+ path(
+ 'account/update/',
+ views.UpdateProfile.as_view(),
+ name='update_profile'
+ ),
+ # READ:
+ path(
+ 'account/show/',
+ views.DetailProfile.as_view(),
+ name='show_profile'
+ ),
]
urlpatterns += [
diff --git a/logs_collector/account/views.py b/logs_collector/account/views.py
index e69de29..5c7d8ea 100644
--- a/logs_collector/account/views.py
+++ b/logs_collector/account/views.py
@@ -0,0 +1,32 @@
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.views import generic
+
+from collector.utils.mixins import ExtraContextMixin
+
+from .forms import UserProfileForm
+from .models import User
+
+
+class DetailProfile(LoginRequiredMixin, ExtraContextMixin, generic.DetailView):
+ model = User
+ template_name = 'account/profile_info.html'
+ context_object_name = 'profile'
+
+ def get_title(self, **kwargs):
+ return f'{self.title} - {self.request.user}'
+
+ def get_object(self):
+ return self.model.objects.get(username=self.request.user)
+
+
+class UpdateProfile(LoginRequiredMixin, ExtraContextMixin, generic.UpdateView):
+ model = User
+ template_name = 'account/profile_update.html'
+ context_object_name = 'profile'
+ form_class = UserProfileForm
+
+ def get_object(self):
+ return self.model.objects.get(username=self.request.user)
+
+ def get_title(self, **kwargs):
+ return f'{self.title} - {self.kwargs.get("username", "account")}'
diff --git a/logs_collector/collector/api/tests/test_views.py b/logs_collector/collector/api/tests/test_views.py
index 798c503..c12806f 100644
--- a/logs_collector/collector/api/tests/test_views.py
+++ b/logs_collector/collector/api/tests/test_views.py
@@ -1,12 +1,13 @@
from pathlib import Path
from django.core.files.base import ContentFile
from django.conf import settings
-from django.contrib.auth.models import User
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
+from account.models import User
+
from collector.models import Archive, Platform, Ticket
diff --git a/logs_collector/collector/models.py b/logs_collector/collector/models.py
index 063351a..0d39e60 100644
--- a/logs_collector/collector/models.py
+++ b/logs_collector/collector/models.py
@@ -2,10 +2,11 @@ import uuid
import hashlib
from django.core.validators import MaxValueValidator, MinValueValidator
-from django.contrib.auth.models import User
from django.db import models
from django.urls import reverse
+from account.models import User
+
from .utils.helpers import logs_dir_path
diff --git a/logs_collector/collector/tests/test_models.py b/logs_collector/collector/tests/test_models.py
index 34a5d4d..92e3890 100644
--- a/logs_collector/collector/tests/test_models.py
+++ b/logs_collector/collector/tests/test_models.py
@@ -1,9 +1,9 @@
from pathlib import Path
from django.test import TestCase
-from django.contrib.auth.models import User
from django.core.files.base import ContentFile
from django.conf import settings
+from account.models import User
from collector.models import Platform, Ticket, Archive
diff --git a/logs_collector/collector/tests/test_urls.py b/logs_collector/collector/tests/test_urls.py
index dd2157a..e85c437 100644
--- a/logs_collector/collector/tests/test_urls.py
+++ b/logs_collector/collector/tests/test_urls.py
@@ -1,6 +1,7 @@
from django.test import TestCase
from django.urls import resolve, reverse
-from django.contrib.auth.models import User
+
+from account.models import User
from collector import views
from collector.models import Ticket, Platform
diff --git a/logs_collector/collector/tests/test_views.py b/logs_collector/collector/tests/test_views.py
index bf7a278..29cc7ee 100644
--- a/logs_collector/collector/tests/test_views.py
+++ b/logs_collector/collector/tests/test_views.py
@@ -1,6 +1,7 @@
from django.test import TestCase, Client
from django.urls import reverse
-from django.contrib.auth.models import User
+
+from account.models import User
from collector.models import Ticket, Platform
diff --git a/logs_collector/logs_collector/settings.py b/logs_collector/logs_collector/settings.py
index 486b5cb..1a74118 100644
--- a/logs_collector/logs_collector/settings.py
+++ b/logs_collector/logs_collector/settings.py
@@ -252,3 +252,6 @@ SIMPLE_JWT = {
LOGIN_URL = 'two_factor:login'
LOGIN_REDIRECT_URL = 'collector:index'
LOGOUT_REDIRECT_URL = 'two_factor:login'
+
+# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-user-model
+AUTH_USER_MODEL = 'account.User'
diff --git a/logs_collector/templates/includes/extra_menu.html b/logs_collector/templates/includes/extra_menu.html
index 5967a1c..c6a898f 100644
--- a/logs_collector/templates/includes/extra_menu.html
+++ b/logs_collector/templates/includes/extra_menu.html
@@ -1,9 +1,13 @@
{% if request.user.is_authenticated %}
-
+
+ {{ request.user }}
+