@ -4,10 +4,28 @@ from app.urls import urlpatterns
|
||||
|
||||
from django.urls import URLPattern, URLResolver
|
||||
|
||||
from settings.models.user_settings import UserSettings
|
||||
|
||||
|
||||
def request(request):
|
||||
return request.get_full_path()
|
||||
|
||||
|
||||
def user_settings(context) -> int:
|
||||
""" Provides the settings ID for the current user
|
||||
|
||||
Returns:
|
||||
int: model usersettings Primary Key
|
||||
"""
|
||||
if context.user.is_authenticated:
|
||||
|
||||
settings = UserSettings.objects.filter(user=context.user)
|
||||
|
||||
return settings[0].pk
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def nav_items(context) -> list(dict()):
|
||||
""" Fetch All Project URLs
|
||||
|
||||
@ -98,4 +116,5 @@ def common(context):
|
||||
|
||||
return {
|
||||
'nav_items': nav_items(context),
|
||||
'user_settings': user_settings(context),
|
||||
}
|
||||
|
||||
18
app/app/tests/test_context_processor_base.py
Normal file
18
app/app/tests/test_context_processor_base.py
Normal file
@ -0,0 +1,18 @@
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_context_processor_base_user_settings_if_authenticated_only():
|
||||
""" Context Processor base to only provide `user_settings` for an authenticated user """
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_context_processor_base_user_settings_is_logged_in_user():
|
||||
""" Context Processor base to only provide `user_settings` for the current logged in user """
|
||||
pass
|
||||
@ -21,14 +21,21 @@ from django.views.static import serve
|
||||
from django.urls import include, path, re_path
|
||||
|
||||
from .views import home
|
||||
|
||||
from core.views import history
|
||||
|
||||
from settings.views import user_settings
|
||||
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('', home.HomeView.as_view(), name='home'),
|
||||
path('admin/', admin.site.urls, name='_administration'),
|
||||
path('account/password_change/', auth_views.PasswordChangeView.as_view(template_name="password_change.html.j2"),
|
||||
name="change_password"),
|
||||
|
||||
path('account/password_change/', auth_views.PasswordChangeView.as_view(template_name="password_change.html.j2"), name="change_password"),
|
||||
path('account/settings/<int:pk>', user_settings.View.as_view(), name="_settings_user"),
|
||||
path("account/", include("django.contrib.auth.urls")),
|
||||
|
||||
path("organization/", include("access.urls")),
|
||||
path("itam/", include("itam.urls")),
|
||||
path("history/<str:model_name>/<int:model_pk>", history.View.as_view(), name='_history'),
|
||||
|
||||
32
app/settings/migrations/0002_usersettings.py
Normal file
32
app/settings/migrations/0002_usersettings.py
Normal file
@ -0,0 +1,32 @@
|
||||
# Generated by Django 5.0.6 on 2024-05-24 23:50
|
||||
|
||||
import access.fields
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0002_alter_team_organization'),
|
||||
('settings', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UserSettings',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
|
||||
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
|
||||
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
|
||||
('default_organization', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='access.organization')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,37 @@
|
||||
# Generated by Django 5.0.6 on 2024-05-24 23:19
|
||||
|
||||
import access.fields
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from settings.models.user_settings import UserSettings
|
||||
|
||||
def add_user_settings(apps, schema_editor):
|
||||
|
||||
for user in User.objects.all():
|
||||
|
||||
if not UserSettings.objects.filter(pk=user.id).exists():
|
||||
|
||||
user_setting = UserSettings.objects.create(
|
||||
user=user
|
||||
)
|
||||
|
||||
user_setting.save()
|
||||
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0002_alter_team_organization'),
|
||||
('settings', '0002_usersettings'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_user_settings),
|
||||
]
|
||||
52
app/settings/models/user_settings.py
Normal file
52
app/settings/models/user_settings.py
Normal file
@ -0,0 +1,52 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
|
||||
from access.fields import *
|
||||
from access.models import TenancyObject, Organization
|
||||
|
||||
from core.mixin.history_save import SaveHistory
|
||||
|
||||
|
||||
class UserSettingsCommonFields(models.Model):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
id = models.AutoField(
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
blank=False
|
||||
)
|
||||
|
||||
slug = None
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
|
||||
class UserSettings(UserSettingsCommonFields):
|
||||
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
blank= False,
|
||||
)
|
||||
|
||||
|
||||
default_organization = models.ForeignKey(
|
||||
Organization,
|
||||
on_delete=models.DO_NOTHING,
|
||||
blank= True,
|
||||
default = None,
|
||||
null = True,
|
||||
)
|
||||
|
||||
def is_owner(self, user: int) -> bool:
|
||||
|
||||
if user == self.user:
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
50
app/settings/tests/user_settings/test_user_settings.py
Normal file
50
app/settings/tests/user_settings/test_user_settings.py
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_user_settings_on_create_of_user_settings_add():
|
||||
""" On Creation of a user their settings are created """
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_user_settings_on_delete_of_user_settings_removed():
|
||||
""" On Delete of a user their settings are removed """
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_user_settings_on_delete_organization_default_organization():
|
||||
""" On Delete of an organization, users default organization set to null """
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_user_settings_on_delete_organization_user_settings_not_deleted():
|
||||
""" On Delete of an organization, users settings are not deleted """
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_user_settings_only_owner_can_view():
|
||||
""" Only owner can access their settings url """
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_user_settings_only_owner_can_edit():
|
||||
""" Only owner and super admin can change user settings """
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_user_settings_super_admin_can_edit():
|
||||
""" Super admin can change user settings """
|
||||
pass
|
||||
|
||||
49
app/settings/views/user_settings.py
Normal file
49
app/settings/views/user_settings.py
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.urls import reverse
|
||||
from django.views import generic
|
||||
|
||||
from access.mixin import OrganizationPermission
|
||||
|
||||
from settings.models.user_settings import UserSettings
|
||||
|
||||
|
||||
|
||||
class View(generic.UpdateView):
|
||||
|
||||
context_object_name = "settings"
|
||||
|
||||
fields = [
|
||||
'default_organization',
|
||||
]
|
||||
|
||||
model = UserSettings
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
if self.object.is_owner(self.request.user):
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
raise PermissionDenied()
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('_settings_user', args=(self.kwargs['pk'],))
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
if self.object.is_owner(self.request.user):
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'User Settings'
|
||||
|
||||
return context
|
||||
|
||||
raise PermissionDenied()
|
||||
@ -15,9 +15,10 @@
|
||||
<body>
|
||||
|
||||
<header>
|
||||
|
||||
|
||||
<h1><a href="/" style="text-decoration: none; color: inherit;">{% settings_value "SITE_TITLE" %}</a></h1>
|
||||
|
||||
{%if user.is_authenticated %}
|
||||
<div class="dropdown" style="right: 0px; position: fixed; padding-right: 50px;">
|
||||
<button class="dropbtn">{% block user_name %}{%if user.username %}{{ user.username }}{% else %}My Account{% endif %}{% endblock %}</button>
|
||||
<div class="dropdown-content">
|
||||
@ -28,6 +29,10 @@
|
||||
<button class="accbtn">Admin Panel</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form action="{% url '_settings_user' pk=user_settings %}" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="accbtn">Settings</button>
|
||||
</form>
|
||||
<form action="{% url 'password_change' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="accbtn">Change Password</button>
|
||||
@ -38,6 +43,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</header>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user