././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/0000755000175000017500000000000000000000000014434 5ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/AUTHORS.md0000644000175000017500000000041700000000000016105 0ustar00niknik# Credits ## Original author: Val Kneeman under the name [django-menuware](https://github.com/un33k/django-menuware) ## Development Lead: * Milton Lenis - miltonln04@gmail.com ## Contributors: * Jonathan Weth - dev@jonathanweth.de * Dominik George - nik@naturalnet.de ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/LICENSE0000755000175000017500000000211700000000000015445 0ustar00niknikThe MIT License Copyright (c) Val Neekman @ Neekware Inc. http://neekware.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/MANIFEST.in0000644000175000017500000000011600000000000016170 0ustar00niknikinclude LICENSE include AUTHORS.md include README.rst recursive-include docs *././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/PKG-INFO0000644000175000017500000000130100000000000015524 0ustar00niknikMetadata-Version: 1.1 Name: django-menu-generator-ng Version: 1.2.3 Summary: A straightforward menu generator for Django Home-page: https://edugit.org/AlekSIS/libs/django-menu-generator-ng Author: The AlekSIS Team Author-email: aleksis-dev@lists.teckids.org License: MIT Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Framework :: Django Classifier: Topic :: Utilities ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/README.rst0000644000175000017500000001515600000000000016133 0ustar00niknikDjango Menu Generator - next generation ======================================= A menu generating application for Django Forked from the original at https://github.com/LaLogiaDePython/django-menu-generator A productivity tool that enables the generation of full featured menus through python dictionaries list, you only need to setup the HTML structure once for each menu you like to build and then use the dictionaries to generate menu items Features: --------- - Tested support to Python 3.7, 3.8, 3.9 - Tested support to Django 2.2, 3.0, 3.1 - No database - Support unlimited menus - Icons support - Semi-Automatically identifies the selected item and his breadcrums - Conditional menu items display through validators (Permissions, Authentications or whatever you want) Documentation: -------------- The full documentation for Django Menu Generator is allowed here: http://django-menu-generator.readthedocs.io/en/latest/index.html Installation: ------------- You can install it with one of these options: - ``easy_install django-menu-generator`` - ``pip install django-menu-generator`` - ``git clone https://github.com/LaLogiaDePython/django-menu-generator`` 1. ``cd django-menu-generator`` 2. run python setup.py - ``wget https://github.com/LaLogiaDePython/django-menu-generator/zipball/master`` 1. unzip the downloaded file 2. cd into django-menu-generator-\* directory 3. run python setup.py Usage: ------ 1. Once installed, add ``'menu_generator'`` to your INSTALLED\_APPS. 2. Add ``{% load menu_generator %}`` to templates that will handle the menus. 3. Add the list dictionaries containing the menu items to the settings .. code:: python #################################################################################### Example: settings.py #################################################################################### NAV_MENU_LEFT = [ { "name": "Home", "url": "/", }, { "name": "About", "url": "/about", }, ] NAV_MENU_RIGHT = [ { "name": "Login", "url": "login_url_view", # reversible "validators": ["menu_generator.validators.is_anonymous"], }, { "name": "Register", "url": "register_view_url", # reversible "validators": ["menu_generator.validators.is_anonymous"], }, { "name": "Account", "url": "/acount", "validators": ["menu_generator.validators.is_authenticated"], "submenu": [ { "name": "Profile", "url": "/account/profile", }, { "name": "Account Balance", "url": "/account/balance", "validators": ["myapp.profiles.is_paid_user"], }, { "name": "Account Secrets", "url": "/account/secrets", "validators": ["menu_generator.validators.is_superuser"], } ], }, ] FOOTER_MENU_LEFT = [ { "name": "Facebook", "url": "facebook.com/foobar", }, { "name": "Contact US", "url": "/contact", }, ] FOOTER_MENU_RIGHT = [ { "name": "Address", "url": "/address", }, ] Or you can build the menu dictionaries list inside the project apps with ``menus.py`` files, see docs for more. 4. In your template, load the template tag to generate your menu. :: {% load menu_generator %} Django Menu Generator {% get_menu "NAV_MENU_LEFT" as left_menu %}
{% for item in left_menu %}
  • {{ item.name }}
  • {% if item.submenu %} {% endif %} {% endfor %}
    {% get_menu "NAV_MENU_RIGHT" as right_menu %}
    {% for item in right_menu %}
  • {{ item.name }}
  • {% if item.submenu %} {% endif %} {% endfor %}
    {% get_menu "FOOTER_MENU_LEFT" as left_footer_menu %}
    {% get_menu "FOOTER_MENU_RIGHT" as right_footer_menu %}
    5. Now you must to see your menus generated when you run your project Running the tests: ------------------ To run the tests against configured environments: :: tox License: -------- Released under a (`MIT `__) license. Author and mantainers: ---------------------- This fork is maintained by the AlekSIS team. Original authors and maintainers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following people maintained django-menu-generator for a long time: `Milton Lenis `__ - miltonln04@gmail.com `Juan Diego García `__ - juandgoc@gmail.com Credits: -------- We would like to thank `Val Kneeman `__, the original author of this project under the name 'menuware' https://github.com/un33k/django-menuware ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/django_menu_generator_ng.egg-info/0000755000175000017500000000000000000000000023146 5ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616968500.0 django-menu-generator-ng-1.2.3/django_menu_generator_ng.egg-info/PKG-INFO0000644000175000017500000000130100000000000024236 0ustar00niknikMetadata-Version: 1.1 Name: django-menu-generator-ng Version: 1.2.3 Summary: A straightforward menu generator for Django Home-page: https://edugit.org/AlekSIS/libs/django-menu-generator-ng Author: The AlekSIS Team Author-email: aleksis-dev@lists.teckids.org License: MIT Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Framework :: Django Classifier: Topic :: Utilities ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616968500.0 django-menu-generator-ng-1.2.3/django_menu_generator_ng.egg-info/SOURCES.txt0000644000175000017500000000252500000000000025036 0ustar00niknikAUTHORS.md LICENSE MANIFEST.in README.rst setup.cfg setup.py django_menu_generator_ng.egg-info/PKG-INFO django_menu_generator_ng.egg-info/SOURCES.txt django_menu_generator_ng.egg-info/dependency_links.txt django_menu_generator_ng.egg-info/top_level.txt docs/Makefile docs/authors.rst docs/changelog.rst docs/conf.py docs/contribute.rst docs/credits.rst docs/index.rst docs/install.rst docs/license.rst docs/make.bat docs/menugeneration.rst docs/support.rst docs/tests.rst docs/urls.rst docs/usage.rst docs/validators.rst menu_generator/__init__.py menu_generator/apps.py menu_generator/defaults.py menu_generator/menu.py menu_generator/utils.py menu_generator/validators.py menu_generator/templatetags/__init__.py menu_generator/templatetags/menu_generator.py menu_generator/templatetags/utils.py menu_generator/tests/__init__.py menu_generator/tests/test_menu.py menu_generator/tests/test_validators.py menu_generator/tests/testsettings.py menu_generator/tests/urls.py menu_generator/tests/utils.py menu_generator/tests/test_apps/__init__.py menu_generator/tests/test_apps/app1/__init__.py menu_generator/tests/test_apps/app1/apps.py menu_generator/tests/test_apps/app1/menus.py menu_generator/tests/test_apps/app2/__init__.py menu_generator/tests/test_apps/app2/menus.py menu_generator/tests/test_apps/app3/__init__.py menu_generator/tests/test_apps/app3/menus.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616968500.0 django-menu-generator-ng-1.2.3/django_menu_generator_ng.egg-info/dependency_links.txt0000644000175000017500000000000100000000000027214 0ustar00niknik ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616968500.0 django-menu-generator-ng-1.2.3/django_menu_generator_ng.egg-info/top_level.txt0000644000175000017500000000031300000000000025675 0ustar00niknikmenu_generator menu_generator/templatetags menu_generator/tests menu_generator/tests/test_apps menu_generator/tests/test_apps/app1 menu_generator/tests/test_apps/app2 menu_generator/tests/test_apps/app3 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/docs/0000755000175000017500000000000000000000000015364 5ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/Makefile0000644000175000017500000000115000000000000017021 0ustar00niknik# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = DjangoMenuGenerator SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/authors.rst0000644000175000017500000000053700000000000017610 0ustar00niknikAuthors ======= `Milton Lenis `__ - miltonln04@gmail.com `Juan Diego García `__ - juandgoc@gmail.com Contributors ============ `Jonathan Weth `__ - dev@jonathanweth.de `Dominik George `__ - nik@naturalnet.de ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616968451.0 django-menu-generator-ng-1.2.3/docs/changelog.rst0000644000175000017500000000256500000000000020055 0ustar00niknikCHANGELOG ========= 1.2.3(2021-03-28) ----------------- - Bugfix: - Catch 404 exception if current path cannot be resolved 1.2.1(2021-01-21) ----------------- - Features: - Adding support for related views (Thanks to @Natureshadow) - Project Enhancements: - Dropping support for Python <3.7 - Dropping support for Django <2.2 - Adding support for Python 3.9 - Forked as django-menu-generator-ng due to lack of maintenance 1.1.0(2020-11-15) ----------------- - Features: - Adding support for newer versions of Python and Django - Adding support for related paths (Thanks to @hansegucker) 1.0.4(2018-02-19) ----------------- - Features: - Adding support for AppConfig in INSTALLED_APPS 1.0.3(2018-01-31) ----------------- - Project Enhancements: - Dropping support for Python 2 - Updating docs - Readme updating - Support for Django 2.0 (Thanks to @lachmanfrantisek) - Features: - Adding support to root paths (Thanks to @lucaskuzma) - BugFix: - Fixing backward compatibility with Django < 1.10 1.0.2(2017-04-29) ----------------- - Updating .gitignore to ignore sphinx builds - Updating docs 1.0.1(2017-04-29) ----------------- - Added docs - Readme enhanced - BUGFIX: Added a correction to the validators evaluation with the AND connector - Added flake8 to CI - Added tox 1.0.0 (2017-04-09) ------------------ - Initial release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/conf.py0000644000175000017500000001144000000000000016663 0ustar00niknik#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Django Menu Generator documentation build configuration file, created by # sphinx-quickstart on Wed Apr 26 22:30:34 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.coverage', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Django Menu Generator' copyright = '2017, Milton Lenis' author = 'Milton Lenis' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.0.3' # The full version, including alpha/beta/rc tags. release = '1.0.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'DjangoMenuGeneratordoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'DjangoMenuGenerator.tex', 'Django Menu Generator Documentation', 'Milton Lenis', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'djangomenugenerator', 'Django Menu Generator Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'DjangoMenuGenerator', 'Django Menu Generator Documentation', author, 'DjangoMenuGenerator', 'One line description of project.', 'Miscellaneous'), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/contribute.rst0000644000175000017500000000046600000000000020302 0ustar00niknikContribute =========== - Issue tracker: `https://github.com/LaLogiaDePython/django-menu-generator/issues `__ - Source code: `https://github.com/LaLogiaDePython/django-menu-generator `__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/credits.rst0000644000175000017500000000027300000000000017555 0ustar00niknikCredits ======== We would like to thank `Val Kneeman `__, the original author of this project under the name 'menuware' https://github.com/un33k/django-menuware././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/index.rst0000644000175000017500000000216600000000000017232 0ustar00niknik.. Django Menu Generator documentation master file, created by sphinx-quickstart on Wed Apr 26 22:30:34 2017. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to Django Menu Generator's documentation! ================================================= Django Menu Generator is a productivity tool that enables the generation of full featured menus through python dictionaries list, you only need to setup the HTML structure once for each menu you like to build and then use the dictionaries to generate menu items Features: --------- - Tested support to Python 3.4, 3.5, 3.6 - Tested support to Django 1.8, 1.9, 1.10, 1.11, 2.0 - No database - Support unlimited menus - Icons support - Semi-Automatically identifies the selected item and his breadcrums - Conditional menu items display through validators (Permissions, Authentications or whatever you want) Contents: --------- .. toctree:: :maxdepth: 2 install usage menugeneration urls validators changelog tests authors contribute support license credits ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/install.rst0000644000175000017500000000076000000000000017567 0ustar00niknikInstallation ============= You can install Django Menu Generator with one of these options: - ``easy_install django-menu-generator`` - ``pip install django-menu-generator`` - ``git clone https://github.com/LaLogiaDePython/django-menu-generator`` 1. ``cd django-menu-generator`` 2. run python setup.py - ``wget https://github.com/LaLogiaDePython/django-menu-generator/zipball/master`` 1. unzip the downloaded file 2. cd into django-menu-generator-\* directory 3. run python setup.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/license.rst0000644000175000017500000000020400000000000017534 0ustar00niknikLicense ======== Released under a (`MIT `__) license.././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/make.bat0000644000175000017500000000146700000000000017001 0ustar00niknik@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build set SPHINXPROJ=DjangoMenuGenerator if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/menugeneration.rst0000644000175000017500000000625400000000000021145 0ustar00niknikMenu Generation =============== Django Menu Generator uses python dictionaries to represent the menu items, usually a menu item is as follows: .. code:: python { "name": 'some name', "icon_class": 'some icon class', "url": URL spec, "root": True | False, "related_urls": [ list of related URLs ], "related_views": [ list of related views ], "validators": [ list of validators ], "submenu": Dictionary like this } Where each key is as follows: - ``name``: A string representing the label of the menu item. If you are using i18n here you can pass the name with the ``ugettext_lazy`` function - ``icon_class``: A string representing the class of the icon you wish to show on the menu item, e.g you can use font-awesome - ``url``: See :doc:`urls` - ``related_urls``: If one of these URLs is part of the path on the currently opened page, the menu item will be marked as selected (format of URLs like described at :doc:`urls`) - ``related_views``: If the currently opened page resolves to one of these views, the menu item will be marked as selected. - ``root``: A flag to indicate this item is the root of a path, with this you can correctly mark nested menus as selected. - ``validators``: See :doc:`validators` - ``submenu``: You can create infinite nested submenus passing here menu items like this Django Menu Generator offers two ways to generate the menus, through the Django settings and through each of the Django apps Generating menus through settings --------------------------------- You can add various list dictionaries representing each menu you have as explained in :doc:`usage` We recommend to have a ``menus.py`` file with the menu list dictionaries and then import it to the settings file if you go this way Generating menus through apps ----------------------------- Some people prefer to isolate all the aspects of a project between apps, so, we add this feature to allow the menus live inside each app. You need to add inside the app a ``menus.py`` file that contains a dictionary called ``MENUS``, each element of the dictionary will be a menu list dictionary with all the configuration needed to display that menu, e.g: .. code:: python MENUS = { 'NAV_MENU_LEFT': [ { "name": "App1 Feature", "url": "/app1-feature" } ], 'NAV_MENU_TOP': [ { "name": "Second Menu Feature", "url": "named_url" } ] } So, as an example, for the ``'NAV_MENU_LEFT'``, Django Menu Generator will loop each app searching for the ``'NAV_MENU_LEFT'`` list dictionaries inside of the ``MENUS`` and build all the menu configuration to build the whole menu. With this feature you can have a project structure like this:: your_project/ ├── config_folder/ │ └── ... ├── app1 │ └── models.py │ forms.py │ views.py │ menus.py │ ├── app2 │ └── models.py │ forms.py │ views.py │ menus.py │ ... You can have a mix of the two approaches if you wish ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/support.rst0000644000175000017500000000014300000000000017630 0ustar00niknikSupport ======= If you are having issues, please let us know. Contact us at: miltonln04@gmail.com ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/tests.rst0000644000175000017500000000054000000000000017257 0ustar00niknikRunning the tests ================== To run the tests against configured environments: * Install pyenv and add all of the supported python versions there * Add all of the version to your locals `pyenv local system 3.7.9 3.6.12 3.5.10` for example * Install the development dependencies in `requirements_dev.txt` * Run the tests with tox :: tox ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/urls.rst0000644000175000017500000000144300000000000017105 0ustar00niknikURLs ==== You can pass the URL parameters to menu items in three ways. Raw URLs -------- A hard-coded url: .. code:: python "url": '/some-path/to-feature' Reversible URLs --------------- An url that can be reversed with the `reverse` method: .. code:: python "url": 'named_url' URL with args or kwargs ----------------------- e.g. If you have an url with kwargs like this: .. code:: python url(r'^update/(?P\d+)/$', SomeView.as_view(), name='update'), you can pass the url as follows: .. code:: python "url": {"viewname": 'update', "kwargs": {"pk": 1}} In fact, you can pass any of the parameters of the reverse method through the dictionary For Django 1.10 the reverse method sign is: ``reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)`` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/usage.rst0000644000175000017500000001077200000000000017231 0ustar00niknikUsage ===== 1. Once installed, add ``'menu_generator'`` to your INSTALLED\_APPS. 2. Add ``{% load menu_generator %}`` to templates that will handle the menus. 3. Add the list dictionaries containing the menu items to the settings .. code:: python #################################################################################### Example: settings.py #################################################################################### NAV_MENU_LEFT = [ { "name": "Home", "url": "/", }, { "name": "About", "url": "/about", }, ] NAV_MENU_RIGHT = [ { "name": "Login", "url": "login_url_view", # reversible "validators": ["menu_generator.validators.is_anonymous"], }, { "name": "Register", "url": "register_view_url", # reversible "validators": ["menu_generator.validators.is_anonymous"], }, { "name": "Account", "url": "/acount", "validators": ["menu_generator.validators.is_authenticated"], "submenu": [ { "name": "Profile", "url": "/account/profile", }, { "name": "Account Balance", "url": "/account/balance", "validators": ["myapp.profiles.is_paid_user"], }, { "name": "Account Secrets", "url": "/account/secrets", "validators": ["menu_generator.validators.is_superuser"], } ], }, ] FOOTER_MENU_LEFT = [ { "name": "Facebook", "url": "facebook.com/foobar", }, { "name": "Contact US", "url": "/contact", }, ] FOOTER_MENU_RIGHT = [ { "name": "Address", "url": "/address", }, ] Or you can build the menu dictionaries list inside the project apps with ``menus.py`` files, see :doc:`menugeneration` for more. 4. In your template, load the template tag to generate your menu. :: {% load menu_generator %} Django Menu Generator {% get_menu "NAV_MENU_LEFT" as left_menu %}
    {% for item in left_menu %}
  • {{ item.name }}
  • {% if item.submenu %} {% endif %} {% endfor %}
    {% get_menu "NAV_MENU_RIGHT" as right_menu %}
    {% for item in right_menu %}
  • {{ item.name }}
  • {% if item.submenu %} {% endif %} {% endfor %}
    {% get_menu "FOOTER_MENU_LEFT" as left_footer_menu %}
    {% get_menu "FOOTER_MENU_RIGHT" as right_footer_menu %}
    5. Now you must to see your menus generated when you run your project ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/docs/validators.rst0000644000175000017500000000607000000000000020271 0ustar00niknikValidators ========== Django Menu Generator uses validators to allow the displaying of menu items. A validator is a function that receives the request as arg and returns a boolean indicating if the check has passed for Django Menu Generator the validators must always be a list containing at least one callable or python path to a callable. If there is more than one validator, all of them will be evaluated using the AND connector. Built-in validators ------------------- Django Menu Generator has the following built-in validators: - is_superuser: A validator to check if the authenticated user is a superuser Usage: .. code:: python "validators": ['menu_generator.validators.is_superuser'] - is_staff: A validator to check if the authenticated user is member of the staff Usage: .. code:: python "validators": ['menu_generator.validators.is_staff'] - is_authenticated: A validator to check if user is authenticated Usage: .. code:: python "validators": ['menu_generator.validators.is_authenticated'] - is_anonymous: A validator to check if the user is not authenticated Usage: .. code:: python "validators": ['menu_generator.validators.is_anonymous'] - user_has_permission: A validator to check if the user has the given permission Usage: .. code:: python "validators": [ ('menu_generator.validators.user_has_permission', 'app_label.permission_codename') ] - More than one validator: You can pass more than one validator to evaluate using the AND connector .. code:: python "validators": [ 'menu_generator.validators.is_staff', ('menu_generator.validators.user_has_permission', 'some_app.some_permission') ... ] Custom validators ----------------- You can build your own validators and use them with Django Menu Generator Let's build a validator that checks if the user have more than one pet (dummy example) assuming the user has a many to many relation called pets Assuming we build the function inside ``your_project/app1`` on a ``menu_validators.py`` we have: .. code:: python # Remember you always must to past the request as first parameter def has_more_than_one_pet(request): return request.user.pets.count() > 0 So we can use it as a validator .. code:: python "validators": ['your_project.app1.menu_validators.has_more_than_one_pet'] Now let's build a validator that checks if the user's pet belongs to a specific type to illustrate the validators with parameters. Assuming we build the function inside the same path and the user have a foreign key called pet .. code:: python def has_a_pet_of_type(request, type): return request.user.pet.type == type So we use the validator like this: .. code:: python "validators": [ ('your_project.app1.menu_validators.has_a_pet_of_type', 'DOG') ] As you can see, we use tuples to pass parameters to the validators, where the first position is the validator and the rest are the function parameters././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/menu_generator/0000755000175000017500000000000000000000000017446 5ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616968457.0 django-menu-generator-ng-1.2.3/menu_generator/__init__.py0000755000175000017500000000025400000000000021563 0ustar00niknik__author__ = 'Milton Lenis' __description__ = 'A straightforward menu generator for Django' __version__ = '1.2.3' default_app_config = 'menu_generator.apps.MenuAppConfig' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/apps.py0000755000175000017500000000030600000000000020765 0ustar00niknikfrom django.apps import AppConfig class MenuAppConfig(AppConfig): name = 'menu_generator' label = 'menu_generator' verbose_name = 'Menu Application' def ready(self): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/defaults.py0000755000175000017500000000013000000000000021624 0ustar00niknikMENU_NOT_FOUND = [ { "name": "MENU NOT FOUND", "url": "/", }, ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616968443.0 django-menu-generator-ng-1.2.3/menu_generator/menu.py0000755000175000017500000001511200000000000020767 0ustar00niknikimport copy import django from django.core.exceptions import ImproperlyConfigured from django.urls import Resolver404 from .utils import get_callable, parse_url, path_startswith if django.VERSION >= (1, 10): # pragma: no cover from django.urls import resolve, reverse, NoReverseMatch else: from django.core.urlresolvers import resolve, reverse, NoReverseMatch class MenuBase(object): """ Base class that generates menu list. """ def __init__(self): self.path = '' self.request = None def save_user_state(self, request): """ Given a request object, store the current user attributes :param request: HttpRequest """ self.request = request self.path = request.path def _is_validated(self, item_dict): """ Given a menu item dictionary, it returns true if the user passes all the validator's conditions, it means, if the user passes all the conditions, the user can see the menu """ validators = item_dict.get('validators') if not validators: return True if not isinstance(validators, (list, tuple)): raise ImproperlyConfigured("validators must be a list") result_validations = [] for validator in validators: if isinstance(validator, tuple): if len(validator) <= 1: raise ImproperlyConfigured("You are passing a tuple validator without args %s" % str(validator)) func = get_callable(validator[0]) # Using a python slice to get all items after the first to build function args args = validator[1:] # Pass the request as first arg by default result_validations.append(func(self.request, *args)) else: func = get_callable(validator) result_validations.append(func(self.request)) # pragma: no cover return all(result_validations) def _has_attr(self, item_dict, attr): """ Given a menu item dictionary, it returns true if an attr is set. """ if item_dict.get(attr, False): return True return False def _get_icon(self, parent_dict): """ Given a menu item dictionary, this returns an icon class if one exist, or returns an empty string. """ return parent_dict.get('icon_class', '') def _get_url(self, item_dict): """ Given a menu item dictionary, it returns the URL or an empty string. """ url = item_dict.get('url', '') return parse_url(url) def _get_related_urls(self, item_dict): """ Given a menu item dictionary, it returns the relateds URLs or an empty list. """ related_urls = item_dict.get('related_urls', []) return [parse_url(url) for url in related_urls] def _get_related_views(self, item_dict): """ Given a menu item dictionary, it returns the relateds viewss or an empty list. """ related_views = item_dict.get('related_views', []) return related_views def _is_selected(self, item_dict): """ Given a menu item dictionary, it returns true if `url` is on path, unless the item is marked as a root, in which case returns true if `url` is part of path. If related URLS are given, it also returns true if one of the related URLS is part of path. If related views are given, it also returns true if the path maps to one of these views. """ url = self._get_url(item_dict) if self._is_root(item_dict) and path_startswith(self.path, url): return True elif url == self.path: return True else: # Go through all related URLs and test for related_url in self._get_related_urls(item_dict): if path_startswith(self.path, related_url): return True # Resolve URL and check if it relates to a related views try: if resolve(self.path).func in self._get_related_views(item_dict): return True except Resolver404: return False return False def _is_root(self, item_dict): """ Given a menu item dictionary, it returns true if item is marked as a `root`. """ return item_dict.get('root', False) def _process_breadcrums(self, menu_list): """ Given a menu list, it marks the items on the current path as selected, which can be used as breadcrumbs """ for item in menu_list: if item['submenu']: item['selected'] = self._process_breadcrums(item['submenu']) if item['selected']: return True return False def _get_submenu_list(self, parent_dict): """ Given a menu item dictionary, it returns a submenu if one exist, or returns None. """ submenu = parent_dict.get('submenu', None) if submenu: for child_dict in submenu: # This does a join between the menu item validators and submenu item validators and stores it on the # submenu's validators child_dict['validators'] = list( set(list(parent_dict.get('validators', [])) + list(child_dict.get('validators', []))) ) submenu = self.generate_menu(submenu) if not submenu: submenu = None return submenu def _get_menu_list(self, list_dict): """ A generator that returns only the visible menu items. """ for item in list_dict: if self._has_attr(item, 'name') and self._has_attr(item, 'url'): if self._is_validated(item): yield copy.copy(item) def generate_menu(self, list_dict): """ Given a list of dictionaries, returns a menu list. """ visible_menu = [] for item in self._get_menu_list(list_dict): item['url'] = self._get_url(item) item['selected'] = self._is_selected(item) item['submenu'] = self._get_submenu_list(item) item['icon_class'] = self._get_icon(item) visible_menu.append(item) self._process_breadcrums(visible_menu) return visible_menu class Menu(MenuBase): """ Class that generates menu list. """ def __call__(self, request, list_dict): self.save_user_state(request) return self.generate_menu(list_dict) generate_menu = Menu() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/menu_generator/templatetags/0000755000175000017500000000000000000000000022140 5ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/templatetags/__init__.py0000755000175000017500000000000000000000000024242 0ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/templatetags/menu_generator.py0000755000175000017500000000226700000000000025536 0ustar00niknikfrom django import template from django.conf import settings from .utils import get_menu_from_apps from .. import defaults from ..menu import generate_menu register = template.Library() @register.simple_tag(takes_context=True) def get_menu(context, menu_name): """ Returns a consumable menu list for a given menu_name found in settings.py. Else it returns an empty list. Update, March 18 2017: Now the function get the menu list from settings and append more items if found on the menus.py's 'MENUS' dict. :param context: Template context :param menu_name: String, name of the menu to be found :return: Generated menu """ menu_list = getattr(settings, menu_name, defaults.MENU_NOT_FOUND) menu_from_apps = get_menu_from_apps(menu_name) # If there isn't a menu on settings but there is menu from apps we built menu from apps if menu_list == defaults.MENU_NOT_FOUND and menu_from_apps: menu_list = menu_from_apps # It there is a menu on settings and also on apps we merge both menus elif menu_list != defaults.MENU_NOT_FOUND and menu_from_apps: menu_list += menu_from_apps return generate_menu(context['request'], menu_list) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/templatetags/utils.py0000644000175000017500000000153100000000000023652 0ustar00niknikfrom django.conf import settings from ..utils import get_callable, clean_app_config MENU_DICT = ".menus.MENUS" def get_menu_from_apps(menu_name): """ Returns a consumable menulist for a given menu_name found in each menus.py file (if exists) on each app on INSTALLED_APPS :param menu_name: String, name of the menu to be found :return: Consumable menu list """ installed_apps = getattr(settings, "INSTALLED_APPS", []) menu_list = [] for app in installed_apps: cleaned_app = clean_app_config(app) try: all_menus_dict = get_callable(cleaned_app + MENU_DICT) except ImportError: all_menus_dict = None except AttributeError: all_menus_dict = None if all_menus_dict: menu_list += all_menus_dict.get(menu_name, []) return menu_list ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/menu_generator/tests/0000755000175000017500000000000000000000000020610 5ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/__init__.py0000755000175000017500000000000000000000000022712 0ustar00niknik././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/0000755000175000017500000000000000000000000022612 5ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/__init__.py0000755000175000017500000000000000000000000024714 0ustar00niknik././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/app1/0000755000175000017500000000000000000000000023453 5ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/app1/__init__.py0000755000175000017500000000005500000000000025567 0ustar00niknikdefault_app_config = 'app1.apps.MyAppConfig' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/app1/apps.py0000644000175000017500000000021600000000000024767 0ustar00niknikfrom django.apps import AppConfig class MyAppConfig(AppConfig): name = 'menu_generator.tests.test_apps.app1' verbose_name = 'app 1' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/app1/menus.py0000755000175000017500000000037100000000000025160 0ustar00niknikMENUS = { 'NAV_MENU': [ { "name": "App1 Feature", "url": "/app1-feature" } ], 'SECOND_MENU': [ { "name": "Second Menu Feature", "url": "named_url" } ] } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/app2/0000755000175000017500000000000000000000000023454 5ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/app2/__init__.py0000755000175000017500000000000000000000000025556 0ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/app2/menus.py0000755000175000017500000000035100000000000025157 0ustar00niknikMENUS = { 'NAV_MENU': [ { "name": "App2 Feature", "url": "/app2-feature" }, { "name": "App2 Second Feature", "url": "/app2-second-feature" } ] } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/app3/0000755000175000017500000000000000000000000023455 5ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/app3/__init__.py0000755000175000017500000000000000000000000025557 0ustar00niknik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/test_apps/app3/menus.py0000755000175000017500000000014200000000000025156 0ustar00niknik# This file doesn't contain a 'MENUWARE_MENUS' dict with any menus, this is for coverage purposes ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/test_menu.py0000755000175000017500000003312200000000000023171 0ustar00niknikfrom django.core.exceptions import ImproperlyConfigured from django.http import HttpRequest from django.test import TestCase from .urls import testview from .utils import TestUser, is_main_site, is_paid_user from ..menu import MenuBase from ..templatetags.menu_generator import get_menu class MenuTestCase(TestCase): """ Menu Test """ def setUp(self): """ Setup the test. """ self.request = HttpRequest() self.request.path = '/' self.menu = MenuBase() def test_template_tag(self): self.request.user = TestUser(authenticated=True) ctx = { 'request': self.request } nav = get_menu(ctx, 'NAV_MENU') # While NAV_MENU is present on app1 menus and app2 menus and settings it will get 7 items self.assertEqual(len(nav), 7) nav = get_menu(ctx, 'SECOND_MENU') self.assertEqual(len(nav), 1) nav = get_menu(ctx, 'NONEXISTENT_MENU') self.assertEqual(len(nav), 1) self.assertEqual(nav[0]['name'], 'MENU NOT FOUND') def test_has_attr(self): self.assertFalse(self.menu._has_attr({}, 'name')) self.assertFalse(self.menu._has_attr({'name': ''}, 'name')) self.assertTrue(self.menu._has_attr({'name': 'Some Name'}, 'name')) self.assertFalse(self.menu._has_attr({}, 'url')) self.assertFalse(self.menu._has_attr({'url': ''}, 'url')) self.assertTrue(self.menu._has_attr({'url': '/'}, 'url')) self.assertTrue(self.menu._has_attr({'url': '/foo/bar'}, 'url')) def test_get_url(self): self.assertEqual(self.menu._get_url({}), '') self.assertEqual(self.menu._get_url({'url': '/'}), '/') self.assertEqual(self.menu._get_url({'url': '/foo/bar'}), '/foo/bar') self.assertEqual(self.menu._get_url({'url': 'named_url'}), '/named-url') params_dict = {"viewname": 'named_with_params', "kwargs": {'pk': 1}} self.assertEqual(self.menu._get_url({'url': params_dict}), '/named-with-params/1/') def test_state_anonymous_user(self): self.request.user = TestUser() self.menu.save_user_state(self.request) self.assertFalse(self.menu.request.user.is_authenticated) def test_state_regular_user(self): self.request.user = TestUser(authenticated=True) self.menu.save_user_state(self.request) self.assertTrue(self.menu.request.user.is_authenticated) self.assertFalse(self.menu.request.user.is_staff) self.assertFalse(self.menu.request.user.is_superuser) def test_state_staff(self): self.request.user = TestUser(authenticated=True, staff=True) self.menu.save_user_state(self.request) self.assertTrue(self.menu.request.user.is_staff) def test_state_superuser(self): self.request.user = TestUser(authenticated=True, superuser=True) self.menu.save_user_state(self.request) self.assertTrue(self.menu.request.user.is_superuser) def test_menu_is_validated_for_non_list_or_dict_validators(self): menu_dict = { "validators": "menu_generator.tests.utils.is_main_site", } with self.assertRaises(ImproperlyConfigured): self.menu._is_validated(menu_dict) def test_menu_is_validated_for_dict_validators(self): menu_dict = { "validators": ("menu_generator.tests.utils.is_main_site",), } self.assertTrue(self.menu._is_validated(menu_dict)) def test_menu_is_validated_dotted_notation(self): menu_dict = { "validators": ["menu_generator.tests.utils.is_main_site"], } self.assertTrue(self.menu._is_validated(menu_dict)) def test_menu_is_validated_invalid_dotted_notation(self): menu_dict = { "validators": ["foobar.hello"], } with self.assertRaises(ImportError): self.menu._is_validated(menu_dict) def test_menu_is_validated_callables(self): menu_dict = { "validators": [is_main_site], } self.assertTrue(self.menu._is_validated(menu_dict)) def test_menu_is_validated_without_validators(self): menu_dict = { # no validators } self.assertTrue(self.menu._is_validated(menu_dict)) def test_menu_is_validated_for_invalid_tuple_validator(self): # Test case when the coder doesn't pass parameters to the validators via tuple menu_dict = { "validators": [("menu_generator.tests.utils.is_main_site",)] } self.assertRaises(ImproperlyConfigured, self.menu._is_validated, menu_dict) def test_menu_is_validated_for_valid_tuple_validator(self): # Test case with correct parameters. menu_dict = { "validators": [ ("menu_generator.tests.utils.validator_with_parameters", "param1", "param2",), "menu_generator.tests.utils.is_main_site" ] } self.assertTrue(self.menu._is_validated(menu_dict)) def test_menu_is_validated_for_authenticated_users(self): menu_dict = { "validators": ["menu_generator.validators.is_authenticated"], } self.request.user = TestUser(authenticated=True) self.menu.save_user_state(self.request) self.assertTrue(self.menu._is_validated(menu_dict)) def test_menu_is_validated_for_unauthenticated_users(self): menu_dict = { "validators": ["menu_generator.validators.is_anonymous"], } self.request.user = TestUser() self.menu.save_user_state(self.request) self.assertTrue(self.menu._is_validated(menu_dict)) def test_menu_is_validated_for_superusers(self): menu_dict = { "validators": ["menu_generator.validators.is_superuser"], } self.request.user = TestUser(authenticated=True, superuser=True) self.menu.save_user_state(self.request) self.assertTrue(self.menu._is_validated(menu_dict)) def test_menu_is_validated_for_staff(self): menu_dict = { "validators": ["menu_generator.validators.is_staff"], } self.request.user = TestUser(authenticated=True, staff=True) self.menu.save_user_state(self.request) self.assertTrue(self.menu._is_validated(menu_dict)) def test_menu_is_validated_for_user_with_permission(self): menu_dict = { "validators": [ ("menu_generator.validators.user_has_permission", "test_permission",) ] } self.request.user = TestUser(authenticated=True) self.menu.save_user_state(self.request) self.assertFalse(self.menu._is_validated(menu_dict)) # Let's add the permission self.request.user.add_perm("test_permission") self.menu.save_user_state(self.request) self.assertTrue(self.menu._is_validated(menu_dict)) def test_menu_is_validated_for_non_user_specific_conditions(self): menu_dict = { "validators": ["menu_generator.tests.utils.is_main_site"], } self.request.user = TestUser() self.menu.save_user_state(self.request) self.assertTrue(self.menu._is_validated(menu_dict)) def test_menu_multiple_validators(self): menu_dict = { "validators": ["menu_generator.validators.is_authenticated", "menu_generator.validators.is_staff"], } self.request.user = TestUser(authenticated=True) self.menu.save_user_state(self.request) self.assertFalse(self.menu._is_validated(menu_dict)) def test_generate_menu_submenu_attribute_inheritance(self): self.request.user = TestUser(staff=True, authenticated=True, happy=True) self.menu.save_user_state(self.request) list_dict = [ { # Menu item -- is_saff validator will be applied to the child node "name": "parent1", "url": "/user/account/", "validators": ["menu_generator.validators.is_staff"], "submenu": [ { "name": "child1", "url": '/user/account/profile/', }, ], }, { # Menu item -- is_saff validator will be applied to the child node "name": "parent2", "url": "/user/settings/", "validators": ["menu_generator.validators.is_authenticated"], "submenu": [ { "name": "child1", "url": '/user/settings/happy/', "validators": [ "menu_generator.tests.utils.is_user_happy", ], }, { "name": "child2", "url": '/user/settings/paid/', "validators": [ is_paid_user, ], }, ], }, ] nav = self.menu.generate_menu(list_dict) self.assertEqual(len(nav), 2) self.assertTrue('menu_generator.validators.is_staff' in nav[0]['validators']) self.assertTrue('menu_generator.validators.is_staff' in nav[0]['submenu'][0]['validators']) self.assertTrue('menu_generator.validators.is_authenticated' in nav[1]['validators']) self.assertTrue('menu_generator.validators.is_authenticated' in nav[1]['submenu'][0]['validators']) self.assertTrue('menu_generator.validators.is_authenticated' in nav[1]['submenu'][1]['validators']) self.assertTrue('menu_generator.tests.utils.is_user_happy' in nav[1]['submenu'][0]['validators']) self.assertTrue(is_paid_user in nav[1]['submenu'][1]['validators']) def test_generate_menu_no_visible_submenu(self): self.request.user = TestUser(authenticated=True) self.menu.save_user_state(self.request) list_dict = [ { # Menu item -- is_saff validator will be applied to the child node "name": "parent1", "url": "/user/account/", "validators": ["menu_generator.validators.is_authenticated"], "submenu": [ { "name": "child1", "url": '/user/account/profile/', "validators": ["menu_generator.validators.is_staff"] # submenu no visible }, ], } ] nav = self.menu.generate_menu(list_dict) self.assertEqual(len(nav), 1) self.assertIsNone(nav[0]["submenu"]) def test_generate_menu_selected_related_urls_simple(self): self.request.user = TestUser(authenticated=True) self.request.path = "/persons/1/" self.menu.save_user_state(self.request) list_dict = [ { "name": "parent1", "url": "/user/account/", "related_urls": ["/persons/", "/groups/"], } ] nav = self.menu.generate_menu(list_dict) self.assertEqual(len(nav), 1) self.assertEqual(nav[0]["selected"], True) def test_generate_menu_selected_related_urls_submenu(self): self.request.user = TestUser(authenticated=True) self.request.path = "/persons/1/" self.menu.save_user_state(self.request) list_dict = [ { "name": "parent1", "url": "/user/account/", "submenu": [ { "name": "child1", "url": '/user/account/profile/', "related_urls": ["/persons/"] }, { "name": "child2", "url": 'named_url', "related_urls": ["/groups/"] }, ], } ] nav = self.menu.generate_menu(list_dict) self.assertEqual(len(nav), 1) self.assertEqual(nav[0]["selected"], True) self.assertEqual(nav[0]["submenu"][0]["selected"], True) self.assertEqual(nav[0]["submenu"][1]["selected"], False) def test_generate_menu_selected_related_views_simple(self): self.request.user = TestUser(authenticated=True) self.request.path = "/known-view/" self.menu.save_user_state(self.request) list_dict = [ { "name": "parent1", "url": "/user/account/", "related_views": [testview], } ] nav = self.menu.generate_menu(list_dict) self.assertEqual(len(nav), 1) self.assertEqual(nav[0]["selected"], True) def test_generate_menu_selected_related_views_submenu(self): self.request.user = TestUser(authenticated=True) self.request.path = "/known-view/" self.menu.save_user_state(self.request) list_dict = [ { "name": "parent1", "url": "/user/account/", "submenu": [ { "name": "child1", "url": '/user/account/profile/', "related_views": [testview] }, { "name": "child2", "url": 'named_url', "related_views": [] }, ], } ] nav = self.menu.generate_menu(list_dict) self.assertEqual(len(nav), 1) self.assertEqual(nav[0]["selected"], True) self.assertEqual(nav[0]["submenu"][0]["selected"], True) self.assertEqual(nav[0]["submenu"][1]["selected"], False) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/test_validators.py0000755000175000017500000000231000000000000024370 0ustar00niknikfrom django.http import HttpRequest from django.test import TestCase from .utils import TestUser from ..validators import is_superuser, is_staff, is_authenticated, is_anonymous, user_has_permission class ValidatorsTestCase(TestCase): """ Validators test """ def setUp(self): """ Setup the test. """ self.request = HttpRequest() self.request.path = '/' def test_is_superuser(self): self.request.user = TestUser(authenticated=True, superuser=True) self.assertTrue(is_superuser(self.request)) def test_is_staff(self): self.request.user = TestUser(authenticated=True, staff=True) self.assertTrue(is_staff(self.request)) def test_is_authenticated(self): self.request.user = TestUser(authenticated=True) self.assertTrue(is_authenticated(self.request)) def test_is_anonymous(self): self.request.user = TestUser() self.assertTrue(is_anonymous(self.request)) def test_user_has_permission(self): self.request.user = TestUser(authenticated=True) self.request.user.add_perm("test_permission") self.assertTrue(user_has_permission(self.request, "test_permission")) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/testsettings.py0000755000175000017500000000163500000000000023732 0ustar00niknikDATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', }, } SECRET_KEY = "r4dy" INSTALLED_APPS = [ 'menu_generator', 'menu_generator.tests.test_apps.app1.apps.MyAppConfig', 'menu_generator.tests.test_apps.app2', 'menu_generator.tests.test_apps.app3' ] MIDDLEWARE_CLASSES = [] ROOT_URLCONF = 'menu_generator.tests.urls' NAV_MENU = [ { "name": "Main", "url": "/", }, { "name": "Account", "url": "/account", "validators": ["menu_generator.validators.is_authenticated", ], "submenu": [ { "name": "Profile", "url": '/account/profile/', }, ], }, { "name": "Create User", "url": "users:create" }, { "name": "Update User", "url": {"viewname": "users:update", "kwargs": {'pk': 1}} } ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/urls.py0000755000175000017500000000047400000000000022157 0ustar00niknikfrom django.conf.urls import url def testview(request): return 'foo' urlpatterns = [ url(r'named-url', lambda: 'foo', name='named_url'), url(r'named-with-params/(?P\d+)/', lambda: 'foo', name='named_with_params'), url(r'known-view', testview, name='known_view'), url(r'', lambda: 'foo'), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/tests/utils.py0000755000175000017500000000276400000000000022336 0ustar00niknikdef is_user_happy(request): return request.user.is_auth and request.user.is_happy # pragma: no cover def is_paid_user(request): return request.user.is_auth and request.user.is_happy # pragma: no cover def is_main_site(request): """ Non-User condition. """ return True # pragma: no covere def validator_with_parameters(request, param1, param2): return True class TestUser(object): """ Test User Object. """ is_auth = False is_staff = False is_superuser = False is_happy = False is_paid = False permissions = [] def __init__(self, staff=False, superuser=False, authenticated=False, happy=False, paid=False): self.is_auth = authenticated self.is_staff = authenticated and staff self.is_superuser = authenticated and superuser self.is_happy = authenticated and happy self.is_paid = authenticated and paid self.permissions = [] @property def is_authenticated(self): return self.is_auth def add_perm(self, permission): """ Method for add a permission to test user :param permission: Permission to be added """ self.permissions.append(permission) def has_perm(self, permission): """ Method for checking if a test user has a permission :param permission: Permission to be checked :return: Boolean indicating if a test user has a permission or not """ return permission in self.permissions ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/utils.py0000755000175000017500000000352600000000000021171 0ustar00niknikfrom importlib import import_module import django from django.apps import apps from django.core.exceptions import ImproperlyConfigured if django.VERSION >= (1, 10): # pragma: no cover from django.urls import reverse, NoReverseMatch else: from django.core.urlresolvers import reverse, NoReverseMatch def get_callable(func_or_path): """ Receives a dotted path or a callable, Returns a callable or None """ if callable(func_or_path): return func_or_path module_name = '.'.join(func_or_path.split('.')[:-1]) function_name = func_or_path.split('.')[-1] _module = import_module(module_name) func = getattr(_module, function_name) return func def clean_app_config(app_path): """ Removes the AppConfig path for this app and returns the new string """ apps_names = [app.name for app in apps.get_app_configs()] if app_path in apps_names: return app_path else: app_split = app_path.split('.') new_app = '.'.join(app_split[:-2]) if new_app in apps_names: return new_app else: # pragma: no cover raise ImproperlyConfigured( "The application {0} is not in the configured apps or does".format(app_path) + "not have the pattern app.apps.AppConfig" ) def parse_url(url): """ Returns concrete URL for a menu dict URL attribute. """ try: final_url = reverse(**url) if type(url) is dict else reverse(url) except NoReverseMatch: final_url = url return final_url def path_startswith(path, prefix): """ Returns True if the leftmost path components are the same as prefix. """ path_components = path.strip("/").split("/") prefix_components = prefix.strip("/").split("/") return path_components[:len(prefix_components)] == prefix_components ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/menu_generator/validators.py0000644000175000017500000000163000000000000022170 0ustar00niknikdef is_superuser(request): """ Returns True if request.user is superuser else returns False """ return is_authenticated(request) and request.user.is_superuser def is_staff(request): """ Returns True if request.user is staff else returns False """ return is_authenticated(request) and request.user.is_staff def is_authenticated(request): """ Returns True if request.user authenticated else returns False """ return request.user.is_authenticated def is_anonymous(request): """ Returns True if request.user is not authenticated else returns False """ return not request.user.is_authenticated def user_has_permission(request, permission): """ Returns True if request.user has the permission else returns False :param request: HttpRequest :param permission: Permission to be searched """ return request.user.has_perm(permission) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1616968500.734314 django-menu-generator-ng-1.2.3/setup.cfg0000644000175000017500000000020100000000000016246 0ustar00niknik[metadata] description-file = README.rst [flake8] max-line-length = 122 exclude = docs/* [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616885458.0 django-menu-generator-ng-1.2.3/setup.py0000755000175000017500000000462400000000000016157 0ustar00niknik#!/usr/bin/env python3 from setuptools import setup import re import os import sys name = 'django-menu-generator-ng' package = 'menu_generator' description = "A straightforward menu generator for Django" url = 'https://edugit.org/AlekSIS/libs/django-menu-generator-ng' author = 'The AlekSIS Team' author_email = 'aleksis-dev@lists.teckids.org' keywords = "django navigation menu generator" license = 'MIT' install_requires = [] classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', "Programming Language :: Python :: 3", "Framework :: Django", 'Topic :: Utilities' ] def get_version(package): """ Return package version as listed in `__version__` in `init.py`. """ init_py = open(os.path.join(package, '__init__.py')).read() return re.search("^__version__ = ['\"]([^'\"]+)['\"]", init_py, re.MULTILINE).group(1) def get_packages(package): """ Return root package and all sub-packages. """ return [dirpath for dirpath, dirnames, filenames in os.walk(package) if os.path.exists(os.path.join(dirpath, '__init__.py'))] def get_package_data(package): """ Return all files under the root package, that are not in a package themselves. """ walk = [(dirpath.replace(package + os.sep, '', 1), filenames) for dirpath, dirnames, filenames in os.walk(package) if not os.path.exists(os.path.join(dirpath, '__init__.py'))] filepaths = [] for base, filenames in walk: filepaths.extend([os.path.join(base, filename) for filename in filenames]) return {package: filepaths} if sys.argv[-1] == 'publish': os.system("python setup.py sdist upload") args = {'version': get_version(package)} print("You probably want to also tag the version now:") print(" git tag -a %(version)s -m 'version %(version)s' && git push --tags" % args) sys.exit() setup( name=name, version=get_version(package), url=url, license=license, description=description, author=author, author_email=author_email, packages=get_packages(package), package_data=get_package_data(package), install_requires=install_requires, classifiers=classifiers, )