Admin permissions
This commit is contained in:
parent
31b0eed298
commit
5faa1e403a
19 changed files with 208 additions and 26 deletions
|
@ -1,10 +1,11 @@
|
|||
from django.urls import reverse
|
||||
|
||||
class NavItem:
|
||||
def __init__(self, name: str, icon: str, path: str):
|
||||
def __init__(self, name: str, icon: str, path: str, permissions: list=[]):
|
||||
self.__name = name
|
||||
self.__icon = icon
|
||||
self.__path = path
|
||||
self.__permissions = permissions
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -18,11 +19,16 @@ class NavItem:
|
|||
def path(self):
|
||||
return self.__path if (self.__path.startswith("/") or "://" in self.__path) else reverse(self.__path)
|
||||
|
||||
@property
|
||||
def permissions(self):
|
||||
return self.__permissions
|
||||
|
||||
class NavSection:
|
||||
def __init__(self, name: str, icon: str = ""):
|
||||
def __init__(self, name: str, icon: str = "", permissions: list=[]):
|
||||
self.__items = []
|
||||
self.__name = name
|
||||
self.__icon = icon
|
||||
self.__permissions = permissions
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -32,6 +38,10 @@ class NavSection:
|
|||
def icon(self):
|
||||
return self.__icon
|
||||
|
||||
@property
|
||||
def permissions(self):
|
||||
return self.__permissions
|
||||
|
||||
def add_item(self, item: NavItem):
|
||||
if not item in self.__items:
|
||||
self.__items.append(item)
|
||||
|
|
2
core/exceptions/auth.py
Normal file
2
core/exceptions/auth.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
class InsufficientPermissionsException(Exception):
|
||||
pass
|
|
@ -1,20 +1,36 @@
|
|||
from django.contrib.auth.mixins import AccessMixin
|
||||
from django.contrib.messages import error
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
|
||||
from core.models.profiles import AdminProfile
|
||||
from core.exceptions.auth import InsufficientPermissionsException
|
||||
|
||||
class AdminMixin(AccessMixin):
|
||||
permissions = []
|
||||
|
||||
@never_cache
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
self.permission_denied_message = "You must be logged in to access this area."
|
||||
|
||||
else:
|
||||
try:
|
||||
AdminProfile.objects.get(user=request.user)
|
||||
user_permissions = AdminProfile.objects.get(user=request.user).permissions
|
||||
for permission in self.permissions:
|
||||
if not permission in user_permissions:
|
||||
raise InsufficientPermissionsException(f"Missing permission: {permission}")
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
except AdminProfile.DoesNotExist:
|
||||
self.permission_denied_message = "You must be an administrator to access this area."
|
||||
except InsufficientPermissionsException:
|
||||
self.permission_denied_message = "You do not have the necessary permissions to access this page."
|
||||
|
||||
return self.handle_no_permission()
|
||||
return self.handle_no_permission()
|
||||
|
||||
def handle_no_permission(self):
|
||||
if self.raise_exception:
|
||||
raise PermissionDenied(self.get_permission_denied_message())
|
||||
|
||||
error(self.request, self.permission_denied_message)
|
||||
|
||||
return redirect_to_login(self.request.get_full_path(), self.get_login_url(), self.get_redirect_field_name())
|
|
@ -24,11 +24,23 @@ class AdminProfile(Profile):
|
|||
role = LongCharField()
|
||||
image = ImageField(null=True, blank=True, upload_to=generate_storage_filename)
|
||||
display_name = LongCharField("Internal Display Name", null=True, blank=True)
|
||||
permission_string = TextField(blank=True, default="")
|
||||
|
||||
@property
|
||||
def get_internal_name(self):
|
||||
return self.display_name if self.display_name else self.user.get_full_name
|
||||
|
||||
@property
|
||||
def permissions(self):
|
||||
return self.permission_string.split(",")
|
||||
|
||||
@permissions.setter
|
||||
def permissions(self, permissions):
|
||||
if isinstance(permissions, str):
|
||||
self.permission_string = permissions
|
||||
else:
|
||||
self.permission_string = ",".join(permissions)
|
||||
|
||||
class ClientProfile(Profile):
|
||||
company = LongCharField(null=True, blank=True)
|
||||
address1 = LongCharField()
|
||||
|
|
15
core/modules/permissions.py
Normal file
15
core/modules/permissions.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
import importlib
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
admin_permissions = []
|
||||
|
||||
for module in ["core"] + settings.EXPEPHALON_MODULES:
|
||||
try:
|
||||
mop = importlib.import_module(f"{module}.permissions")
|
||||
for admin_permission in mop.ADMIN_PERMISSIONS:
|
||||
admin_permissions.append(admin_permission)
|
||||
except (AttributeError, ModuleNotFoundError):
|
||||
continue
|
||||
|
||||
admin_permissions = tuple(admin_permissions)
|
97
core/permissions.py
Normal file
97
core/permissions.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
ADMIN_PERMISSIONS = []
|
||||
|
||||
# Client Administration Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_clients", "Read Clients"))
|
||||
ADMIN_PERMISSIONS.append(("modify_clients", "Modify Clients"))
|
||||
ADMIN_PERMISSIONS.append(("delete_clients", "Delete Clients"))
|
||||
ADMIN_PERMISSIONS.append(("create_clients", "Create Clients"))
|
||||
|
||||
# Client Group Administration Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_clientgroups", "Read Client Groups"))
|
||||
ADMIN_PERMISSIONS.append(("modify_clientgroups", "Modify Client Groups"))
|
||||
ADMIN_PERMISSIONS.append(("delete_clientgroups", "Delete Client Groups"))
|
||||
ADMIN_PERMISSIONS.append(("create_clientgroups", "Create Client Groups"))
|
||||
|
||||
# Quoting Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_quotes", "Read Quotes"))
|
||||
ADMIN_PERMISSIONS.append(("modify_quotes", "Modify Quotes"))
|
||||
ADMIN_PERMISSIONS.append(("delete_quotes", "Delete Quotes"))
|
||||
ADMIN_PERMISSIONS.append(("create_quotes", "Create Quotes"))
|
||||
|
||||
# Invoicing Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_invoices", "Read Invoices"))
|
||||
ADMIN_PERMISSIONS.append(("modify_invoices", "Modify Invoices"))
|
||||
ADMIN_PERMISSIONS.append(("delete_invoices", "Delete Invoices"))
|
||||
ADMIN_PERMISSIONS.append(("create_invoices", "Create Invoices"))
|
||||
|
||||
# Billable Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_billables", "Read Billable Items"))
|
||||
ADMIN_PERMISSIONS.append(("modify_billables", "Modify Billable Items"))
|
||||
ADMIN_PERMISSIONS.append(("delete_billables", "Delete Billable Items"))
|
||||
ADMIN_PERMISSIONS.append(("create_billables", "Create Billable Items"))
|
||||
|
||||
# Product Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_products", "Read Products"))
|
||||
ADMIN_PERMISSIONS.append(("modify_products", "Modify Products"))
|
||||
ADMIN_PERMISSIONS.append(("delete_products", "Delete Products"))
|
||||
ADMIN_PERMISSIONS.append(("create_products", "Create Products"))
|
||||
|
||||
# Product Group Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_productgroups", "Read Product Groups"))
|
||||
ADMIN_PERMISSIONS.append(("modify_productgroups", "Modify Product Groups"))
|
||||
ADMIN_PERMISSIONS.append(("delete_productgroups", "Delete Product Groups"))
|
||||
ADMIN_PERMISSIONS.append(("create_productgroups", "Create Product Groups"))
|
||||
|
||||
# Backend User Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_admins", "Read Administrators"))
|
||||
ADMIN_PERMISSIONS.append(("modify_admins", "Modify Administrators"))
|
||||
ADMIN_PERMISSIONS.append(("delete_admins", "Delete Administrators"))
|
||||
ADMIN_PERMISSIONS.append(("create_admins", "Create Administrators"))
|
||||
|
||||
# Brand Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_brands", "Read Brands"))
|
||||
ADMIN_PERMISSIONS.append(("modify_brands", "Modify Brands"))
|
||||
ADMIN_PERMISSIONS.append(("delete_brands", "Delete Brands"))
|
||||
ADMIN_PERMISSIONS.append(("create_brands", "Create Brands"))
|
||||
|
||||
# Firewall Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_firewall", "Read Firewall Rules"))
|
||||
ADMIN_PERMISSIONS.append(("modify_firewall", "Modify Firewall Rules"))
|
||||
ADMIN_PERMISSIONS.append(("delete_firewall", "Delete Firewall Rules"))
|
||||
ADMIN_PERMISSIONS.append(("create_firewall", "Create Firewall Rules"))
|
||||
|
||||
# Currency Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_currencies", "Read Currencies"))
|
||||
ADMIN_PERMISSIONS.append(("modify_currencies", "Modify Currencies"))
|
||||
ADMIN_PERMISSIONS.append(("delete_currencies", "Delete Currencies"))
|
||||
ADMIN_PERMISSIONS.append(("create_currencies", "Create Currencies"))
|
||||
|
||||
# Tax Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_taxes", "Read Taxes"))
|
||||
ADMIN_PERMISSIONS.append(("modify_taxes", "Modify Taxes"))
|
||||
ADMIN_PERMISSIONS.append(("delete_taxes", "Delete Taxes"))
|
||||
ADMIN_PERMISSIONS.append(("create_taxes", "Create Taxes"))
|
||||
|
||||
# Database Setting Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("read_dbsettings", "Read Database Settings"))
|
||||
ADMIN_PERMISSIONS.append(("modify_dbsettings", "Modify Database Settings"))
|
||||
ADMIN_PERMISSIONS.append(("delete_dbsettings", "Delete Database Settings"))
|
||||
ADMIN_PERMISSIONS.append(("create_dbsettings", "Create Database Settings"))
|
||||
|
||||
# Superuser Permissions
|
||||
|
||||
ADMIN_PERMISSIONS.append(("grant_permissions", "Grant Permissions"))
|
||||
ADMIN_PERMISSIONS.append(("grant_all_permissions", "Grant all Permissions"))
|
20
core/templatetags/permissions.py
Normal file
20
core/templatetags/permissions.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from django import template
|
||||
|
||||
from core.models.profiles import AdminProfile
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def admin_has_permissions(context, permissions):
|
||||
user = context["request"].user
|
||||
user_permissions = user.profile.permissions
|
||||
|
||||
if isinstance(permissions, str):
|
||||
permissions = permissions.split(",")
|
||||
|
||||
if isinstance(user.profile, AdminProfile):
|
||||
for permission in permissions:
|
||||
if not permission in user_permissions:
|
||||
return False
|
||||
return True
|
||||
return False
|
|
@ -5,18 +5,11 @@ from django.conf import settings
|
|||
from core.views.backend.dbsettings import *
|
||||
from core.views.auth import *
|
||||
from core.views.backend.profiles import *
|
||||
from core.views.generic import *
|
||||
from core.views.backend.generic import *
|
||||
from core.views.backend.brands import *
|
||||
from core.views.backend.firewall import *
|
||||
from core.views.backend.invoices import *
|
||||
|
||||
class DashboardView(BackendTemplateView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/index.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Dashboard"
|
||||
return context
|
||||
from core.views.backend.dashboard import *
|
||||
|
||||
class BackendNotImplementedView(BackendTemplateView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/notimplemented.html"
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.conf import settings
|
|||
from django.urls import reverse_lazy
|
||||
|
||||
from core.models.brands import Brand
|
||||
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
|
||||
from core.views.backend.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
|
||||
|
||||
class BrandListView(BackendListView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/brands/index.html"
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.conf import settings
|
|||
from django.urls import reverse_lazy
|
||||
|
||||
from core.models.local import Currency
|
||||
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
|
||||
from core.views.backend.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
|
||||
|
||||
class CurrencyListView(BackendListView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/currencies/index.html"
|
||||
|
|
11
core/views/backend/dashboard.py
Normal file
11
core/views/backend/dashboard.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from core.views.backend.generic import BackendTemplateView
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
class DashboardView(BackendTemplateView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/index.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["title"] = "Dashboard"
|
||||
return context
|
|
@ -2,7 +2,7 @@ from django.conf import settings
|
|||
from django.urls import reverse_lazy
|
||||
|
||||
from dbsettings.models import Setting
|
||||
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
|
||||
from core.views.backend.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
|
||||
|
||||
class DBSettingsListView(BackendListView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings/index.html"
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.conf import settings
|
|||
from django.urls import reverse_lazy
|
||||
|
||||
from core.models.auth import IPLimit
|
||||
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
|
||||
from core.views.backend.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
|
||||
|
||||
class RateLimitListView(BackendListView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/firewall/index.html"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django.views.generic import TemplateView, ListView, CreateView, FormView, DeleteView, UpdateView
|
||||
|
||||
from core.mixins.auth import AdminMixin
|
||||
|
||||
class BackendTemplateView(AdminMixin, TemplateView):
|
|
@ -2,7 +2,7 @@ from django.conf import settings
|
|||
from django.urls import reverse_lazy
|
||||
|
||||
from core.models.invoices import Invoice, InvoiceItem
|
||||
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
|
||||
from core.views.backend.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
|
||||
|
||||
class InvoiceListView(BackendListView):
|
||||
template_name = f"{settings.EXPEPHALON_BACKEND}/invoices/index.html"
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.contrib import messages
|
|||
|
||||
from core.models import AdminProfile, ClientProfile, ClientGroup
|
||||
from core.forms.profiles import AdminEditForm, AdminCreateForm, ClientForm
|
||||
from core.views.generic import BackendFormView, BackendListView, BackendDeleteView, BackendUpdateView, BackendCreateView
|
||||
from core.views.backend.generic import BackendFormView, BackendListView, BackendDeleteView, BackendUpdateView, BackendCreateView
|
||||
from core.helpers.auth import request_password
|
||||
|
||||
### AdminProfiles
|
||||
|
|
|
@ -175,7 +175,7 @@ LOGGING = {
|
|||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
|
||||
'format': '%(levelname)s %(asctime)s %(name)s %(process)d %(thread)d %(message)s'
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(levelname)s %(message)s'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{% load static %}
|
||||
{% load navigation %}
|
||||
{% load dbsetting %}
|
||||
{% load permissions %}
|
||||
{% dbsetting "core.title" as sitetitle %}
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
@ -161,6 +162,8 @@
|
|||
<ul class="vertical-nav-menu">
|
||||
{% get_nav_sections_by_name "backend_main" as nav %}
|
||||
{% for section in nav %}
|
||||
{% admin_has_permissions section.permissions as check %}
|
||||
{% if check %}
|
||||
<li class="app-sidebar__heading">{{ section.name }}</li>
|
||||
{% for item in section.items %}
|
||||
<li>
|
||||
|
@ -170,6 +173,7 @@
|
|||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "backend/base.html" %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<div class="app-page-title">
|
||||
<div class="page-title-wrapper">
|
||||
|
@ -86,7 +87,7 @@
|
|||
<div class="widget-content p-0">
|
||||
<div class="widget-content-wrapper">
|
||||
<div class="widget-content-left mr-3">
|
||||
<img width="42" class="rounded-circle" src="assets/images/avatars/9.jpg" alt="">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
|
||||
</div>
|
||||
<div class="widget-content-left">
|
||||
<div class="widget-heading">Ella-Rose Henry</div>
|
||||
|
@ -108,7 +109,7 @@
|
|||
<div class="widget-content p-0">
|
||||
<div class="widget-content-wrapper">
|
||||
<div class="widget-content-left mr-3">
|
||||
<img width="42" class="rounded-circle" src="assets/images/avatars/5.jpg" alt="">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
|
||||
</div>
|
||||
<div class="widget-content-left">
|
||||
<div class="widget-heading">Ruben Tillman</div>
|
||||
|
@ -130,7 +131,7 @@
|
|||
<div class="widget-content p-0">
|
||||
<div class="widget-content-wrapper">
|
||||
<div class="widget-content-left mr-3">
|
||||
<img width="42" class="rounded-circle" src="assets/images/avatars/4.jpg" alt="">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
|
||||
</div>
|
||||
<div class="widget-content-left">
|
||||
<div class="widget-heading">Vinnie Wagstaff</div>
|
||||
|
@ -152,7 +153,7 @@
|
|||
<div class="widget-content p-0">
|
||||
<div class="widget-content-wrapper">
|
||||
<div class="widget-content-left mr-3">
|
||||
<img width="42" class="rounded-circle" src="assets/images/avatars/3.jpg" alt="">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
|
||||
</div>
|
||||
<div class="widget-content-left">
|
||||
<div class="widget-heading">Ella-Rose Henry</div>
|
||||
|
@ -174,7 +175,7 @@
|
|||
<div class="widget-content p-0">
|
||||
<div class="widget-content-wrapper">
|
||||
<div class="widget-content-left mr-3">
|
||||
<img width="42" class="rounded-circle" src="assets/images/avatars/2.jpg" alt="">
|
||||
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
|
||||
</div>
|
||||
<div class="widget-content-left">
|
||||
<div class="widget-heading">Ruben Tillman</div>
|
||||
|
|
Loading…
Reference in a new issue