diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 21a465e..7aa07c9 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,6 +5,7 @@
<<<<<<< HEAD
<<<<<<< HEAD
+<<<<<<< HEAD
=======
<<<<<<< HEAD
@@ -28,6 +29,10 @@
>>>>>>> 1-rbac
=======
+>>>>>>> 1-rbac
+=======
+
+
>>>>>>> 1-rbac
@@ -42,11 +47,12 @@
-
-
+
+
<<<<<<< HEAD
<<<<<<< HEAD
+<<<<<<< HEAD
<<<<<<< HEAD
@@ -65,14 +71,19 @@
=======
+>>>>>>> 1-rbac
+=======
+
+
>>>>>>> 1-rbac
-
+
+<<<<<<< HEAD
@@ -178,6 +189,8 @@
+=======
+>>>>>>> 1-rbac
@@ -200,34 +213,50 @@
@@ -244,12 +273,16 @@
=======
=======
+<<<<<<< HEAD
>>>>>>> 1-rbac
+>>>>>>> 1-rbac
+=======
+
>>>>>>> 1-rbac
-
-
+
+
@@ -296,12 +329,6 @@
-
-
-
-
-
-
@@ -327,9 +354,9 @@
+
-
@@ -400,6 +427,7 @@
<<<<<<< HEAD
+<<<<<<< HEAD
<<<<<<< HEAD
=======
@@ -407,28 +435,31 @@
>>>>>>> 1-rbac
=======
+>>>>>>> 1-rbac
+=======
+
>>>>>>> 1-rbac
-
+
-
+
-
+
-
+
-
+
@@ -442,124 +473,124 @@
-
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+<<<<<<< HEAD
<<<<<<< HEAD
@@ -582,42 +613,50 @@
=======
>>>>>>> 1-rbac
+=======
+
+>>>>>>> 1-rbac
-
-
+
+
-
+
-
-
+
+
-
-
+
-
-
+
+
-
+
<<<<<<< HEAD
+<<<<<<< HEAD
<<<<<<< HEAD
=======
+>>>>>>> 1-rbac
+=======
+
+
>>>>>>> 1-rbac
-
+
+<<<<<<< HEAD
<<<<<<< HEAD
@@ -648,14 +687,23 @@
=======
>>>>>>> 1-rbac
-
-
-
+=======
+
+
-
+
+>>>>>>> 1-rbac
+
+
+
+
+
+
+
+<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
@@ -674,35 +722,40 @@
=======
+>>>>>>> 1-rbac
+=======
+
+
>>>>>>> 1-rbac
-
+
-
-
+
+
-
+
-
-
+
+
-
+
+
-
-
-
+
+
-
+
<<<<<<< HEAD
+<<<<<<< HEAD
<<<<<<< HEAD
@@ -723,205 +776,173 @@
>>>>>>> a328e88122d80e72999261759f2594796f8da35b
+=======
+
+
+>>>>>>> 1-rbac
-
+
-
-
-
+
+
-
+
-
-
-
+
+
-
+
-
-
-
-
-
+
+
-
+
-
-
-
-
-
+
+
-
+
-
-
-
+
+
-
+
-
-
-
+
+
-
+
-
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+<<<<<<< HEAD
<<<<<<< HEAD
@@ -950,6 +971,8 @@
+=======
+>>>>>>> 1-rbac
diff --git a/apps/__pycache__/custom.cpython-36.pyc b/apps/__pycache__/custom.cpython-36.pyc
index 4c1262f..458ff71 100644
Binary files a/apps/__pycache__/custom.cpython-36.pyc and b/apps/__pycache__/custom.cpython-36.pyc differ
diff --git a/apps/custom.py b/apps/custom.py
index 67e2975..6c2e497 100644
--- a/apps/custom.py
+++ b/apps/custom.py
@@ -4,13 +4,45 @@
import json
-from django.views.generic import CreateView
+from django.views.generic import CreateView, UpdateView
from django.shortcuts import HttpResponse
+from django.http import Http404
from system.mixin import LoginRequiredMixin
+from system.models import Menu
-class SimpleInfoCreateView(LoginRequiredMixin, CreateView):
+class BreadcrumbMixin:
+
+ def get_context_data(self, **kwargs):
+ menu = Menu.get_menu_by_request_url(url=self.request.path_info)
+ if menu is not None:
+ kwargs.update(menu)
+ return super().get_context_data(**kwargs)
+
+
+class SandboxGetObjectMixin:
+
+ def get_object(self, queryset=None):
+
+ if queryset is None:
+ queryset = self.get_queryset()
+ if 'id' in self.request.GET and self.request.GET['id']:
+ queryset = queryset.filter(id=int(self.request.GET['id']))
+ elif 'id' in self.request.POST and self.request.POST['id']:
+ queryset = queryset.filter(id=int(self.request.POST['id']))
+ else:
+ raise AttributeError("Generic detail view %s must be called with id. "
+ % self.__class__.__name__)
+ try:
+ obj = queryset.get()
+ except queryset.model.DoesNotExist:
+ raise Http404("No %(verbose_name)s found matching the query" %
+ {'verbose_name': queryset.model._meta.verbose_name})
+ return obj
+
+
+class SandboxEditViewMixin:
def post(self, request, *args, **kwargs):
res = dict(result=False)
@@ -19,3 +51,17 @@ class SimpleInfoCreateView(LoginRequiredMixin, CreateView):
form.save()
res['result'] = True
return HttpResponse(json.dumps(res), content_type='application/json')
+
+
+class SandboxCreateView(LoginRequiredMixin, SandboxEditViewMixin, CreateView):
+ """"
+ View for create an object, with a response rendered by a template.
+ Returns information with Json when the data is created successfully or fails.
+ """
+
+
+class SandboxUpdateView(LoginRequiredMixin, SandboxEditViewMixin, SandboxGetObjectMixin, UpdateView):
+ """View for updating an object, with a response rendered by a template."""
+ def post(self, request, *args, **kwargs):
+ self.object = self.get_object()
+ return super().post(request, *args, **kwargs)
diff --git a/apps/system/__pycache__/forms.cpython-36.pyc b/apps/system/__pycache__/forms.cpython-36.pyc
index 56b28e7..86c7ea0 100644
Binary files a/apps/system/__pycache__/forms.cpython-36.pyc and b/apps/system/__pycache__/forms.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/middleware.cpython-36.pyc b/apps/system/__pycache__/middleware.cpython-36.pyc
new file mode 100644
index 0000000..33eb240
Binary files /dev/null and b/apps/system/__pycache__/middleware.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/models.cpython-36.pyc b/apps/system/__pycache__/models.cpython-36.pyc
index 5f4dd80..3a07064 100644
Binary files a/apps/system/__pycache__/models.cpython-36.pyc and b/apps/system/__pycache__/models.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/urls.cpython-36.pyc b/apps/system/__pycache__/urls.cpython-36.pyc
index 6bf5a5c..f15392e 100644
Binary files a/apps/system/__pycache__/urls.cpython-36.pyc and b/apps/system/__pycache__/urls.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/views.cpython-36.pyc b/apps/system/__pycache__/views.cpython-36.pyc
index 15a6daa..f3fd174 100644
Binary files a/apps/system/__pycache__/views.cpython-36.pyc and b/apps/system/__pycache__/views.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/views_menu.cpython-36.pyc b/apps/system/__pycache__/views_menu.cpython-36.pyc
index 11b3e31..da598de 100644
Binary files a/apps/system/__pycache__/views_menu.cpython-36.pyc and b/apps/system/__pycache__/views_menu.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/views_role.cpython-36.pyc b/apps/system/__pycache__/views_role.cpython-36.pyc
new file mode 100644
index 0000000..905c786
Binary files /dev/null and b/apps/system/__pycache__/views_role.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/views_structure.cpython-36.pyc b/apps/system/__pycache__/views_structure.cpython-36.pyc
index 2b4d80b..352da33 100644
Binary files a/apps/system/__pycache__/views_structure.cpython-36.pyc and b/apps/system/__pycache__/views_structure.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/views_user.cpython-36.pyc b/apps/system/__pycache__/views_user.cpython-36.pyc
index 4f7f5e7..3603868 100644
Binary files a/apps/system/__pycache__/views_user.cpython-36.pyc and b/apps/system/__pycache__/views_user.cpython-36.pyc differ
diff --git a/apps/system/middleware.py b/apps/system/middleware.py
new file mode 100644
index 0000000..7e3ec37
--- /dev/null
+++ b/apps/system/middleware.py
@@ -0,0 +1,100 @@
+import re
+
+from django.utils.deprecation import MiddlewareMixin
+from django.conf import settings
+from django.shortcuts import render
+
+
+class MenuCollection(MiddlewareMixin):
+
+ def get_user(self, request):
+ return request.user
+
+ def get_menu_from_role(self, request, user=None):
+ if user is None:
+ user = self.get_user(request)
+ try:
+ menus = user.roles.values(
+ 'permissions__id',
+ 'permissions__name',
+ 'permissions__url',
+ 'permissions__icon',
+ 'permissions__code',
+ 'permissions__parent'
+ ).distinct()
+ return [menu for menu in menus if menu['permissions__id'] is not None]
+ except AttributeError:
+ return None
+
+ def get_permission_url(self, request):
+ role_menus = self.get_menu_from_role(request)
+ if role_menus is not None:
+ permission_url_list = [menu['permissions__url'] for menu in role_menus]
+ return permission_url_list
+
+ def get_permission_menu(self, request):
+ permission_menu_list = []
+ role_menus = self.get_menu_from_role(request)
+ if role_menus is not None:
+ for item in role_menus:
+ menu = {
+ 'id': item['permissions__id'],
+ 'name': item['permissions__name'],
+ 'url': item['permissions__url'],
+ 'icon': item['permissions__icon'],
+ 'code': item['permissions__code'],
+ 'parent': item['permissions__parent'],
+ 'status': False,
+ 'sub_menu': [],
+ }
+ permission_menu_list.append(menu)
+ return permission_menu_list
+
+ def get_top_reveal_menu(self, request):
+ top_menu = []
+ permission_menu_dict = {}
+ request_url = request.path_info
+ permission_menu_list = self.get_permission_menu(request)
+ if permission_menu_list is not None:
+ for menu in permission_menu_list:
+
+ url = menu['url']
+ if url and re.match(url, request_url):
+ menu['status'] = True
+ if menu['parent'] is None:
+ top_menu.insert(0, menu)
+ permission_menu_dict[menu['id']] = menu
+
+ menu_data = []
+ for i in permission_menu_dict:
+ if permission_menu_dict[i]['parent']:
+ pid = permission_menu_dict[i]['parent']
+ parent_menu = permission_menu_dict[pid]
+ parent_menu['sub_menu'].append(permission_menu_dict[i])
+ else:
+ menu_data.append(permission_menu_dict[i])
+ if [menu['sub_menu'] for menu in menu_data if menu['url'] in request_url]:
+ reveal_menu = [menu['sub_menu'] for menu in menu_data if menu['url'] in request_url][0]
+ else:
+ reveal_menu = None
+ return top_menu, reveal_menu
+
+ def process_request(self, request):
+ if self.get_top_reveal_menu(request):
+ request.top_menu, request.reveal_menu = self.get_top_reveal_menu(request)
+ request.permission_url_list = self.get_permission_url(request)
+
+
+class RbacMiddleware(MiddlewareMixin):
+
+ def process_request(self, request):
+ if hasattr(request, 'permission_url_list'):
+ request_url = request.path_info
+ permission_url = request.permission_url_list
+ for url in settings.SAFE_URL:
+ if re.match(url, request_url):
+ return None
+ if request_url in permission_url:
+ return None
+ else:
+ return render(request, 'page404.html')
\ No newline at end of file
diff --git a/apps/system/migrations/0002_auto_20181115_2124.py b/apps/system/migrations/0002_auto_20181115_2124.py
new file mode 100644
index 0000000..acec7fa
--- /dev/null
+++ b/apps/system/migrations/0002_auto_20181115_2124.py
@@ -0,0 +1,22 @@
+# Generated by Django 2.1.2 on 2018-11-15 21:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('system', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='menu',
+ options={'ordering': ['number'], 'verbose_name': '菜单', 'verbose_name_plural': '菜单'},
+ ),
+ migrations.AddField(
+ model_name='menu',
+ name='number',
+ field=models.FloatField(blank=True, null=True, verbose_name='编号'),
+ ),
+ ]
diff --git a/apps/system/migrations/__pycache__/0002_auto_20181115_2124.cpython-36.pyc b/apps/system/migrations/__pycache__/0002_auto_20181115_2124.cpython-36.pyc
new file mode 100644
index 0000000..2f6d4f7
Binary files /dev/null and b/apps/system/migrations/__pycache__/0002_auto_20181115_2124.cpython-36.pyc differ
diff --git a/apps/system/models.py b/apps/system/models.py
index 74d1d67..9522c78 100644
--- a/apps/system/models.py
+++ b/apps/system/models.py
@@ -11,6 +11,7 @@ class Menu(models.Model):
icon = models.CharField(max_length=50, null=True, blank=True, verbose_name="图标")
code = models.CharField(max_length=50, null=True, blank=True, verbose_name="编码")
url = models.CharField(max_length=128, unique=True, null=True, blank=True)
+ number = models.FloatField(null=True, blank=True, verbose_name="编号")
def __str__(self):
return self.name
@@ -18,10 +19,14 @@ class Menu(models.Model):
class Meta:
verbose_name = '菜单'
verbose_name_plural = verbose_name
+ ordering = ['number']
@classmethod
def get_menu_by_request_url(cls, url):
- return dict(menu=Menu.objects.get(url=url))
+ try:
+ return dict(menu=Menu.objects.get(url=url))
+ except:
+ None
class Role(models.Model):
diff --git a/apps/system/urls.py b/apps/system/urls.py
index 47bfdb2..e7d858f 100644
--- a/apps/system/urls.py
+++ b/apps/system/urls.py
@@ -1,7 +1,7 @@
-from django.urls import path, re_path
+from django.urls import path
from .views import SystemView
-from . import views_structure, views_user, views_menu
+from . import views_structure, views_user, views_menu, views_role
app_name = 'system'
@@ -23,5 +23,16 @@ urlpatterns = [
path('basic/user/enable/', views_user.UserEnableView.as_view(), name='basic-user-enable'),
path('basic/user/disable/', views_user.UserDisableView.as_view(), name='basic-user-disable'),
+ path('rbac/menu/', views_menu.MenuListView.as_view(), name='rbac-menu'),
path('rbac/menu/create/', views_menu.MenuCreateView.as_view(), name='rbac-menu-create'),
+ path('rbac/menu/update/', views_menu.MenuUpdateView.as_view(), name='rbac-menu-update'),
+
+ path('rbac/role/', views_role.RoleView.as_view(), name='rbac-role'),
+ path('rbac/role/create/', views_role.RoleCreateView.as_view(), name='rbac-role-create'),
+ path('rbac/role/list/', views_role.RoleListView.as_view(), name='rbac-role-list'),
+ path('rbac/role/update/', views_role.RoleUpdateView.as_view(), name='rbac-role-update'),
+ path('rbac/role/delete/', views_role.RoleDeleteView.as_view(), name='rbac-role-delete'),
+ path('rbac/role/role2user/', views_role.Role2UserView.as_view(), name="rbac-role-role2user"),
+ path('rbac/role/role2menu/', views_role.Role2MenuView.as_view(), name="rbac-role-role2menu"),
+ path('rbac/role/role2menu_list/', views_role.Role2MenuListView.as_view(), name="rbac-role-role2menu_list"),
]
diff --git a/apps/system/views.py b/apps/system/views.py
index a2c097f..0ef5235 100644
--- a/apps/system/views.py
+++ b/apps/system/views.py
@@ -1,11 +1,10 @@
-from django.shortcuts import render
-from django.views.generic.base import View
+from django.views.generic import TemplateView
from .mixin import LoginRequiredMixin
+from custom import BreadcrumbMixin
-class SystemView(LoginRequiredMixin, View):
+class SystemView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
- def get(self, request):
- return render(request, 'system/system_index.html')
+ template_name = 'system/system_index.html'
diff --git a/apps/system/views_menu.py b/apps/system/views_menu.py
index f522b10..2857516 100644
--- a/apps/system/views_menu.py
+++ b/apps/system/views_menu.py
@@ -1,17 +1,29 @@
-# @Time : 2018/11/9 12:24
-# @Author : RobbieHan
-# @File : views.menu.py
+from django.views.generic import ListView
-from apps.custom import SimpleInfoCreateView
+from .mixin import LoginRequiredMixin
+from apps.custom import SandboxCreateView, SandboxUpdateView, BreadcrumbMixin
from .models import Menu
-class MenuCreateView(SimpleInfoCreateView):
+class MenuCreateView(SandboxCreateView):
model = Menu
fields = '__all__'
- extra_context = dict(menu_all=Menu.objects.all())
+
+ def get_context_data(self, **kwargs):
+ kwargs['menu_all'] = Menu.objects.all()
+ return super().get_context_data(**kwargs)
+class MenuListView(LoginRequiredMixin, BreadcrumbMixin, ListView):
+ model = Menu
+ context_object_name = 'menu_all'
+class MenuUpdateView(SandboxUpdateView):
+ model = Menu
+ fields = '__all__'
+ template_name_suffix = '_update'
+ def get_context_data(self, **kwargs):
+ kwargs['menu_all'] = Menu.objects.all()
+ return super().get_context_data(**kwargs)
diff --git a/apps/system/views_role.py b/apps/system/views_role.py
new file mode 100644
index 0000000..aecc746
--- /dev/null
+++ b/apps/system/views_role.py
@@ -0,0 +1,118 @@
+# @Time : 2018/11/13 23:25
+# @Author : RobbieHan
+# @File : views_role.py
+
+import json
+
+from django.views.generic.base import View
+from django.shortcuts import HttpResponse, get_object_or_404
+from django.views.generic import TemplateView
+from django.contrib.auth import get_user_model
+from django.shortcuts import render
+
+from .mixin import LoginRequiredMixin
+from .models import Role, Menu
+from custom import SandboxCreateView, SandboxUpdateView, BreadcrumbMixin
+
+User = get_user_model()
+
+
+class RoleView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
+ template_name = 'system/role.html'
+
+
+class RoleCreateView(SandboxCreateView):
+ model = Role
+ fields = '__all__'
+
+
+class RoleListView(LoginRequiredMixin, View):
+
+ def get(self, reqeust):
+ fields = ['id', 'name', 'desc']
+ ret = dict(data=list(Role.objects.values(*fields)))
+ return HttpResponse(json.dumps(ret), content_type='application/json')
+
+
+class RoleUpdateView(SandboxUpdateView):
+ model = Role
+ fields = '__all__'
+ template_name_suffix = '_update'
+
+
+class RoleDeleteView(LoginRequiredMixin, View):
+
+ def post(self, request):
+ ret = dict(result=False)
+ if 'id' in request.POST and request.POST['id']:
+ id_list = map(int, request.POST['id'].split(','))
+ Role.objects.filter(id__in=id_list).delete()
+ ret['result'] = True
+ return HttpResponse(json.dumps(ret), content_type='application/json')
+
+
+class Role2UserView(LoginRequiredMixin, View):
+ """
+ 角色关联用户
+ """
+
+ def get(self, request):
+ if 'id' in request.GET and request.GET['id']:
+ role = get_object_or_404(Role, pk=int(request.GET.get('id')))
+ added_users = role.userprofile_set.all()
+ all_users = User.objects.all()
+ un_add_users = set(all_users).difference(added_users)
+ ret = dict(role=role, added_users=added_users, un_add_users=list(un_add_users))
+ return render(request, 'system/role_role2user.html', ret)
+
+ def post(self, request):
+ res = dict(result=False)
+ id_list = None
+ role = get_object_or_404(Role, pk=int(request.POST.get('id')))
+ if 'to' in request.POST and request.POST['to']:
+ id_list = map(int, request.POST.getlist('to', []))
+ role.userprofile_set.clear()
+ if id_list:
+ for user in User.objects.filter(id__in=id_list):
+ role.userprofile_set.add(user)
+ res['result'] = True
+ return HttpResponse(json.dumps(res), content_type='application/json')
+
+
+class Role2MenuView(LoginRequiredMixin, View):
+ """
+ 角色绑定菜单
+ """
+ def get(self, request):
+ if 'id' in request.GET and request.GET['id']:
+ role = get_object_or_404(Role, pk=request.GET['id'])
+ ret = dict(role=role)
+ return render(request, 'system/role_role2menu.html', ret)
+
+ def post(self, request):
+ res = dict(result=False)
+ role = get_object_or_404(Role, pk=request.POST['id'])
+ tree = json.loads(self.request.POST['tree'])
+ role.permissions.clear()
+ for menu in tree:
+ if menu['checked'] is True:
+ menu_checked = get_object_or_404(Menu, pk=menu['id'])
+ role.permissions.add(menu_checked)
+ res['result'] = True
+ return HttpResponse(json.dumps(res), content_type='application/json')
+
+
+class Role2MenuListView(LoginRequiredMixin, View):
+ """
+ 获取zTree菜单列表
+ """
+ def get(self, request):
+ fields = ['id', 'name', 'parent']
+ if 'id' in request.GET and request.GET['id']:
+ role = Role.objects.get(id=request.GET.get('id'))
+ role_menus = role.permissions.values(*fields)
+ ret = dict(data=list(role_menus))
+ else:
+ menus = Menu.objects.all()
+ ret = dict(data=list(menus.values(*fields)))
+ return HttpResponse(json.dumps(ret), content_type='application/json')
\ No newline at end of file
diff --git a/apps/system/views_structure.py b/apps/system/views_structure.py
index a6f8f39..f99d5be 100644
--- a/apps/system/views_structure.py
+++ b/apps/system/views_structure.py
@@ -14,11 +14,12 @@ from django.contrib.auth import get_user_model
from .mixin import LoginRequiredMixin
from .models import Structure
from .forms import StructureForm
+from apps.custom import BreadcrumbMixin
User = get_user_model()
-class StructureView(LoginRequiredMixin, TemplateView):
+class StructureView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'system/structure/structure.html'
diff --git a/apps/system/views_user.py b/apps/system/views_user.py
index d575a31..2066631 100644
--- a/apps/system/views_user.py
+++ b/apps/system/views_user.py
@@ -17,6 +17,7 @@ from django.db.models import Q
from .forms import LoginForm, UserCreateForm, UserUpdateForm, PasswordChangeForm
from .mixin import LoginRequiredMixin
from .models import Structure, Role
+from apps.custom import BreadcrumbMixin
User = get_user_model()
@@ -63,7 +64,7 @@ class LogoutView(View):
return HttpResponseRedirect(reverse('login'))
-class UserView(LoginRequiredMixin, TemplateView):
+class UserView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'system/users/user.html'
diff --git a/db.sqlite3 b/db.sqlite3
index 4153a6b..537bf92 100644
Binary files a/db.sqlite3 and b/db.sqlite3 differ
diff --git a/requirements/dev b/requirements/dev
new file mode 100644
index 0000000..4643bc9
--- /dev/null
+++ b/requirements/dev
@@ -0,0 +1,2 @@
+django==2.1.2
+pillow==5.3.0
\ No newline at end of file
diff --git a/requirements/pro b/requirements/pro
new file mode 100644
index 0000000..4643bc9
--- /dev/null
+++ b/requirements/pro
@@ -0,0 +1,2 @@
+django==2.1.2
+pillow==5.3.0
\ No newline at end of file
diff --git a/sandboxMP/__pycache__/settings.cpython-36.pyc b/sandboxMP/__pycache__/settings.cpython-36.pyc
index bee31c1..cf9d0a2 100644
Binary files a/sandboxMP/__pycache__/settings.cpython-36.pyc and b/sandboxMP/__pycache__/settings.cpython-36.pyc differ
diff --git a/sandboxMP/settings.py b/sandboxMP/settings.py
index 30bcdb9..7520930 100644
--- a/sandboxMP/settings.py
+++ b/sandboxMP/settings.py
@@ -50,6 +50,8 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'apps.system.middleware.MenuCollection',
+ 'apps.system.middleware.RbacMiddleware',
]
ROOT_URLCONF = 'sandboxMP.urls'
@@ -132,3 +134,19 @@ MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
LOGIN_URL = '/login/'
+
+# safe url
+SAFE_URL = [r'^/$',
+ '/login/',
+ '/logout',
+ '/index/',
+ '/media/',
+ '/admin/',
+ '/ckeditor/',
+ ]
+
+# session timeout
+
+SESSION_COOKIE_AGE = 60 * 20
+SESSION_EXPIRE_AT_BROWSER_CLOSE = True
+SESSION_SAVE_EVERY_REQUEST = True
\ No newline at end of file
diff --git a/templates/base-left.html b/templates/base-left.html
index 2856245..f047efa 100644
--- a/templates/base-left.html
+++ b/templates/base-left.html
@@ -21,20 +21,32 @@
-