From 7c883887d18eae8cf37ab38670564766936c311e Mon Sep 17 00:00:00 2001 From: MiltonLn <miltonln04@gmail.com> Date: Sat, 29 Apr 2017 16:26:00 -0500 Subject: [PATCH] Docs finished. --- README.rst | 10 +- docs/_build_html/.doctrees/changelog.doctree | Bin 2219 -> 4076 bytes docs/_build_html/.doctrees/environment.pickle | Bin 10808 -> 14489 bytes .../.doctrees/menugeneration.doctree | Bin 2248 -> 20129 bytes docs/_build_html/.doctrees/urls.doctree | Bin 2194 -> 7736 bytes docs/_build_html/.doctrees/validators.doctree | Bin 2224 -> 24340 bytes docs/_build_html/_sources/changelog.rst.txt | 14 ++ .../_sources/menugeneration.rst.txt | 84 ++++++++++++ docs/_build_html/_sources/urls.rst.txt | 38 ++++++ docs/_build_html/_sources/validators.rst.txt | 121 ++++++++++++++++++ docs/_build_html/changelog.html | 27 +++- docs/_build_html/index.html | 25 +++- docs/_build_html/menugeneration.html | 86 ++++++++++++- docs/_build_html/searchindex.js | 2 +- docs/_build_html/urls.html | 39 +++++- docs/_build_html/validators.html | 113 +++++++++++++++- docs/changelog.rst | 14 ++ docs/menugeneration.rst | 84 ++++++++++++ docs/urls.rst | 38 ++++++ docs/validators.rst | 121 ++++++++++++++++++ setup.cfg | 1 + 21 files changed, 801 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index ed9b11c..4498cf5 100644 --- a/README.rst +++ b/README.rst @@ -31,14 +31,14 @@ 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/RADYConsultores/django-menu-generator +- ``easy_install django-menu-generator`` +- ``pip install django-menu-generator`` +- ``git clone https://github.com/RADYConsultores/django-menu-generator`` - 1. cd django-menu-generator + 1. ``cd django-menu-generator`` 2. run python setup.py -- wget https://github.com/RADYConsultores/django-menu-generator/zipball/master +- ``wget https://github.com/RADYConsultores/django-menu-generator/zipball/master`` 1. unzip the downloaded file 2. cd into django-menu-generator-\* directory diff --git a/docs/_build_html/.doctrees/changelog.doctree b/docs/_build_html/.doctrees/changelog.doctree index b2d55c6ce02adf6da61aac4d85dc9d5089eb7991..ccaac60189bb31dc23d55ba3297b021313e478b2 100644 GIT binary patch delta 1633 zcmb`I-)qxQ6vrF8u4^l+8;*f<E;3}DqoXZ?KTu(tbJjr~1Yr+u^R~J6WTr_;6Q>U{ zeGpNCAj$p!AH)ZrWN(5@5C#(wL7xN}d-qQm%yW~bNz*d+;6qAs&bjw|?)RQ^`o8b) zNbE`1`fmDlti)wLRFd2RB*&lHkL?V^pc~?g_>%SvW?z0XH<`~Aa{2Sw+_`Mw;<(L1 zV4#!dneFY9gA*`medJEA888OD_%#6>L?Hv?aCSaslptcJN`@>`eOZ?3x^3BBN{=C8 zCYWbg(rGo|Hj`7LNJU;$%XJq818YPh6^+y&5D2pj^c0c?SNMpt2vuZ=eGGA~ffh&W zOcQ(xzpnb=0&-7#oM!qKC1REGQ~{!tsp!(&@cgjU5UMTCXIR4h?;0fnLrnf*)O7@( z)A)>9Ke+A|vL1}Rb2u_#`u5!Q;=(1~m5T6XRnur$msEw<RbB_mKOnLs5?$5mJjHi| zIL=i`2gf}nT*F0*f{Rowd@wK?C;Q)dd|?bc>}uQPB+yzvoCN*u(eKIRKE1#WJyQ%H z!)%<QQ>ag3V$N6LE3&?FM_Vo@iBT^Z8EP{I2bN8v8g=zyGDKu1TxfK;o`ImnM|y|J z-nNO8LhBo59qf!itxw$0n%i@>$z^R8b~#Y?PKO#L+AKyhDv5cUcs1Fn;|Yvnv7T9U zHe=V5^1H=p^U!Fy8JsQsY_{b1=5WdR!=;nU7vMW4`qr!iHeI#@X?w9tuQa>RnP|bT zEh>^O5t-MhOi7)F?f`bYt_{ce>31;Uu2T}$CO6np2glI$hmjrkF5$ONCOF8j37><~ zvn39GSo%Uh`c8Trs6C1hxX45qM^uH%YQ^4o&dnc*tz@}KWaC$KJvy6NiR1f97yiH& ye1Uz~Gt7iSs#JeeqjmPj;6w<pCBVL5z%a8o;VBHCfIZkYtQYa0T+~|Z5&i-3og@AL delta 75 zcmaDOzgm#Bfo19fj)|;E8#A~V851{mb0jb^WrS`1z%`XwA;Ta;x44HhFEKZ@q_QBj ecuMV*3<JO73|Szpo55W?`4rz}M#jnW`27IjRT{wn diff --git a/docs/_build_html/.doctrees/environment.pickle b/docs/_build_html/.doctrees/environment.pickle index 6e6049c00d3c5e43b2d529db4052baf17a1bb06a..f7723c50a5903ec7eab9c4c4da9adae298f5c36c 100644 GIT binary patch literal 14489 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 delta 2894 zcmbtWZERCj7~Z*S*L7{TZtGgst=qEkQNV4o!5BhNHj$5MWPu2p;B2?cZcn;)y}fs6 z+^B=KNOFad_i92yFir$P2on9FCMJ-Wn3zD6s4?*mCMKfshY7}n2+{Z4bLj^g{;)sx z-sgSJ$Md}B-tIjo5})j{zGiu|eZ^&KQ(4NYh)E@>YxZF&6dM(z@ljo?m<UB9BOxU& z<A7C7hDJqQvqdArVl0WMa!ib=qhd^yLrNqb(>04KM{%Yt5=$zfXcSRW9UqU&3T`zV zk128_tSW4aVOboBFg0r=HX^>P&uTDNMpF-NxMkD#PrSHz&*iavR|Yxo$R%4&hg0p) zWjf$_$ENcI6`1VN+c{eXHU0O{aw&nZ?1`bibax7l*`g%z(OsLT6mB*9;fm7(y^ayc zbb8?V$|_O|o1BB-Le}jFmeOSeIs%Vc=~7Zs0>_*_SnsMA))=JSAl0xm17}^mLVqEd zh9izHGRSr#38-{-5fSlIaH(>mcMWbP&Cq`KNYg>~NKg?<t9~IzVX(RxD%`!~2%=UH z-M+p<I6)25DSRxS)~EEK^o}$qy}MTr(u4Cj`ucq84VVpj;D)=6Tr#&`4C|`h<)?6} zbb($#vaS3*o}4pePgPfSo<Ra-=Su`1uJK288~HY?vAZU#@eR{xH8rmDo#z)sGw^ZE zo$h-$PXA$n2pCFPu(Dud!Ont%1s4ldEL6kI+Rw@FhW9I;YH}U!dD_aa;TBlm86#PW z^DsG-KXQP%>SrVP8YdSx;RULHc=6Zj{A4Svs&9sKi@Xr<wvx@58Sjwv8R(?9iac(h z>)zVO7N~#zVLx21)8`}mvvQL@Px&5RqJm^w`4CSMMzW75F_!d?@p=ywQ}2%UI*2T4 zA#<jI75By8&KEzKFMc9lJXMT1`<g$fuOrvsYW;5V6~<=h_4~<}29o_X<O>6x_Iryx z<Xg=R(9&QN?g}v5&<@Wv^a^(v+#>wN9;=|Du~N9r;Xq?ev2vNtx^h4NYF9#gpaTWH zP!VV)LCm(2B^bMyBxyC!uYsZ!-436fc5ulTvXbvN)!OO$QsWo18`)KM+Q+k9Jln>z zL6$v9cVLFUphLyJ)b*|^JKey|q_bw8ZONHASYReqU}mzw%uKOnK6BOC>2_}BLvCj2 z;!0^wl|H?HG#3lpoG);5w!lrgST`pfH6FT$yZO;L?vQj5djXJOqd>!OwS75sxINAv z78pCeq}lo10z7H-Id3e$-wf1mNEB;$TT^Xql>4o)u+PKvd%OevPQYM8!?J`{5sD2< zaXA(m6Lo&F8ed|2U`Z35z=6FwJN(0Zise71O!Fz^fho?sT_)kJ?u{-Y%?uDJjem&& z0zY;4rT$0r!T+aXQgiQ%M8)Kg5+9Pquo{VuuvSgkcE_+~zdCDlG!wGEW<u88Ovrj% zmx)=cGa+kuCS+~Tgsk~-2#M8R_-*x{si(2rff7{{XwuN$j`@^Mv-J2Yd9-F5i;t-2 z7`g0}QaP#Q&B^ge#VAK}jLY%S_&B=}8Vk7u-ryoi2=?~02nh?MdR7Xu1upaiY!m2! z?zh0#Jxfvw!;H|J<)r+l=dBMxWLVo~Cdxt!MP`VO=XriXnMe(rFq=X^!SrJ9Zi) zEj!qSHMcmETdcD0Djti5!Xn;S*;`0hN>-Gxh+AcJI)#89xSufvQzoOUSTmejyBRNS PKd;>>oVLKa-k1IbIuX1r diff --git a/docs/_build_html/.doctrees/menugeneration.doctree b/docs/_build_html/.doctrees/menugeneration.doctree index 8dd4960f7317042ff529f80fa06c572c77bf5c36..21626c8ad0acb84fca406fded570266372947f07 100644 GIT binary patch literal 20129 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 delta 71 zcmZ2Dm+^!kYXi&Fy&Mx+lQw2ZGcqP_7IV*GV#)~H-0nG*Ss}w9L$|nxGcPeWwWP8j awRlSHlnevE;tW|Jt((DJyt&?2i4g#9J{Z6N diff --git a/docs/_build_html/.doctrees/urls.doctree b/docs/_build_html/.doctrees/urls.doctree index 94527770bb220113e0ca8de8d36fcfab903d4e19..6c0c8324a768c006496629fce7fc9932befcce5d 100644 GIT binary patch literal 7736 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 delta 69 zcmdmCGf9xOfn{nt$3)hoi5b5c6F2V`3u9u+2-_?sIh9!<!yrSqxQ8<@F*miOvLLm1 YO6`;k1Ha-7Ss<;O!CkyrOjd~z09IEQ>;M1& diff --git a/docs/_build_html/.doctrees/validators.doctree b/docs/_build_html/.doctrees/validators.doctree index d2f609e7edaca3d6181187a750bc8d86cdcd5685..e1c7c24e0fcd8454e5697b81d92442a9e0d9b19d 100644 GIT binary patch literal 24340 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 O50wmNnmD7)#{UA}p(UsQ delta 71 zcmbQTk8y(_YXi&Fr5qDklQw4XGBPG^o)(_O#FP=X`ETS@W`zuc4Bg@$&b-9j)RM}A a)Z!_%Q!)(viZf(^v~C7>@#aslN{j%Oj~XQa diff --git a/docs/_build_html/_sources/changelog.rst.txt b/docs/_build_html/_sources/changelog.rst.txt index c4df475..33a4f83 100644 --- a/docs/_build_html/_sources/changelog.rst.txt +++ b/docs/_build_html/_sources/changelog.rst.txt @@ -1,2 +1,16 @@ CHANGELOG ========= + +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 \ No newline at end of file diff --git a/docs/_build_html/_sources/menugeneration.rst.txt b/docs/_build_html/_sources/menugeneration.rst.txt index f1426f7..23e2846 100644 --- a/docs/_build_html/_sources/menugeneration.rst.txt +++ b/docs/_build_html/_sources/menugeneration.rst.txt @@ -1,2 +1,86 @@ Menu 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, + "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` + +- ``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 \ No newline at end of file diff --git a/docs/_build_html/_sources/urls.rst.txt b/docs/_build_html/_sources/urls.rst.txt index b5d18e7..c7e4e8d 100644 --- a/docs/_build_html/_sources/urls.rst.txt +++ b/docs/_build_html/_sources/urls.rst.txt @@ -1,2 +1,40 @@ URLs ==== + +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<pk>\d+)/$', SomeView.as_view(), name='update'), + +you can pass the url as follows: + + "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)`` + diff --git a/docs/_build_html/_sources/validators.rst.txt b/docs/_build_html/_sources/validators.rst.txt index 70fbe57..839126a 100644 --- a/docs/_build_html/_sources/validators.rst.txt +++ b/docs/_build_html/_sources/validators.rst.txt @@ -1,2 +1,123 @@ Validators ========== + +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 \ No newline at end of file diff --git a/docs/_build_html/changelog.html b/docs/_build_html/changelog.html index dce67b0..bde3381 100644 --- a/docs/_build_html/changelog.html +++ b/docs/_build_html/changelog.html @@ -45,6 +45,22 @@ <div class="section" id="changelog"> <h1>CHANGELOG<a class="headerlink" href="#changelog" title="Permalink to this headline">¶</a></h1> +<div class="section" id="id1"> +<h2>1.0.1(2017-04-29)<a class="headerlink" href="#id1" title="Permalink to this headline">¶</a></h2> +<ul class="simple"> +<li>Added docs</li> +<li>Readme enhanced</li> +<li>BUGFIX: Added a correction to the validators evaluation with the AND connector</li> +<li>Added flake8 to CI</li> +<li>Added tox</li> +</ul> +</div> +<div class="section" id="id2"> +<h2>1.0.0 (2017-04-09)<a class="headerlink" href="#id2" title="Permalink to this headline">¶</a></h2> +<ul class="simple"> +<li>Initial release</li> +</ul> +</div> </div> @@ -52,7 +68,16 @@ </div> </div> <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> - <div class="sphinxsidebarwrapper"><div class="relations"> + <div class="sphinxsidebarwrapper"> + <h3><a href="index.html">Table Of Contents</a></h3> + <ul> +<li><a class="reference internal" href="#">CHANGELOG</a><ul> +<li><a class="reference internal" href="#id1">1.0.1(2017-04-29)</a></li> +<li><a class="reference internal" href="#id2">1.0.0 (2017-04-09)</a></li> +</ul> +</li> +</ul> +<div class="relations"> <h3>Related Topics</h3> <ul> <li><a href="index.html">Documentation overview</a><ul> diff --git a/docs/_build_html/index.html b/docs/_build_html/index.html index 3561654..38399ce 100644 --- a/docs/_build_html/index.html +++ b/docs/_build_html/index.html @@ -66,10 +66,27 @@ Authentications or whatever you want)</li> <ul> <li class="toctree-l1"><a class="reference internal" href="install.html">Installation</a></li> <li class="toctree-l1"><a class="reference internal" href="usage.html">Usage</a></li> -<li class="toctree-l1"><a class="reference internal" href="menugeneration.html">Menu Generation</a></li> -<li class="toctree-l1"><a class="reference internal" href="urls.html">URLs</a></li> -<li class="toctree-l1"><a class="reference internal" href="validators.html">Validators</a></li> -<li class="toctree-l1"><a class="reference internal" href="changelog.html">CHANGELOG</a></li> +<li class="toctree-l1"><a class="reference internal" href="menugeneration.html">Menu Generation</a><ul> +<li class="toctree-l2"><a class="reference internal" href="menugeneration.html#generating-menus-through-settings">Generating menus through settings</a></li> +<li class="toctree-l2"><a class="reference internal" href="menugeneration.html#generating-menus-through-apps">Generating menus through apps</a></li> +</ul> +</li> +<li class="toctree-l1"><a class="reference internal" href="urls.html">URLs</a><ul> +<li class="toctree-l2"><a class="reference internal" href="urls.html#raw-urls">Raw URLs</a></li> +<li class="toctree-l2"><a class="reference internal" href="urls.html#reversible-urls">Reversible URLs</a></li> +<li class="toctree-l2"><a class="reference internal" href="urls.html#url-with-args-or-kwargs">URL with args or kwargs</a></li> +</ul> +</li> +<li class="toctree-l1"><a class="reference internal" href="validators.html">Validators</a><ul> +<li class="toctree-l2"><a class="reference internal" href="validators.html#built-in-validators">Built-in validators</a></li> +<li class="toctree-l2"><a class="reference internal" href="validators.html#custom-validators">Custom validators</a></li> +</ul> +</li> +<li class="toctree-l1"><a class="reference internal" href="changelog.html">CHANGELOG</a><ul> +<li class="toctree-l2"><a class="reference internal" href="changelog.html#id1">1.0.1(2017-04-29)</a></li> +<li class="toctree-l2"><a class="reference internal" href="changelog.html#id2">1.0.0 (2017-04-09)</a></li> +</ul> +</li> <li class="toctree-l1"><a class="reference internal" href="tests.html">Running the tests</a></li> <li class="toctree-l1"><a class="reference internal" href="contribute.html">Contribute</a></li> <li class="toctree-l1"><a class="reference internal" href="support.html">Support</a></li> diff --git a/docs/_build_html/menugeneration.html b/docs/_build_html/menugeneration.html index 6fba2ee..431e34f 100644 --- a/docs/_build_html/menugeneration.html +++ b/docs/_build_html/menugeneration.html @@ -26,7 +26,7 @@ <script type="text/javascript" src="_static/doctools.js"></script> <link rel="index" title="Index" href="genindex.html" /> <link rel="search" title="Search" href="search.html" /> - <link rel="next" title="<no title>" href="urls.html" /> + <link rel="next" title="URLs" href="urls.html" /> <link rel="prev" title="Usage" href="usage.html" /> <link rel="stylesheet" href="_static/custom.css" type="text/css" /> @@ -45,6 +45,77 @@ <div class="section" id="menu-generation"> <h1>Menu Generation<a class="headerlink" href="#menu-generation" title="Permalink to this headline">¶</a></h1> +<p>Django Menu Generator uses python dictionaries to represent the menu items, usually a menu item is as follows:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="p">{</span> + <span class="s2">"name"</span><span class="p">:</span> <span class="s1">'some name'</span><span class="p">,</span> + <span class="s2">"icon_class"</span><span class="p">:</span> <span class="s1">'some icon class'</span><span class="p">,</span> + <span class="s2">"url"</span><span class="p">:</span> <span class="n">URL</span> <span class="n">spec</span><span class="p">,</span> + <span class="s2">"validators"</span><span class="p">:</span> <span class="p">[</span> <span class="nb">list</span> <span class="n">of</span> <span class="n">validators</span> <span class="p">],</span> + <span class="s2">"submenu"</span><span class="p">:</span> <span class="n">Dictionary</span> <span class="n">like</span> <span class="n">this</span> +<span class="p">}</span> +</pre></div> +</div> +<p>Where each key is as follows:</p> +<ul class="simple"> +<li><code class="docutils literal"><span class="pre">name</span></code>: A string representing the label of the menu item. If you are using i18n here you can pass the name with the <code class="docutils literal"><span class="pre">ugettext_lazy</span></code> function</li> +<li><code class="docutils literal"><span class="pre">icon_class</span></code>: A string representing the class of the icon you wish to show on the menu item, e.g you can use font-awesome</li> +<li><code class="docutils literal"><span class="pre">url</span></code>: See <a class="reference internal" href="urls.html"><span class="doc">URLs</span></a></li> +<li><code class="docutils literal"><span class="pre">validators</span></code>: See <a class="reference internal" href="validators.html"><span class="doc">Validators</span></a></li> +<li><code class="docutils literal"><span class="pre">submenu</span></code>: You can create infinite nested submenus passing here menu items like this</li> +</ul> +<p>Django Menu Generator offers two ways to generate the menus, through the Django settings and through each of the Django +apps</p> +<div class="section" id="generating-menus-through-settings"> +<h2>Generating menus through settings<a class="headerlink" href="#generating-menus-through-settings" title="Permalink to this headline">¶</a></h2> +<p>You can add various list dictionaries representing each menu you have as explained in <a class="reference internal" href="usage.html"><span class="doc">Usage</span></a> +We recommend to have a <code class="docutils literal"><span class="pre">menus.py</span></code> file with the menu list dictionaries and then import it to the settings file if you +go this way</p> +</div> +<div class="section" id="generating-menus-through-apps"> +<h2>Generating menus through apps<a class="headerlink" href="#generating-menus-through-apps" title="Permalink to this headline">¶</a></h2> +<p>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.</p> +<p>You need to add inside the app a <code class="docutils literal"><span class="pre">menus.py</span></code> file that contains a dictionary called <code class="docutils literal"><span class="pre">MENUS</span></code>, each element of the +dictionary will be a menu list dictionary with all the configuration needed to display that menu, e.g:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="n">MENUS</span> <span class="o">=</span> <span class="p">{</span> + <span class="s1">'NAV_MENU_LEFT'</span><span class="p">:</span> <span class="p">[</span> + <span class="p">{</span> + <span class="s2">"name"</span><span class="p">:</span> <span class="s2">"App1 Feature"</span><span class="p">,</span> + <span class="s2">"url"</span><span class="p">:</span> <span class="s2">"/app1-feature"</span> + <span class="p">}</span> + <span class="p">],</span> + <span class="s1">'NAV_MENU_TOP'</span><span class="p">:</span> <span class="p">[</span> + <span class="p">{</span> + <span class="s2">"name"</span><span class="p">:</span> <span class="s2">"Second Menu Feature"</span><span class="p">,</span> + <span class="s2">"url"</span><span class="p">:</span> <span class="s2">"named_url"</span> + <span class="p">}</span> + <span class="p">]</span> +<span class="p">}</span> +</pre></div> +</div> +<p>So, as an example, for the <code class="docutils literal"><span class="pre">'NAV_MENU_LEFT'</span></code>, Django Menu Generator will loop each app searching for the <code class="docutils literal"><span class="pre">'NAV_MENU_LEFT'</span></code> +list dictionaries inside of the <code class="docutils literal"><span class="pre">MENUS</span></code> and build all the menu configuration to build the whole menu.</p> +<p>With this feature you can have a project structure like this:</p> +<div class="highlight-default"><div class="highlight"><pre><span></span>your_project/ +├── config_folder/ +│ └── ... +├── app1 +│ └── models.py +│ forms.py +│ views.py +│ menus.py +│ +├── app2 +│ └── models.py +│ forms.py +│ views.py +│ menus.py +│ + ... +</pre></div> +</div> +<p>You can have a mix of the two approaches if you wish</p> +</div> </div> @@ -52,12 +123,21 @@ </div> </div> <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> - <div class="sphinxsidebarwrapper"><div class="relations"> + <div class="sphinxsidebarwrapper"> + <h3><a href="index.html">Table Of Contents</a></h3> + <ul> +<li><a class="reference internal" href="#">Menu Generation</a><ul> +<li><a class="reference internal" href="#generating-menus-through-settings">Generating menus through settings</a></li> +<li><a class="reference internal" href="#generating-menus-through-apps">Generating menus through apps</a></li> +</ul> +</li> +</ul> +<div class="relations"> <h3>Related Topics</h3> <ul> <li><a href="index.html">Documentation overview</a><ul> <li>Previous: <a href="usage.html" title="previous chapter">Usage</a></li> - <li>Next: <a href="urls.html" title="next chapter"><no title></a></li> + <li>Next: <a href="urls.html" title="next chapter">URLs</a></li> </ul></li> </ul> </div> diff --git a/docs/_build_html/searchindex.js b/docs/_build_html/searchindex.js index c8be8b7..4fa264a 100644 --- a/docs/_build_html/searchindex.js +++ b/docs/_build_html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["changelog","contribute","credits","index","install","license","menugeneration","support","tests","urls","usage","validators"],envversion:51,filenames:["changelog.rst","contribute.rst","credits.rst","index.rst","install.rst","license.rst","menugeneration.rst","support.rst","tests.rst","urls.rst","usage.rst","validators.rst"],objects:{},objnames:{},objtypes:{},terms:{"class":10,"float":10,Then:[],_gener:[],about:10,abov:[],account:10,acount:10,activ:10,add:10,address:10,against:8,app:10,authent:3,author:2,automat:3,balanc:10,bar:10,bodi:10,breadcrum:3,build:[3,10],can:[4,10],changelog:3,clone:4,code:1,com:[1,2,4,7,10],condit:3,contact:[7,10],contain:10,contribut:3,credit:3,current:8,databas:3,dictionari:[3,10],directori:4,displai:3,div:10,django:[1,2,4,10],doc:[],doctyp:10,download:4,each:3,easy_instal:4,enabl:3,end:10,endfor:10,endif:10,environ:8,exampl:10,facebook:10,file:[4,10],foobar:10,footer:10,footer_menu_left:10,footer_menu_right:10,full:3,gener:[1,4,10],get_menu:10,git:4,github:[1,2,4],handl:10,have:7,head:10,his:3,home:10,href:10,html:[3,10],http:[1,2,4],icon:3,icon_class:10,identifi:3,insid:10,instal:[3,10],installed_app:10,instruct:[],is_anonym:10,is_authent:10,is_paid_us:10,is_superus:10,issu:[1,7],item:[3,10],kneeman:2,know:7,left:10,left_footer_menu:10,left_menu:10,let:7,licens:3,like:[2,3],list:[3,10],load:10,login:10,login_url_view:10,loop:10,manag:8,mantain:7,master:4,menu:[1,4,10],menu_gener:10,menuwar:2,mit:5,more:10,must:10,myapp:10,name:[2,10],nav:10,nav_menu_left:10,nav_menu_right:10,need:3,now:10,onc:[3,10],one:4,onli:3,opensourc:7,option:4,origin:2,per:[],permiss:3,pip:4,pleas:7,product:3,profil:10,project:[2,7,10],python:[3,4,8],radyconsultor:[1,4,7],regist:10,register_view_url:10,releas:5,revers:10,right:10,right_footer_menu:10,right_menu:10,run:[3,4,10],secret:10,see:10,select:[3,10],semi:3,set:10,setup:[3,4],sourc:1,start:10,structur:3,style:10,submenu:10,support:3,tag:10,templat:10,test:3,thank:2,thi:[2,7],through:[3,10],titl:10,tool:3,tracker:1,un33k:2,under:[2,5],unlimit:3,unzip:4,url:[3,10],usag:3,use:3,val:2,valid:[3,10],want:3,wget:4,whatev:3,when:10,would:2,you:[3,4,7,10],your:10,zipbal:4},titles:["CHANGELOG","Contribute","Credits","Welcome to Django Menu Generator’s documentation!","Installation","License","Menu Generation","Support","Running the tests","URLs","Usage","Validators"],titleterms:{changelog:0,content:3,contribut:1,credit:2,django:3,document:3,featur:3,gener:[3,6],instal:4,licens:5,menu:[3,6],run:8,support:7,test:8,url:9,usag:10,valid:11,welcom:3}}) \ No newline at end of file +Search.setIndex({docnames:["changelog","contribute","credits","index","install","license","menugeneration","support","tests","urls","usage","validators"],envversion:51,filenames:["changelog.rst","contribute.rst","credits.rst","index.rst","install.rst","license.rst","menugeneration.rst","support.rst","tests.rst","urls.rst","usage.rst","validators.rst"],objects:{},objnames:{},objtypes:{},terms:{"boolean":11,"class":[6,10],"float":10,"function":[6,11],"import":6,"return":11,AND:[0,11],Added:0,For:9,Then:[],With:6,_gener:[],about:10,abov:[],access:[],account:10,acount:10,activ:10,add:[6,10],address:10,against:8,all:[6,11],allow:[6,11],alwai:11,ani:9,app1:[6,11],app2:6,app:[3,10],app_label:11,approach:6,arg:[3,11],as_view:9,aspect:6,assum:11,authent:[3,11],author:2,automat:3,awesom:6,balanc:10,bar:10,belong:11,between:6,bodi:10,breadcrum:3,bugfix:0,build:[3,6,10,11],built:3,call:[6,11],callabl:11,can:[4,6,9,10,11],changelog:3,check:11,clone:4,code:[1,9],com:[1,2,4,7,10],condit:3,config_fold:6,configur:6,connector:[0,11],construct:[],contact:[7,10],contain:[6,10,11],contribut:3,cookiecutt:[],correct:0,count:11,creat:6,credit:3,current:8,current_app:9,custom:3,databas:3,def:11,dictionari:[3,6,9,10],directori:4,displai:[3,6,11],div:10,django:[1,2,4,6,9,10,11],doc:0,doctyp:10,dog:11,download:4,dummi:11,each:[3,6],easy_instal:4,element:6,enabl:3,end:10,endfor:10,endif:10,enhanc:0,environ:8,evalu:[0,11],exampl:[6,10,11],explain:6,facebook:10,fact:9,featur:[6,9],file:[4,6,10],first:11,flake8:0,follow:[6,9,11],font:6,foobar:10,footer:10,footer_menu_left:10,footer_menu_right:10,foreign:11,form:6,full:3,gener:[1,4,10,11],get_menu:10,git:4,github:[1,2,4],given:11,handl:10,hard:9,has:11,has_a_pet_of_typ:11,has_more_than_one_pet:11,have:[6,7,9,11],head:10,here:6,his:3,home:10,href:10,html:[3,10],http:[1,2,4],i18n:6,icon:[3,6],icon_class:[6,10],identifi:3,illustr:11,indic:11,infinit:6,initi:0,insid:[6,10,11],instal:[3,10],installed_app:10,instruct:[],is_anonym:[10,11],is_authent:[10,11],is_paid_us:10,is_staff:11,is_superus:[10,11],isol:6,issu:[1,7],item:[3,6,9,10,11],json:[],kei:[6,11],kneeman:2,know:7,kwarg:3,label:6,least:11,left:10,left_footer_menu:10,left_menu:10,let:[7,11],licens:3,like:[2,3,6,9,11],list:[3,6,10,11],live:6,load:10,login:10,login_url_view:10,loop:[6,10],manag:8,mani:11,mantain:7,master:4,member:11,menu:[1,4,9,10,11],menu_gener:[10,11],menu_valid:11,menuwar:2,method:9,mit:5,mix:6,model:6,more:[10,11],must:[10,11],myapp:10,name:[2,6,9,10],named_url:[6,9],nav:10,nav_menu_left:[6,10],nav_menu_right:10,nav_menu_top:6,need:[3,6],nest:6,none:9,normal:[],now:[10,11],offer:6,onc:[3,10],one:[4,11],onli:3,opensourc:7,option:4,origin:2,own:11,paramet:[9,11],pass:[6,9,11],past:11,path:[9,11],peopl:6,per:[],permiss:[3,11],permission_codenam:11,pet:11,pip:4,pleas:7,posit:11,prefer:6,product:3,profil:10,project:[2,6,7,10],python:[3,4,6,8,11],radyconsultor:[1,4,7],raw:3,readm:0,receiv:11,recommend:6,regist:10,register_view_url:10,relat:11,releas:[0,5],rememb:11,repres:6,request:11,rest:11,revers:[3,10],right:10,right_footer_menu:10,right_menu:10,run:[3,4,10],same:11,search:6,second:6,secret:10,see:[6,10,11],select:[3,10],semi:3,set:[3,10],setup:[3,4],show:6,sign:9,some:[6,9],some_app:11,some_permiss:11,someview:9,sourc:1,spec:6,specif:11,staff:11,start:10,string:6,structur:[3,6],style:10,submenu:[6,10],superus:11,support:3,tag:10,templat:10,test:3,than:11,thank:2,them:11,thi:[2,6,7,9,11],three:9,through:[3,9,10],titl:10,tool:3,tox:0,tracker:1,tupl:11,two:6,type:11,ugettext_lazi:6,un33k:2,under:[2,5],unlimit:3,unzip:4,updat:9,url:[3,6,10],urlconf:9,usag:[3,6,11],use:[3,6,11],user:11,user_has_permiss:11,uses:[6,11],using:[6,11],usual:6,val:2,valid:[0,3,6,10],variabl:[],variou:6,view:6,viewnam:9,wai:[6,9],want:3,wget:4,whatev:3,when:10,where:[6,11],whole:6,wish:6,would:2,you:[3,4,6,7,9,10,11],your:[10,11],your_project:[6,11],zipbal:4},titles:["CHANGELOG","Contribute","Credits","Welcome to Django Menu Generator’s documentation!","Installation","License","Menu Generation","Support","Running the tests","URLs","Usage","Validators"],titleterms:{app:6,arg:9,built:11,changelog:0,content:3,contribut:1,credit:2,custom:11,django:3,document:3,featur:3,gener:[3,6],instal:4,kwarg:9,licens:5,menu:[3,6],raw:9,revers:9,run:8,set:6,support:7,test:8,through:6,url:9,usag:10,valid:11,welcom:3}}) \ No newline at end of file diff --git a/docs/_build_html/urls.html b/docs/_build_html/urls.html index 8dc8ddf..ba859d0 100644 --- a/docs/_build_html/urls.html +++ b/docs/_build_html/urls.html @@ -45,6 +45,33 @@ <div class="section" id="urls"> <h1>URLs<a class="headerlink" href="#urls" title="Permalink to this headline">¶</a></h1> +<p>You can pass the URL parameters to menu items in three ways.</p> +<div class="section" id="raw-urls"> +<h2>Raw URLs<a class="headerlink" href="#raw-urls" title="Permalink to this headline">¶</a></h2> +<p>A hard-coded url:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="s2">"url"</span><span class="p">:</span> <span class="s1">'/some-path/to-feature'</span> +</pre></div> +</div> +</div> +<div class="section" id="reversible-urls"> +<h2>Reversible URLs<a class="headerlink" href="#reversible-urls" title="Permalink to this headline">¶</a></h2> +<p>An url that can be reversed with the <cite>reverse</cite> method:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="s2">"url"</span><span class="p">:</span> <span class="s1">'named_url'</span> +</pre></div> +</div> +</div> +<div class="section" id="url-with-args-or-kwargs"> +<h2>URL with args or kwargs<a class="headerlink" href="#url-with-args-or-kwargs" title="Permalink to this headline">¶</a></h2> +<p>e.g. If you have an url with kwargs like this:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^update/(?P<pk>\d+)/$'</span><span class="p">,</span> <span class="n">SomeView</span><span class="o">.</span><span class="n">as_view</span><span class="p">(),</span> <span class="n">name</span><span class="o">=</span><span class="s1">'update'</span><span class="p">),</span> +</pre></div> +</div> +<p>you can pass the url as follows:</p> +<blockquote> +<div>“url”: {“viewname”: ‘update’, “kwargs”: {“pk”: 1}}</div></blockquote> +<p>In fact, you can pass any of the parameters of the reverse method through the dictionary</p> +<p>For Django 1.10 the reverse method sign is: <code class="docutils literal"><span class="pre">reverse(viewname,</span> <span class="pre">urlconf=None,</span> <span class="pre">args=None,</span> <span class="pre">kwargs=None,</span> <span class="pre">current_app=None)</span></code></p> +</div> </div> @@ -52,7 +79,17 @@ </div> </div> <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> - <div class="sphinxsidebarwrapper"><div class="relations"> + <div class="sphinxsidebarwrapper"> + <h3><a href="index.html">Table Of Contents</a></h3> + <ul> +<li><a class="reference internal" href="#">URLs</a><ul> +<li><a class="reference internal" href="#raw-urls">Raw URLs</a></li> +<li><a class="reference internal" href="#reversible-urls">Reversible URLs</a></li> +<li><a class="reference internal" href="#url-with-args-or-kwargs">URL with args or kwargs</a></li> +</ul> +</li> +</ul> +<div class="relations"> <h3>Related Topics</h3> <ul> <li><a href="index.html">Documentation overview</a><ul> diff --git a/docs/_build_html/validators.html b/docs/_build_html/validators.html index c0ac7cc..9044771 100644 --- a/docs/_build_html/validators.html +++ b/docs/_build_html/validators.html @@ -45,6 +45,108 @@ <div class="section" id="validators"> <h1>Validators<a class="headerlink" href="#validators" title="Permalink to this headline">¶</a></h1> +<p>Django Menu Generator uses validators to allow the displaying of menu items.</p> +<p>A validator is a function that receives the request as arg and returns a boolean indicating if the check has passed</p> +<p>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.</p> +<div class="section" id="built-in-validators"> +<h2>Built-in validators<a class="headerlink" href="#built-in-validators" title="Permalink to this headline">¶</a></h2> +<p>Django Menu Generator has the following built-in validators:</p> +<ul> +<li><p class="first">is_superuser:</p> +<blockquote> +<div><p>A validator to check if the authenticated user is a superuser</p> +<p>Usage:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="s2">"validators"</span><span class="p">:</span> <span class="p">[</span><span class="s1">'menu_generator.validators.is_superuser'</span><span class="p">]</span> +</pre></div> +</div> +</div></blockquote> +</li> +<li><p class="first">is_staff:</p> +<blockquote> +<div><p>A validator to check if the authenticated user is member of the staff</p> +<p>Usage:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="s2">"validators"</span><span class="p">:</span> <span class="p">[</span><span class="s1">'menu_generator.validators.is_staff'</span><span class="p">]</span> +</pre></div> +</div> +</div></blockquote> +</li> +<li><p class="first">is_authenticated:</p> +<blockquote> +<div><p>A validator to check if user is authenticated</p> +<p>Usage:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="s2">"validators"</span><span class="p">:</span> <span class="p">[</span><span class="s1">'menu_generator.validators.is_authenticated'</span><span class="p">]</span> +</pre></div> +</div> +</div></blockquote> +</li> +<li><p class="first">is_anonymous:</p> +<blockquote> +<div><p>A validator to check if the user is not authenticated</p> +<p>Usage:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="s2">"validators"</span><span class="p">:</span> <span class="p">[</span><span class="s1">'menu_generator.validators.is_anonymous'</span><span class="p">]</span> +</pre></div> +</div> +</div></blockquote> +</li> +<li><p class="first">user_has_permission:</p> +<blockquote> +<div><p>A validator to check if the user has the given permission</p> +<p>Usage:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="s2">"validators"</span><span class="p">:</span> <span class="p">[</span> + <span class="p">(</span><span class="s1">'menu_generator.validators.user_has_permission'</span><span class="p">,</span> <span class="s1">'app_label.permission_codename'</span><span class="p">)</span> +<span class="p">]</span> +</pre></div> +</div> +</div></blockquote> +</li> +<li><p class="first">More than one validator:</p> +<blockquote> +<div><p>You can pass more than one validator to evaluate using the AND connector</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="s2">"validators"</span><span class="p">:</span> <span class="p">[</span> + <span class="s1">'menu_generator.validators.is_staff'</span><span class="p">,</span> + <span class="p">(</span><span class="s1">'menu_generator.validators.user_has_permission'</span><span class="p">,</span> <span class="s1">'some_app.some_permission'</span><span class="p">)</span> + <span class="o">...</span> +<span class="p">]</span> +</pre></div> +</div> +</div></blockquote> +</li> +</ul> +</div> +<div class="section" id="custom-validators"> +<h2>Custom validators<a class="headerlink" href="#custom-validators" title="Permalink to this headline">¶</a></h2> +<p>You can build your own validators and use them with Django Menu Generator</p> +<p>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</p> +<p>Assuming we build the function inside <code class="docutils literal"><span class="pre">your_project/app1</span></code> on a <code class="docutils literal"><span class="pre">menu_validators.py</span></code> we have:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="c1"># Remember you always must to past the request as first parameter</span> +<span class="k">def</span> <span class="nf">has_more_than_one_pet</span><span class="p">(</span><span class="n">request</span><span class="p">):</span> + + <span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">pets</span><span class="o">.</span><span class="n">count</span><span class="p">()</span> <span class="o">></span> <span class="mi">0</span> +</pre></div> +</div> +<p>So we can use it as a validator</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="s2">"validators"</span><span class="p">:</span> <span class="p">[</span><span class="s1">'your_project.app1.menu_validators.has_more_than_one_pet'</span><span class="p">]</span> +</pre></div> +</div> +<p>Now let’s build a validator that checks if the user’s pet belongs to a specific type to illustrate the validators with +parameters.</p> +<p>Assuming we build the function inside the same path and the user have a foreign key called pet</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">has_a_pet_of_type</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="nb">type</span><span class="p">):</span> + + <span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">pet</span><span class="o">.</span><span class="n">type</span> <span class="o">==</span> <span class="nb">type</span> +</pre></div> +</div> +<p>So we use the validator like this:</p> +<div class="code python highlight-default"><div class="highlight"><pre><span></span><span class="s2">"validators"</span><span class="p">:</span> <span class="p">[</span> + <span class="p">(</span><span class="s1">'your_project.app1.menu_validators.has_a_pet_of_type'</span><span class="p">,</span> <span class="s1">'DOG'</span><span class="p">)</span> +<span class="p">]</span> +</pre></div> +</div> +<p>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</p> +</div> </div> @@ -52,7 +154,16 @@ </div> </div> <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> - <div class="sphinxsidebarwrapper"><div class="relations"> + <div class="sphinxsidebarwrapper"> + <h3><a href="index.html">Table Of Contents</a></h3> + <ul> +<li><a class="reference internal" href="#">Validators</a><ul> +<li><a class="reference internal" href="#built-in-validators">Built-in validators</a></li> +<li><a class="reference internal" href="#custom-validators">Custom validators</a></li> +</ul> +</li> +</ul> +<div class="relations"> <h3>Related Topics</h3> <ul> <li><a href="index.html">Documentation overview</a><ul> diff --git a/docs/changelog.rst b/docs/changelog.rst index c4df475..33a4f83 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,2 +1,16 @@ CHANGELOG ========= + +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 \ No newline at end of file diff --git a/docs/menugeneration.rst b/docs/menugeneration.rst index f1426f7..23e2846 100644 --- a/docs/menugeneration.rst +++ b/docs/menugeneration.rst @@ -1,2 +1,86 @@ Menu 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, + "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` + +- ``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 \ No newline at end of file diff --git a/docs/urls.rst b/docs/urls.rst index b5d18e7..c7e4e8d 100644 --- a/docs/urls.rst +++ b/docs/urls.rst @@ -1,2 +1,40 @@ URLs ==== + +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<pk>\d+)/$', SomeView.as_view(), name='update'), + +you can pass the url as follows: + + "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)`` + diff --git a/docs/validators.rst b/docs/validators.rst index 70fbe57..839126a 100644 --- a/docs/validators.rst +++ b/docs/validators.rst @@ -1,2 +1,123 @@ Validators ========== + +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 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 435aa96..3867c7a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,4 @@ description-file = README.md [flake8] max-line-length = 122 +exclude = docs/* \ No newline at end of file -- GitLab