gmcm-django-superadmin 2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gmcm_django_superadmin-2.2.0.dist-info/LICENCE +19 -0
- gmcm_django_superadmin-2.2.0.dist-info/METADATA +53 -0
- gmcm_django_superadmin-2.2.0.dist-info/RECORD +54 -0
- gmcm_django_superadmin-2.2.0.dist-info/WHEEL +6 -0
- gmcm_django_superadmin-2.2.0.dist-info/top_level.txt +1 -0
- superadmin/__init__.py +12 -0
- superadmin/admin.py +46 -0
- superadmin/apps.py +14 -0
- superadmin/checks.py +45 -0
- superadmin/context_processors.py +53 -0
- superadmin/decorators.py +33 -0
- superadmin/forms.py +66 -0
- superadmin/management/__init__.py +0 -0
- superadmin/management/commands/__init__.py +0 -0
- superadmin/management/commands/base.py +148 -0
- superadmin/management/commands/createactions.py +13 -0
- superadmin/management/commands/createbasemenu.py +57 -0
- superadmin/management/commands/createpermission.py +0 -0
- superadmin/management/commands/menu.py +40 -0
- superadmin/migrations/0001_action_menu.py +134 -0
- superadmin/migrations/0002_auto_20211106_0726.py +17 -0
- superadmin/migrations/__init__.py +0 -0
- superadmin/migrations/unaccent_extension.py +8 -0
- superadmin/mixins/__init__.py +6 -0
- superadmin/mixins/breadcrumbs.py +102 -0
- superadmin/mixins/filters.py +105 -0
- superadmin/mixins/inlines.py +90 -0
- superadmin/mixins/permissions.py +52 -0
- superadmin/mixins/templates.py +5 -0
- superadmin/mixins/urls.py +16 -0
- superadmin/mixins/utils.py +35 -0
- superadmin/models.py +152 -0
- superadmin/options.py +301 -0
- superadmin/services.py +324 -0
- superadmin/settings.py +33 -0
- superadmin/shortcuts.py +68 -0
- superadmin/signals.py +82 -0
- superadmin/sites.py +175 -0
- superadmin/templatetags/__init__.py +0 -0
- superadmin/templatetags/superadmin_forms.py +554 -0
- superadmin/templatetags/superadmin_utils.py +65 -0
- superadmin/tests.py +3 -0
- superadmin/utils.py +83 -0
- superadmin/views/__init__.py +10 -0
- superadmin/views/base.py +91 -0
- superadmin/views/create.py +106 -0
- superadmin/views/delete.py +80 -0
- superadmin/views/detail.py +135 -0
- superadmin/views/duplicate.py +15 -0
- superadmin/views/export.py +86 -0
- superadmin/views/filter.py +116 -0
- superadmin/views/filtering.py +322 -0
- superadmin/views/list.py +176 -0
- superadmin/views/update.py +128 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2020, Denis Siavichay
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: gmcm-django-superadmin
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: A Django app for build admin sites like django admin.
|
|
5
|
+
Home-page: https://gitlab.com/mmorona/gmcm-django-superadmin
|
|
6
|
+
Author: Denis Siavichay
|
|
7
|
+
Author-email: dbsiavichay@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Environment :: Web Environment
|
|
12
|
+
Classifier: Framework :: Django
|
|
13
|
+
Classifier: Framework :: Django :: 3.2
|
|
14
|
+
Classifier: Framework :: Django :: 4.0
|
|
15
|
+
Classifier: Framework :: Django :: 4.1
|
|
16
|
+
Classifier: Framework :: Django :: 4.2
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
27
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
28
|
+
Requires-Python: >=3.8
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENCE
|
|
31
|
+
Requires-Dist: Django>=3.2
|
|
32
|
+
Requires-Dist: pyyaml
|
|
33
|
+
|
|
34
|
+
Superadmin
|
|
35
|
+
=====
|
|
36
|
+
|
|
37
|
+
Superadmin is a Django app based in django admin to build admin sites quickly and easily, and generate site page like django admin.
|
|
38
|
+
|
|
39
|
+
Detailed documentation is in the "docs" directory.
|
|
40
|
+
|
|
41
|
+
Quick start
|
|
42
|
+
-----------
|
|
43
|
+
|
|
44
|
+
1. Add "superadmin" to your INSTALLED_APPS setting like this::
|
|
45
|
+
|
|
46
|
+
INSTALLED_APPS = [
|
|
47
|
+
...
|
|
48
|
+
'superadmin',
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
3. Run ``python manage.py migrate`` to create the superadmin migrations.
|
|
52
|
+
|
|
53
|
+
4. Continue
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
superadmin/__init__.py,sha256=xY_ZGbpqe8k2Gnbzp3zoJhICotPGaVatPcmNHpfhmO8,288
|
|
2
|
+
superadmin/admin.py,sha256=jLBfNlcrVGhi-kboYcYfPecMH_jI5O9soaN3KtjDLnA,1297
|
|
3
|
+
superadmin/apps.py,sha256=umBpp6E-RYaIxdIN9Z2ZUwu4dJr7cmrpKGxBKo8oInc,263
|
|
4
|
+
superadmin/checks.py,sha256=sJj9FTQ1Ra_vN9MA6JbPODwa1DOrU96v9NWbD9HAt_Y,1590
|
|
5
|
+
superadmin/context_processors.py,sha256=IZydf6pEaH5dCDZrSRxrp9bZAt6sKJPYdVMwrJIVPdY,1750
|
|
6
|
+
superadmin/decorators.py,sha256=b0uNNMuCR3nnbxgYhI-F3WlMqVKUHo4RNGXG2I5SmaA,1029
|
|
7
|
+
superadmin/forms.py,sha256=Kxr8JCjC2PQtK-NM7ef6qkQ8CwdQtudeqCLXJvdM6No,2239
|
|
8
|
+
superadmin/models.py,sha256=LjGVmE0O7NfwUog7pTJXINUCUgqeMlbOpOoo_wQUghU,4889
|
|
9
|
+
superadmin/options.py,sha256=cpsaW_n-ADU-rieBPIP8U1hkxX6osDluOyfeNE70Udg,10831
|
|
10
|
+
superadmin/services.py,sha256=l9UHXNNaTWm_mD5v6PHpPC05yTs1qK2d6Zly8FCzdXA,11726
|
|
11
|
+
superadmin/settings.py,sha256=JscpGBTwCIaunP-R1bxle6TV-cZemgWKTNtAeGr_Ab0,1427
|
|
12
|
+
superadmin/shortcuts.py,sha256=FQaZ_8HN_CyyiGjcPurixhepAzFK48m8HGH2O8phbGE,2141
|
|
13
|
+
superadmin/signals.py,sha256=mfKBYjA5IOvUrnrwkGpTP5r9fURjvyWR_ZxWoEdorsc,2140
|
|
14
|
+
superadmin/sites.py,sha256=K-ufuO8DR4cVr3mgGYso3wCyM0SIjom1M3GRcewWBUU,5794
|
|
15
|
+
superadmin/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
|
|
16
|
+
superadmin/utils.py,sha256=Qo3_I2YnSa2cOcf00qvArb8-1j5A-ZWe-XZgUl2rYkE,1971
|
|
17
|
+
superadmin/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
superadmin/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
superadmin/management/commands/base.py,sha256=uUike50nua9MvGqqS_YakCNQ3adhOR86W0zb5zswItM,4454
|
|
20
|
+
superadmin/management/commands/createactions.py,sha256=ESOhhP7LaAoM-MrcNFmQ4g7vWo91TBUegM0hgpmw2eU,340
|
|
21
|
+
superadmin/management/commands/createbasemenu.py,sha256=_0bToDMIU8vbskPPsA1VbNswrTDOq5MQcxRF6oXCnUY,1606
|
|
22
|
+
superadmin/management/commands/createpermission.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
superadmin/management/commands/menu.py,sha256=165h7E5lX8EsD7DNX0f5m7Sjw3HvDN81rtU4_FNVzAA,1183
|
|
24
|
+
superadmin/migrations/0001_action_menu.py,sha256=ZlpbYuujXZ8o2bw0HYYMJcSFx2YDJry1D20N0TGkmFY,4350
|
|
25
|
+
superadmin/migrations/0002_auto_20211106_0726.py,sha256=LQ5DHhZCOyvfv4PiBvrtD8C2wvZYJALVCJjDBGXY0zM,345
|
|
26
|
+
superadmin/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
superadmin/migrations/unaccent_extension.py,sha256=EC5U9eVnqrjvms0oHfxclqytxLJKhrn4lj90Doi8Pus,227
|
|
28
|
+
superadmin/mixins/__init__.py,sha256=TkKqewDJOq065DsCIiaNFkZiibMFTkm80AXgUjA6fIM,221
|
|
29
|
+
superadmin/mixins/breadcrumbs.py,sha256=RKesyhkQSLehJcNW3zWHO31LGUJHK6j6hne66FcSuFM,3200
|
|
30
|
+
superadmin/mixins/filters.py,sha256=pbtszUEB9hYuqpId8zFegmOxaGRGEK1WsD4zzBF-Nzc,3666
|
|
31
|
+
superadmin/mixins/inlines.py,sha256=n-ffyn5Mv50ombBXRpkLX_51qe3KECgXq_RHWnHgCrk,2928
|
|
32
|
+
superadmin/mixins/permissions.py,sha256=WCLYXv9gS1WKA4zzCqG2hOzTIyu1u-8bx64IwTY9yd4,1784
|
|
33
|
+
superadmin/mixins/templates.py,sha256=L2MZ4MvXcNgHxDVBCmIN_pl7yQA_3MqsSBnWfF4wju0,234
|
|
34
|
+
superadmin/mixins/urls.py,sha256=s2M5Xe4kh0hv7EuMdC0UtW7MoA7s3j2V6O3bGukLrUo,517
|
|
35
|
+
superadmin/mixins/utils.py,sha256=mL4sAwYzYRKxbj4RftPHReXfku3ZO45xaZZhxxcTVgE,1205
|
|
36
|
+
superadmin/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
+
superadmin/templatetags/superadmin_forms.py,sha256=DWfEc2T6ECzGz7VuFCObpUhAQDTSPY3xq5_F2tiJ9Rs,14325
|
|
38
|
+
superadmin/templatetags/superadmin_utils.py,sha256=46oAASKfQNtoWi-oy_UcSDv37QM7z_nkcNasKQxXHlg,1676
|
|
39
|
+
superadmin/views/__init__.py,sha256=V2GwZb5ZBCZ03OEGMOYLppvTQKkT6Vf_xZS9Pz0apPA,401
|
|
40
|
+
superadmin/views/base.py,sha256=YvZRqJd7QUb2kJaY5By73Yco0ug9Cm7TkFJfh4G7iyw,2843
|
|
41
|
+
superadmin/views/create.py,sha256=i48oqCCRu5O3dkNUmARK3K981KbkAwnX3uortzxEG3I,3944
|
|
42
|
+
superadmin/views/delete.py,sha256=qqMcItsBukRn2ktQF0N_vHMzI6XAPlZ7oWcreOX-cMc,2643
|
|
43
|
+
superadmin/views/detail.py,sha256=ncvI7bbjJGsGQO2PL5NV23M3w00Gq77nFgamsJjU9x8,4598
|
|
44
|
+
superadmin/views/duplicate.py,sha256=O1Ug4BqNPPD1K7va4lXu6gACRLPSas3ZfoQNkJ6u63k,459
|
|
45
|
+
superadmin/views/export.py,sha256=J_Ccj70kNtvluN6iCP1yjKI0eSinZ19xGVpyiw1ruZk,3117
|
|
46
|
+
superadmin/views/filter.py,sha256=XcvAo2JgXZOSyGU8LO5gYcFpzdU6smnFdNG3VlrDZY0,3915
|
|
47
|
+
superadmin/views/filtering.py,sha256=JiG504RnbS_dbWvMYlftTpBxMegguhZwEP1242rcsbU,11123
|
|
48
|
+
superadmin/views/list.py,sha256=w3DXKpmBcGnlMIMFOP_OgcUCKfQpRkg5zg3AyO2Ji6M,6108
|
|
49
|
+
superadmin/views/update.py,sha256=RHtzOxS2ONz1ytfl_5YgMYrmuVpzefeJ3X9uM-Vt4sw,4603
|
|
50
|
+
gmcm_django_superadmin-2.2.0.dist-info/LICENCE,sha256=vq6PaqQ09rc6JhaJG-B76D2YSnTKzvaDkOsU8zmdWDk,1059
|
|
51
|
+
gmcm_django_superadmin-2.2.0.dist-info/METADATA,sha256=jSt2NPBAY-FTu3sRrC-qz1Y5UH8SgaYs2ByOx3LCSwQ,1716
|
|
52
|
+
gmcm_django_superadmin-2.2.0.dist-info/WHEEL,sha256=OpXWERl2xLPRHTvd2ZXo_iluPEQd8uSbYkJ53NAER_Y,109
|
|
53
|
+
gmcm_django_superadmin-2.2.0.dist-info/top_level.txt,sha256=EPWyLOWlKsTG6m9K_ksHGTV1b_j9Rv51T_lmcYjZMJI,11
|
|
54
|
+
gmcm_django_superadmin-2.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
superadmin
|
superadmin/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
""" Hydra init config """
|
|
2
|
+
from .sites import site
|
|
3
|
+
from .options import ModelSite
|
|
4
|
+
from django.utils.module_loading import autodiscover_modules
|
|
5
|
+
|
|
6
|
+
__all__ = ["site", "ModelSite"]
|
|
7
|
+
|
|
8
|
+
default_app_config = "superadmin.apps.SuperAdminConfig"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def autodiscover():
|
|
12
|
+
autodiscover_modules("sites")
|
superadmin/admin.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
""" Hydra model admin """
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Django
|
|
6
|
+
from django.contrib import admin
|
|
7
|
+
from django.contrib.auth.models import Permission
|
|
8
|
+
from django.contrib.contenttypes.models import ContentType
|
|
9
|
+
|
|
10
|
+
# Models
|
|
11
|
+
from hydra.models import Action, Menu
|
|
12
|
+
|
|
13
|
+
# Forms
|
|
14
|
+
from hydra.forms import BaseActionForm, BaseMenuForm, BasePermissionForm
|
|
15
|
+
|
|
16
|
+
@admin.register(Action)
|
|
17
|
+
class ActionAdmin(admin.ModelAdmin):
|
|
18
|
+
form = BaseActionForm
|
|
19
|
+
list_display = ("__str__", "app_label")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@admin.register(Menu)
|
|
23
|
+
class MenuAdmin(admin.ModelAdmin):
|
|
24
|
+
form = BaseMenuForm
|
|
25
|
+
list_display = ('__str__', 'action',)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@admin.register(Permission)
|
|
29
|
+
class PermisionAdmin(admin.ModelAdmin):
|
|
30
|
+
model = Permission
|
|
31
|
+
form = BasePermissionForm
|
|
32
|
+
list_display = ("name", "codename", "__str__")
|
|
33
|
+
|
|
34
|
+
def __init__(self, model, admin_site):
|
|
35
|
+
self.model._meta.verbose_name = "permiso específico"
|
|
36
|
+
self.model._meta.verbose_name_plural = "permisos específicos"
|
|
37
|
+
super().__init__(model, admin_site)
|
|
38
|
+
|
|
39
|
+
def get_queryset(self, request):
|
|
40
|
+
qs = super().get_queryset(request)
|
|
41
|
+
ct = ContentType.objects.get_for_model(self.model)
|
|
42
|
+
codenames = ("add_permission","change_permission","delete_permission","view_permission")
|
|
43
|
+
qs = qs.filter(content_type=ct).exclude(codename__in=codenames)
|
|
44
|
+
return qs
|
|
45
|
+
|
|
46
|
+
"""
|
superadmin/apps.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
""" Hydra Apps config """
|
|
2
|
+
|
|
3
|
+
# Django
|
|
4
|
+
from django.apps import AppConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SuperAdminConfig(AppConfig):
|
|
8
|
+
name = "superadmin"
|
|
9
|
+
|
|
10
|
+
def ready(self):
|
|
11
|
+
from . import signals
|
|
12
|
+
from . import checks # noqa: F401
|
|
13
|
+
|
|
14
|
+
self.module.autodiscover()
|
superadmin/checks.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""System checks that validate ModelSite field configuration at startup.
|
|
2
|
+
|
|
3
|
+
These surface typos in ``list_fields`` / ``detail_fields`` as ``manage.py check``
|
|
4
|
+
warnings instead of letting them blow up with an ``AttributeError`` in front of
|
|
5
|
+
the user at request time.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Django
|
|
9
|
+
from django.contrib.admin.utils import flatten
|
|
10
|
+
from django.core.checks import Warning, register
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _detail_field_names(detail_fields):
|
|
14
|
+
if isinstance(detail_fields, dict):
|
|
15
|
+
names = []
|
|
16
|
+
for fieldset in detail_fields.values():
|
|
17
|
+
names += flatten(fieldset)
|
|
18
|
+
return names
|
|
19
|
+
return flatten(detail_fields or ())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@register()
|
|
23
|
+
def check_model_sites(app_configs, **kwargs):
|
|
24
|
+
from .services import FieldService
|
|
25
|
+
from .sites import site
|
|
26
|
+
|
|
27
|
+
errors = []
|
|
28
|
+
for model, model_site in site._registry.items():
|
|
29
|
+
fields = list(getattr(model_site, "list_fields", ()) or ())
|
|
30
|
+
fields += _detail_field_names(getattr(model_site, "detail_fields", ()))
|
|
31
|
+
for field in fields:
|
|
32
|
+
if not isinstance(field, str):
|
|
33
|
+
continue
|
|
34
|
+
try:
|
|
35
|
+
FieldService.get_field_label(model, field)
|
|
36
|
+
except AttributeError as error:
|
|
37
|
+
errors.append(
|
|
38
|
+
Warning(
|
|
39
|
+
"Invalid field %r in %s for model %s: %s"
|
|
40
|
+
% (field, model_site.__class__.__name__, model.__name__, error),
|
|
41
|
+
hint="Check the list_fields / detail_fields of the ModelSite.",
|
|
42
|
+
id="superadmin.W001",
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
return errors
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Django
|
|
2
|
+
from django.core.cache import cache
|
|
3
|
+
|
|
4
|
+
# Models
|
|
5
|
+
from .models import Menu
|
|
6
|
+
from . import settings as superadmin_settings
|
|
7
|
+
from .utils import get_menu_version, get_user_menu
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def menu(request):
|
|
11
|
+
return {"menu_tree": get_cached_user_menu(request.user)}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_cached_user_menu(user):
|
|
15
|
+
if not user.is_authenticated or not user.is_active:
|
|
16
|
+
return []
|
|
17
|
+
key = "superadmin:menu:%s:%s" % (get_menu_version(), user.pk)
|
|
18
|
+
menu_tree = cache.get(key)
|
|
19
|
+
if menu_tree is None:
|
|
20
|
+
menu_tree = build_user_menu(user)
|
|
21
|
+
cache.set(key, menu_tree, superadmin_settings.MENU_CACHE_TIMEOUT)
|
|
22
|
+
return menu_tree
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_user_menu(user):
|
|
26
|
+
if not user.is_authenticated or not user.is_active:
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
user_perms = set(user.get_all_permissions())
|
|
30
|
+
object_list = (
|
|
31
|
+
Menu.objects.filter(parent__isnull=True, is_active=True)
|
|
32
|
+
.select_related("action", "parent")
|
|
33
|
+
.prefetch_related(
|
|
34
|
+
"submenus",
|
|
35
|
+
"submenus__action",
|
|
36
|
+
"submenus__parent",
|
|
37
|
+
"submenus__submenus",
|
|
38
|
+
"submenus__submenus__action",
|
|
39
|
+
"submenus__submenus__parent",
|
|
40
|
+
"submenus__submenus__submenus",
|
|
41
|
+
"submenus__submenus__submenus__action",
|
|
42
|
+
"submenus__submenus__submenus__parent",
|
|
43
|
+
"submenus__submenus__submenus__submenus",
|
|
44
|
+
"submenus__submenus__submenus__submenus__action",
|
|
45
|
+
"submenus__submenus__submenus__submenus__parent",
|
|
46
|
+
"submenus__submenus__submenus__submenus__submenus",
|
|
47
|
+
"submenus__submenus__submenus__submenus__submenus__action",
|
|
48
|
+
"submenus__submenus__submenus__submenus__submenus__parent",
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
return get_user_menu(object_list, user, user_perms)
|
|
52
|
+
|
|
53
|
+
|
superadmin/decorators.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from django.apps import apps
|
|
2
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def register(model):
|
|
6
|
+
"""
|
|
7
|
+
Register the given model(s) classes and wrapped ModelSite class:
|
|
8
|
+
@register(Author)
|
|
9
|
+
class AuthorSite(superadmin.ModelSite):
|
|
10
|
+
pass
|
|
11
|
+
"""
|
|
12
|
+
from superadmin import ModelSite, site
|
|
13
|
+
|
|
14
|
+
def _model_site_wrapper(site_class):
|
|
15
|
+
if not model:
|
|
16
|
+
raise ValueError("One model must be passed to register.")
|
|
17
|
+
|
|
18
|
+
model_class = None
|
|
19
|
+
if isinstance(model, str):
|
|
20
|
+
try:
|
|
21
|
+
app_name, model_name = model.split(".")
|
|
22
|
+
model_class = apps.get_model(app_name, model_name)
|
|
23
|
+
except ValueError:
|
|
24
|
+
raise ImproperlyConfigured("Does not exist '%s' model" % model)
|
|
25
|
+
|
|
26
|
+
if not issubclass(site_class, ModelSite):
|
|
27
|
+
raise ValueError("Wrapped class must subclass ModelSite.")
|
|
28
|
+
|
|
29
|
+
site.register(model_class if model_class else model, site_class=site_class)
|
|
30
|
+
|
|
31
|
+
return site_class
|
|
32
|
+
|
|
33
|
+
return _model_site_wrapper
|
superadmin/forms.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
from functools import reduce
|
|
3
|
+
|
|
4
|
+
# Django
|
|
5
|
+
from django.forms import BaseModelForm
|
|
6
|
+
from django.forms.models import ModelFormMetaclass as DjangoModelFormMetaclass
|
|
7
|
+
from django.contrib.admin.utils import flatten
|
|
8
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ModelFormMetaclass(DjangoModelFormMetaclass):
|
|
12
|
+
def __new__(mcs, name, bases, attrs):
|
|
13
|
+
fieldsets = None
|
|
14
|
+
if "Meta" in attrs and hasattr(attrs["Meta"], "fieldsets"):
|
|
15
|
+
fieldsets = attrs["Meta"].fieldsets
|
|
16
|
+
fields = mcs.__fields__(fieldsets)
|
|
17
|
+
if hasattr(attrs["Meta"], "fields"):
|
|
18
|
+
fields = fields + attrs["Meta"].fields
|
|
19
|
+
attrs["Meta"].fields = fields
|
|
20
|
+
new_class = super().__new__(mcs, name, bases, attrs)
|
|
21
|
+
if fieldsets:
|
|
22
|
+
new_class._meta.fieldsets = fieldsets
|
|
23
|
+
return new_class
|
|
24
|
+
|
|
25
|
+
def __fields__(fieldsets):
|
|
26
|
+
if isinstance(fieldsets, (list, tuple)):
|
|
27
|
+
fields = flatten(fieldsets)
|
|
28
|
+
elif isinstance(fieldsets, dict):
|
|
29
|
+
fields = reduce(
|
|
30
|
+
lambda acc, fieldset: acc + flatten(fieldset), fieldsets.values(), []
|
|
31
|
+
)
|
|
32
|
+
else:
|
|
33
|
+
raise ImproperlyConfigured(
|
|
34
|
+
"The fieldsets must be an instance of list, tuple or dict"
|
|
35
|
+
)
|
|
36
|
+
return tuple(fields)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
|
|
40
|
+
def parse(self, fieldset):
|
|
41
|
+
def wrap(fields):
|
|
42
|
+
fields = fields if isinstance(fields, (list, tuple)) else [fields]
|
|
43
|
+
return {
|
|
44
|
+
"bs_cols": int(12 / len(fields)),
|
|
45
|
+
"fields": [self[field] for field in fields],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fieldset_list = list(map(wrap, fieldset))
|
|
49
|
+
return fieldset_list
|
|
50
|
+
|
|
51
|
+
def get_fieldsets(self):
|
|
52
|
+
fieldsets_list = self._meta.fieldsets
|
|
53
|
+
fieldsets = (
|
|
54
|
+
[(None, fieldsets_list)]
|
|
55
|
+
if isinstance(fieldsets_list, (list, tuple))
|
|
56
|
+
else fieldsets_list.items()
|
|
57
|
+
)
|
|
58
|
+
fieldsets = [
|
|
59
|
+
{"title": title or "", "fieldset": self.parse(fieldset)}
|
|
60
|
+
for title, fieldset in fieldsets
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
return fieldsets
|
|
64
|
+
|
|
65
|
+
def has_fieldsets(self):
|
|
66
|
+
return hasattr(self._meta, "fieldsets")
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
import inspect
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
|
|
5
|
+
# Django
|
|
6
|
+
from django.db.models import Q
|
|
7
|
+
from django.contrib.auth.models import Permission
|
|
8
|
+
from django.contrib.contenttypes.models import ContentType
|
|
9
|
+
from django.views.generic import View
|
|
10
|
+
from django.apps import apps
|
|
11
|
+
|
|
12
|
+
# Models
|
|
13
|
+
from ...models import Action, Menu
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_views_catalog():
|
|
17
|
+
module_name = "django.views"
|
|
18
|
+
|
|
19
|
+
def map_module(module_name):
|
|
20
|
+
try:
|
|
21
|
+
module = import_module(module_name)
|
|
22
|
+
except ModuleNotFoundError:
|
|
23
|
+
return []
|
|
24
|
+
|
|
25
|
+
names = [
|
|
26
|
+
name
|
|
27
|
+
for name, cand in inspect.getmembers(module, inspect.isclass)
|
|
28
|
+
if issubclass(cand, View)
|
|
29
|
+
]
|
|
30
|
+
for name, mod in inspect.getmembers(module, inspect.ismodule):
|
|
31
|
+
names.extend(map_module(f"{module_name}.{name}"))
|
|
32
|
+
|
|
33
|
+
return names
|
|
34
|
+
|
|
35
|
+
names = set(map_module(module_name))
|
|
36
|
+
return list(names)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_actions_and_elements(app_config, VIEWS_CATALOG):
|
|
40
|
+
actions = {
|
|
41
|
+
action.element: action
|
|
42
|
+
for action in Action.objects.filter(app_label=app_config.label)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
model_elements = {
|
|
46
|
+
model._meta.model_name: (
|
|
47
|
+
f"{app_config.verbose_name.capitalize()} | {model._meta.verbose_name.capitalize()}"
|
|
48
|
+
)
|
|
49
|
+
for model in app_config.get_models()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
module = import_module(f"{app_config.name}.views")
|
|
54
|
+
view_elements = {
|
|
55
|
+
name: (f"{app_config.verbose_name.capitalize()} | {name}")
|
|
56
|
+
for name, candidate in inspect.getmembers(module, inspect.isclass)
|
|
57
|
+
if issubclass(candidate, View) and not candidate.__name__ in VIEWS_CATALOG
|
|
58
|
+
}
|
|
59
|
+
except (ModuleNotFoundError, ImportError):
|
|
60
|
+
view_elements = {}
|
|
61
|
+
|
|
62
|
+
elements = {**model_elements, **view_elements}
|
|
63
|
+
|
|
64
|
+
return actions, elements, model_elements, view_elements
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_actions():
|
|
68
|
+
VIEWS_CATALOG = get_views_catalog()
|
|
69
|
+
|
|
70
|
+
for app in apps.get_app_configs():
|
|
71
|
+
actions, elements, m, v = get_actions_and_elements(app, VIEWS_CATALOG)
|
|
72
|
+
|
|
73
|
+
if not elements:
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
acts = []
|
|
77
|
+
acts += [
|
|
78
|
+
Action(
|
|
79
|
+
to=Action.ToChoices.MODEL,
|
|
80
|
+
app_label=app.label,
|
|
81
|
+
name=name,
|
|
82
|
+
element=element,
|
|
83
|
+
)
|
|
84
|
+
for (element, name) in m.items()
|
|
85
|
+
if element not in actions
|
|
86
|
+
]
|
|
87
|
+
acts += [
|
|
88
|
+
Action(
|
|
89
|
+
to=Action.ToChoices.CLASSVIEW,
|
|
90
|
+
app_label=app.label,
|
|
91
|
+
name=name,
|
|
92
|
+
element=element,
|
|
93
|
+
)
|
|
94
|
+
for (element, name) in v.items()
|
|
95
|
+
if element not in actions
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
Action.objects.bulk_create(acts)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def build_menu(data):
|
|
102
|
+
create_actions()
|
|
103
|
+
Menu.objects.all().delete()
|
|
104
|
+
default_action = Action.objects.get(app_label="superadmin", element="ModuleView")
|
|
105
|
+
|
|
106
|
+
def permissions(data, action):
|
|
107
|
+
content_type = ContentType.objects.get_for_model(Permission)
|
|
108
|
+
action.permissions.clear()
|
|
109
|
+
for perm in data:
|
|
110
|
+
obj, created = Permission.objects.update_or_create(
|
|
111
|
+
codename=perm.get("codename"),
|
|
112
|
+
content_type=content_type,
|
|
113
|
+
defaults={"name": perm.get("name")},
|
|
114
|
+
)
|
|
115
|
+
action.permissions.add(obj)
|
|
116
|
+
|
|
117
|
+
RESERVED_KEYS = {"app", "model", "view", "permissions", "icon"}
|
|
118
|
+
|
|
119
|
+
def build(name, data, action, parent=None, menu_list=[]):
|
|
120
|
+
default = action
|
|
121
|
+
is_group = True
|
|
122
|
+
icon = data.get("icon") if isinstance(data, dict) else None
|
|
123
|
+
if "app" in data:
|
|
124
|
+
element = data["model"] if "model" in data else data["view"]
|
|
125
|
+
default = Action.objects.get(app_label=data["app"], element=element)
|
|
126
|
+
is_group = False
|
|
127
|
+
if "permissions" in data:
|
|
128
|
+
permissions(data["permissions"], default)
|
|
129
|
+
|
|
130
|
+
current = Menu.objects.create(
|
|
131
|
+
parent=parent,
|
|
132
|
+
name=name.capitalize(),
|
|
133
|
+
action=default,
|
|
134
|
+
is_group=is_group,
|
|
135
|
+
sequence=len(menu_list) + 1,
|
|
136
|
+
icon_class=icon,
|
|
137
|
+
)
|
|
138
|
+
menu_list.append(current)
|
|
139
|
+
if is_group:
|
|
140
|
+
for key in data:
|
|
141
|
+
if key in RESERVED_KEYS:
|
|
142
|
+
continue
|
|
143
|
+
build(key, data[key], action, parent=current, menu_list=menu_list)
|
|
144
|
+
return menu_list
|
|
145
|
+
|
|
146
|
+
menus = []
|
|
147
|
+
for key in data:
|
|
148
|
+
build(key, data[key], default_action, menu_list=menus)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Django
|
|
2
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
3
|
+
|
|
4
|
+
# local
|
|
5
|
+
from .base import create_actions
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Command(BaseCommand):
|
|
9
|
+
help = "Create actions mapping all apps"
|
|
10
|
+
|
|
11
|
+
def handle(self, *args, **options):
|
|
12
|
+
create_actions()
|
|
13
|
+
self.stdout.write(self.style.SUCCESS("Successfully actions was created"))
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Django
|
|
2
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
3
|
+
from django.core.management import call_command
|
|
4
|
+
|
|
5
|
+
# Models
|
|
6
|
+
from superadmin.models import Action, Menu
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
from superadmin import site
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Command(BaseCommand):
|
|
13
|
+
help = "Create base menu mapping all apps"
|
|
14
|
+
|
|
15
|
+
def handle(self, *args, **options):
|
|
16
|
+
call_command("createactions")
|
|
17
|
+
|
|
18
|
+
Menu.objects.all().delete()
|
|
19
|
+
|
|
20
|
+
default_action = Action.objects.get(
|
|
21
|
+
app_label="superadmin", element="ModuleView"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
apps = {}
|
|
25
|
+
for model in site._registry:
|
|
26
|
+
if model._meta.app_config in apps:
|
|
27
|
+
apps[model._meta.app_config].append(model)
|
|
28
|
+
else:
|
|
29
|
+
apps[model._meta.app_config] = [model]
|
|
30
|
+
|
|
31
|
+
sequence = 1
|
|
32
|
+
for app in apps:
|
|
33
|
+
menu = Menu.objects.create(
|
|
34
|
+
name=app.verbose_name.capitalize(),
|
|
35
|
+
action=default_action,
|
|
36
|
+
is_group=True,
|
|
37
|
+
sequence=sequence,
|
|
38
|
+
)
|
|
39
|
+
sequence += 1
|
|
40
|
+
|
|
41
|
+
index = 1
|
|
42
|
+
for model in apps[app]:
|
|
43
|
+
action = Action.objects.get(
|
|
44
|
+
app_label=app.label, element=model._meta.model_name
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
Menu.objects.create(
|
|
48
|
+
parent=menu,
|
|
49
|
+
name=model._meta.verbose_name_plural.capitalize(),
|
|
50
|
+
action=action,
|
|
51
|
+
is_group=False,
|
|
52
|
+
sequence=index,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
index += 1
|
|
56
|
+
|
|
57
|
+
self.stdout.write(self.style.SUCCESS("Successfully base menu was created"))
|
|
File without changes
|