mirror of
https://github.com/MOIS3Y/logs-collector.git
synced 2025-02-01 01:10:52 +01:00
Add: account views, tests, override user model
This commit is contained in:
parent
305001c9ab
commit
2cba6321c2
@ -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)
|
||||
|
37
logs_collector/account/forms.py
Normal file
37
logs_collector/account/forms.py
Normal file
@ -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('<i class="bi bi-envelope-at"></i>'),
|
||||
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'),
|
||||
)
|
44
logs_collector/account/migrations/0001_initial.py
Normal file
44
logs_collector/account/migrations/0001_initial.py
Normal file
@ -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()),
|
||||
],
|
||||
),
|
||||
]
|
@ -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')
|
||||
|
30
logs_collector/account/templates/account/base.html
Normal file
30
logs_collector/account/templates/account/base.html
Normal file
@ -0,0 +1,30 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block account_head %}
|
||||
<title>{% block title %}{% endblock title %}</title>
|
||||
{% endblock account_head %}
|
||||
|
||||
{% block account_content %}
|
||||
<header class="sticky-top">
|
||||
<section>
|
||||
{% include 'includes/navigation.html' %}
|
||||
</section>
|
||||
</header>
|
||||
<main>
|
||||
<section>
|
||||
{% block main %}{% endblock main %}
|
||||
</section>
|
||||
</main>
|
||||
<footer class="footer mt-auto">
|
||||
<section>
|
||||
{% include 'includes/footer.html' %}
|
||||
</section>
|
||||
</footer>
|
||||
{% endblock account_content %}
|
||||
|
||||
{% block account_scripts %}
|
||||
<script src="{% static 'collector/js/jquery-3.7.0.min.js' %}"></script>
|
||||
{% block bs %}{% endblock bs %}
|
||||
{% block jquery %}{% endblock jquery %}
|
||||
{% endblock account_scripts %}
|
@ -0,0 +1,39 @@
|
||||
<div class="container">
|
||||
<h5 class="card-title">Authentication</h5>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text"><i class="bi bi-person-circle"></i></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="{{ request.user.username }}"
|
||||
aria-label="Username"
|
||||
disabled
|
||||
readonly
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text"><i class="bi bi-shield-lock"></i></span>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
placeholder="●●●●●●●●"
|
||||
aria-label="Password"
|
||||
disabled
|
||||
readonly
|
||||
>
|
||||
<a
|
||||
type="button"
|
||||
class="btn btn-outline-danger"
|
||||
href="{% url 'account:password_change' %}"
|
||||
>
|
||||
<i class="bi bi-pencil-square"></i> Edit
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,26 @@
|
||||
<div class="container">
|
||||
<h5 class="card-title">Profile</h5>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text"><i class="bi bi-envelope-at"></i></span>
|
||||
<input type="text" class="form-control" placeholder="{{ request.user.email }}" aria-label="Email" disabled readonly>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">First name:</span>
|
||||
<input type="text" class="form-control" placeholder="{{ request.user.first_name }}" aria-label="Username" disabled readonly>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">Last name:</i></span>
|
||||
<input type="text" class="form-control" placeholder="{{ request.user.last_name }}" aria-label="Email" disabled readonly>
|
||||
</div>
|
||||
<a
|
||||
href="{% url 'account:update_profile' %}"
|
||||
class="btn btn-outline-warning"
|
||||
>
|
||||
<i class="bi bi-pencil-square"></i> Edit
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,12 @@
|
||||
{% extends 'account/profile.html' %}
|
||||
{% load static %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% block password_change %}
|
||||
<form method="post">
|
||||
<div class="col-lg-6">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<p><input class="btn btn-primary" type="submit" value="Change" /></p>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock password_change %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'account/profile_info.html' %}
|
||||
{% load static %}
|
||||
{% block profile_alerts %}
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<div class=" d-flex align-items-center mt-1">
|
||||
<h5><i class="bi bi-check-circle-fill"></i> Password changed</h5>
|
||||
</div>
|
||||
Your password has been successfully changed.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endblock profile_alerts %}
|
17
logs_collector/account/templates/account/profile.html
Normal file
17
logs_collector/account/templates/account/profile.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends 'account/base.html' %}
|
||||
{% load static %}
|
||||
{% block title %} {{ title }} {% endblock title %}
|
||||
{% block main %}
|
||||
<div class="container mt-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Account:</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% block profile_info %}{% endblock profile_info %}
|
||||
{% block profile_update %}{% endblock profile_update %}
|
||||
{% block password_change %}{% endblock password_change %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock main %}
|
@ -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 %}
|
13
logs_collector/account/templates/account/profile_update.html
Normal file
13
logs_collector/account/templates/account/profile_update.html
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends 'account/profile.html' %}
|
||||
{% load static %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% block profile_update %}
|
||||
{% include 'account/includes/auth_credentials.html' %}
|
||||
<div class="container">
|
||||
<h5 class="card-title">Profile</h5>
|
||||
<hr />
|
||||
<div class="row">
|
||||
{% crispy form %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock profile_update %}
|
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
0
logs_collector/account/tests/__init__.py
Normal file
0
logs_collector/account/tests/__init__.py
Normal file
36
logs_collector/account/tests/test_urls.py
Normal file
36
logs_collector/account/tests/test_urls.py
Normal file
@ -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)
|
@ -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 += [
|
||||
|
@ -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")}'
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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'
|
||||
|
@ -1,9 +1,13 @@
|
||||
{% if request.user.is_authenticated %}
|
||||
<li class="nav-item py-2 py-lg-1 col-12 col-lg-auto">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-person-square"></i> {{ request.user }}
|
||||
</button>
|
||||
<a
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
href="{% url 'account:show_profile' %}"
|
||||
>
|
||||
<i class="bi bi-person-circle"></i> {{ request.user }}
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split"
|
||||
@ -24,7 +28,7 @@
|
||||
href="{% url 'two_factor:profile' %}"
|
||||
class="dropdown-item"
|
||||
type="button">
|
||||
<i class="bi bi-gear"></i> Settings
|
||||
<i class="bi bi-dice-5"></i> 2FA
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider" /></li>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<nav class="navbar navbar-expand-xl bg-body-tertiary">
|
||||
<div class="container">
|
||||
<!--Brand logo -->
|
||||
{% include 'includes/brand.html' %}
|
||||
|
Loading…
Reference in New Issue
Block a user