Denis K пре 8 година
родитељ
комит
d284dff58e
85 измењених фајлова са 1031 додато и 486 уклоњено
  1. 7 0
      .travis.yml
  2. 18 0
      CHANGELOG.rst
  3. 147 0
      CONTRIBUTION.rst
  4. 21 9
      README.rst
  5. BIN
      docs/_static/change_form_sibling_links.png
  6. BIN
      docs/_static/dashboard_module_app_list.png
  7. BIN
      docs/_static/dashboard_module_feed.png
  8. BIN
      docs/_static/dashboard_module_google_analytics.png
  9. BIN
      docs/_static/dashboard_module_link_list.png
  10. BIN
      docs/_static/dashboard_module_model_list.png
  11. BIN
      docs/_static/dashboard_module_recent_actions.png
  12. BIN
      docs/_static/dashboard_module_settings.png
  13. BIN
      docs/_static/dashboard_module_yandex_metrika.png
  14. BIN
      docs/_static/side_menu_compact.png
  15. BIN
      docs/_static/themes.png
  16. 31 4
      docs/autocomplete.rst
  17. 6 2
      docs/conf.py
  18. 22 7
      docs/config_file.rst
  19. 147 0
      docs/contribution.rst
  20. 1 1
      docs/dashboard.rst
  21. 21 0
      docs/dashboard_api.rst
  22. 0 11
      docs/dashboard_base.rst
  23. 189 0
      docs/dashboard_custom_module.rst
  24. 1 0
      docs/dashboard_getting_started.rst
  25. 27 6
      docs/dashboard_modules.rst
  26. 8 19
      docs/index.rst
  27. 1 1
      docs/install_dashboard.rst
  28. 1 1
      gulpfile.js
  29. 4 3
      jet/dashboard/dashboard_modules/google_analytics.py
  30. 4 3
      jet/dashboard/dashboard_modules/yandex_metrika.py
  31. 61 80
      jet/dashboard/locale/en/LC_MESSAGES/django.po
  32. BIN
      jet/dashboard/locale/ru/LC_MESSAGES/django.mo
  33. 78 97
      jet/dashboard/locale/ru/LC_MESSAGES/django.po
  34. 25 0
      jet/dashboard/modules.py
  35. 1 1
      jet/dashboard/templates/jet.dashboard/dashboard_tools.html
  36. 3 3
      jet/dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html
  37. 3 3
      jet/dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html
  38. 1 1
      jet/dashboard/templates/jet.dashboard/update_module_fieldset.html
  39. 1 0
      jet/management/commands/jet_custom_apps_example.py
  40. 9 1
      jet/static/jet/css/_changeform.scss
  41. 16 7
      jet/static/jet/css/_changelist.scss
  42. 1 0
      jet/static/jet/css/_content.scss
  43. 7 2
      jet/static/jet/css/_dashboard.scss
  44. 1 0
      jet/static/jet/css/_forms.scss
  45. 5 4
      jet/static/jet/css/_header.scss
  46. 1 0
      jet/static/jet/css/_login.scss
  47. 1 1
      jet/static/jet/css/_modules.scss
  48. 21 1
      jet/static/jet/css/_object-tools.scss
  49. 62 33
      jet/static/jet/css/_sidebar.scss
  50. 1 5
      jet/static/jet/css/select2/_layout.scss
  51. 0 0
      jet/static/jet/css/themes/default/base.css
  52. 0 0
      jet/static/jet/css/themes/default/base.css.map
  53. 0 0
      jet/static/jet/css/themes/default/select2.theme.css
  54. 0 0
      jet/static/jet/css/themes/default/select2.theme.css.map
  55. 0 0
      jet/static/jet/css/themes/green/base.css
  56. 0 0
      jet/static/jet/css/themes/green/base.css.map
  57. 0 0
      jet/static/jet/css/themes/green/select2.theme.css
  58. 0 0
      jet/static/jet/css/themes/green/select2.theme.css.map
  59. 0 0
      jet/static/jet/css/themes/light-blue/base.css
  60. 0 0
      jet/static/jet/css/themes/light-blue/base.css.map
  61. 0 0
      jet/static/jet/css/themes/light-blue/select2.theme.css
  62. 0 0
      jet/static/jet/css/themes/light-blue/select2.theme.css.map
  63. 0 0
      jet/static/jet/css/themes/light-gray/base.css
  64. 0 0
      jet/static/jet/css/themes/light-gray/base.css.map
  65. 0 0
      jet/static/jet/css/themes/light-gray/select2.theme.css
  66. 0 0
      jet/static/jet/css/themes/light-gray/select2.theme.css.map
  67. 0 0
      jet/static/jet/css/themes/light-green/base.css
  68. 0 0
      jet/static/jet/css/themes/light-green/base.css.map
  69. 0 0
      jet/static/jet/css/themes/light-green/select2.theme.css
  70. 0 0
      jet/static/jet/css/themes/light-green/select2.theme.css.map
  71. 0 0
      jet/static/jet/css/themes/light-violet/base.css
  72. 0 0
      jet/static/jet/css/themes/light-violet/base.css.map
  73. 0 0
      jet/static/jet/css/themes/light-violet/select2.theme.css
  74. 0 0
      jet/static/jet/css/themes/light-violet/select2.theme.css.map
  75. 0 0
      jet/static/jet/js/build/bundle.min.js
  76. 2 2
      jet/static/jet/js/src/features/changelist.js
  77. 5 1
      jet/static/jet/js/src/layout-updaters/branding.js
  78. 17 0
      jet/static/jet/js/src/layout-updaters/object-tools.js
  79. 2 7
      jet/static/jet/js/src/layout-updaters/toolbar.js
  80. 1 1
      jet/static/jet/js/src/layout-updaters/user-tools.js
  81. 1 0
      jet/static/jet/js/src/main.js
  82. 23 23
      jet/templates/admin/base.html
  83. 2 2
      jet/templates/admin/includes/fieldset.html
  84. 20 140
      jet/templatetags/jet_tags.py
  85. 4 4
      jet/tests/test_tags.py

+ 7 - 0
.travis.yml

@@ -11,6 +11,7 @@ env:
   - DJANGO=1.7.7
   - DJANGO=1.8.3
   - DJANGO=1.9.8
+  - DJANGO=1.10.0
 before_install:
   - export DJANGO_SETTINGS_MODULE=jet.tests.settings
 install:
@@ -35,3 +36,9 @@ matrix:
       env: DJANGO=1.9.8
     - python: 3.3
       env: DJANGO=1.9.8
+    - python: 2.6
+      env: DJANGO=1.10.0
+    - python: 3.2
+      env: DJANGO=1.10.0
+    - python: 3.3
+      env: DJANGO=1.10.0

+ 18 - 0
CHANGELOG.rst

@@ -1,6 +1,24 @@
 Changelog
 =========
 
+0.9.1
+-----
+* Mobile UX improved
+* Refactored and optimized locale files
+* More documentation added
+* Improved object tools and toolbar arrangement
+* Fixed change list footer misplacement
+* Fix chromium sidebar scrollbar misplacement
+* Remove unused tags
+* Prefixed JET template tags
+* Fixed jet_custom_apps_example command
+* Fixed Django 1.6 user tools permission check
+* Issue-93: Fixed static urls version appending (thanks to kbruner32 for report)
+* Fixed Django 1.6 line.has_visible_field field
+* Updated default dashboard action list style
+* Added Django 1.10.0 tests
+
+
 0.9.0
 -----
 * Almost complete layout rewrite with only 3 template overrides

+ 147 - 0
CONTRIBUTION.rst

@@ -0,0 +1,147 @@
+Contributing
+============
+
+Django JET is open-source and every member of the community can contribute to it. We are happy to see patches
+and improvements with Django JET. But please keep in mind that there are some guidelines you should follow.
+
+.. _requirements:
+
+Requirements
+------------
+
+* Git master branch should always be stable
+* All pull requests are made to git dev branch
+* GPL (or similar) code is not eligible for inclusion
+
+Guidelines For Reporting An Issue/Feature
+-----------------------------------------
+
+So you've found a bug or have a great idea for a feature. Here's the steps you should take
+to help get it added/fixed in Django JET:
+
+* First check if there's an existing issue/pull request for this bug/feature. Issues can be found here
+  https://github.com/geex-arts/django-jet/issues, PRs here https://github.com/geex-arts/django-jet/pulls
+* If there isn't one there, please add an issue. The ideal report includes:
+
+  * A description of the problem/suggestion
+  * How to reproduce the bug
+  * If relevant including the versions of your:
+
+        * Python interpreter
+        * Django
+        * Django JET
+        * Optionally of the other dependencies involved
+
+  * It would be great if you also make a pull request which solves your issue
+
+Guidelines For Contributing Code
+--------------------------------
+
+If you're ready to contribute back some code/docs, the process should look like:
+
+* Fork the project on GitHub into your own account
+* Clone your copy of Django JET to a separate folder
+* Install it into your demo project using ``pip install -e PATH_TO_CLONED_JET``
+* Make a new branch in git & commit your changes there
+* Push your new branch up to GitHub
+* Again, ensure there isn't already an issue or pull request out there on it. If there is and you feel you have
+  a better fix, please take note of the issue number and mention it in your pull request
+* Create a new pull request (based on your branch), including what the problem/feature is, versions of
+  your software and referencing any related issues/pull requests
+
+In order to be merged into Django JET, contributions must have the following:
+
+* A solid patch that:
+
+  * is clear
+  * works across all supported versions of Python/Django
+  * follows the existing style of the code base (mostly PEP-8)
+
+* Desirably a test case that demonstrates the previous flaw that now passes with the included patch
+* If it adds/changes a public API, it must also include documentation for those changes
+* Must be appropriately licensed (see requirements_)
+
+If your contribution lacks any of these things, they will have to be added by a core contributor before
+being merged into Django JET proper, which may take time to get to.
+
+Contribution Styles/Javascript/Translations
+-------------------------------------------
+
+Javascript/CSS/Translations need to be built each time after updating. For this you need `Node <http://nodejs.org>`_
+and `Gulp <http://gulpjs.com>`_. It's out of the scope of this tutorial to go into details, but you should
+find lots of useful references on how to install it.
+
+Node is needed for Gulp, so install it using your system package manager:
+
+.. code-block:: bash
+
+    apt-get install -nodejs
+    # or
+    yum install nodejs
+    # or
+    brew install node
+    # ...
+
+Now you are able to install Gulp globally:
+
+.. code-block:: bash
+
+    npm install --global gulp-cli
+
+Change your working directory to Django JET where ``package.json`` and ``gulpfile.js`` are located and
+install Gulp dependencies:
+
+.. code-block:: bash
+
+    npm install
+
+Now you are ready for contribution. Run Gulp from JET's directory to build all styles/scripts/locales and
+start watching for changes (gulp will rebuild files as soon they change):
+
+.. code-block:: bash
+
+    gulp
+
+Or if you want just to perform a single build without watching for changes run:
+
+.. code-block:: bash
+
+    gulp build
+
+Building produces the following files:
+
+* CSS files for each theme:
+
+  * jet/static/jet/css/themes/THEME_NAME/base.css
+  * jet/static/jet/css/themes/THEME_NAME/jquery-ui.theme.css
+  * jet/static/jet/css/themes/THEME_NAME/select2.theme.css
+
+* CSS for other JS libraries used in JET – jet/static/jet/css/vendor.css
+* Combined JS scripts of JET – jet/static/jet/js/build/bundle.min.js
+* Localization files for JS libraries:
+
+  * jet/static/jet/js/i18n/jquery-ui/
+  * jet/static/jet/js/i18n/jquery-ui-timepicker/
+  * jet/static/jet/js/i18n/select2/
+
+* Compiled Django localizations:
+
+  * jet/locale/LOCALE/LC_MESSAGES/django.mo
+  * jet/locale/LOCALE/LC_MESSAGES/djangojs.mo
+  * jet/dashboard/locale/LOCALE/LC_MESSAGES/django.mo
+  * jet/dashboard/locale/LOCALE/LC_MESSAGES/djangojs.mo
+
+You should commit generated build files together with sources.
+
+Contribution Documentation
+--------------------------
+
+If you update documentation files, you can build the html files (this is not needed with a pull-request,
+but you might wanna check how documentation will look like locally). To do so change your working directory
+to ``docs/`` inside JET and run:
+
+.. code-block:: bash
+
+    make html
+
+``docs/_build/html/`` folder will contain all html files including starting ``index.html``.

+ 21 - 9
README.rst

@@ -16,7 +16,7 @@ and applications without the provisions of the GPLv2.
     :width: 500px
     :height: 500px
     :scale: 50%
-    :alt: Screenshot #1
+    :alt: Logo
     :align: center
     
 * Home page: http://jet.geex-arts.com/
@@ -25,23 +25,35 @@ and applications without the provisions of the GPLv2.
 * PyPI: https://pypi.python.org/pypi/django-jet
 * Support: support@jet.geex-arts.com
 
+Why Django JET?
+===============
+
+* New fresh look
+* Responsive mobile interface
+* Useful admin home page
+* Minimal template overriding
+* Easy integration
+* Themes support
+* Autocompletion
+* Handy controls
+
 Screenshots
 ===========
 
-.. image:: https://raw.githubusercontent.com/geex-arts/jet/static/screen1_720.png
+.. image:: https://raw.githubusercontent.com/geex-arts/django-jet/static/screen1_720.png
     :alt: Screenshot #1
     :align: center
-    :target: https://raw.githubusercontent.com/geex-arts/jet/static/screen1.png
+    :target: https://raw.githubusercontent.com/geex-arts/django-jet/static/screen1.png
     
-.. image:: https://raw.githubusercontent.com/geex-arts/jet/static/screen2_720.png
-    :alt: Screenshot #1
+.. image:: https://raw.githubusercontent.com/geex-arts/django-jet/static/screen2_720.png
+    :alt: Screenshot #2
     :align: center
-    :target: https://raw.githubusercontent.com/geex-arts/jet/static/screen2.png
+    :target: https://raw.githubusercontent.com/geex-arts/django-jet/static/screen2.png
     
-.. image:: https://raw.githubusercontent.com/geex-arts/jet/static/screen3_720.png
-    :alt: Screenshot #1
+.. image:: https://raw.githubusercontent.com/geex-arts/django-jet/static/screen3_720.png
+    :alt: Screenshot #3
     :align: center
-    :target: https://raw.githubusercontent.com/geex-arts/jet/static/screen3.png
+    :target: https://raw.githubusercontent.com/geex-arts/django-jet/static/screen3.png
 
 License
 =======

BIN
docs/_static/change_form_sibling_links.png


BIN
docs/_static/dashboard_module_app_list.png


BIN
docs/_static/dashboard_module_feed.png


BIN
docs/_static/dashboard_module_google_analytics.png


BIN
docs/_static/dashboard_module_link_list.png


BIN
docs/_static/dashboard_module_model_list.png


BIN
docs/_static/dashboard_module_recent_actions.png


BIN
docs/_static/dashboard_module_settings.png


BIN
docs/_static/dashboard_module_yandex_metrika.png


BIN
docs/_static/side_menu_compact.png


BIN
docs/_static/themes.png


+ 31 - 4
docs/autocomplete.rst

@@ -2,12 +2,18 @@
 Autocomplete
 ============
 
+
 By default Django JET renders all possible choices for select inputs. This behavior may be unwanted if number of
 available options is rather big. In this case Django JET allows you to load these options dynamically through AJAX.
 
+Configuration
+-------------
+
 In order to achieve this functionality all you have to do is:
 
-* Specify which model fields should be searchable by AJAX queries. Add this static method to all models which you want to be searchable with AJAX:
+-
+    Specify which model fields should be searchable by AJAX queries. Add this static method which must return
+    a ``tuple`` or ``list`` of fields you want to be searchable with AJAX:
 
 .. code:: python
 
@@ -15,6 +21,12 @@ In order to achieve this functionality all you have to do is:
     def autocomplete_search_fields():
         return 'field1', 'field2'
 
+    # for single field
+
+    @staticmethod
+    def autocomplete_search_fields():
+        return 'field1',
+
 Example from Django JET demo site:
 
 .. code:: python
@@ -36,7 +48,7 @@ Example from Django JET demo site:
         def autocomplete_search_fields():
             return 'name', 'city__name'
 
-* Use custom AJAX filter class ``jet.filters.RelatedFieldAjaxListFilter`` if you have any foreign key list filters:
+- Use custom AJAX filter class ``jet.filters.RelatedFieldAjaxListFilter`` if you have any foreign key list filters:
 
 .. code:: python
 
@@ -48,7 +60,22 @@ Example from Django JET demo site:
             ('address', RelatedFieldAjaxListFilter),
         )
 
-* Now all your admin select boxes will perform AJAX queries to load available options while you type.
+- Now all your admin select boxes will perform AJAX queries to load available options while you type.
 
 .. note::
-    This work for both ForeignKey and ManyToManyField fields.
+    This work for both ForeignKey and ManyToManyField fields.
+
+Disabling Autocomplete For Form Fields
+--------------------------------------
+
+Autocomplete is nice, but sometimes you don't want this behaviour (e.x. because you want to limit the provided
+queryset for a particular widget). In this case you can disable autocompletion this way:
+
+    .. code:: python
+
+        class YourForm(forms.ModelForm):
+            def __init__(self, *args, **kwargs):
+                super(YourForm, self).__init__(*args, **kwargs)
+                if SOME_CASE(self.instance):
+                    self.fields['FIELD_NAME'].autocomplete = False
+                    self.fields['FIELD_NAME'].queryset = Model.queryset.some_filter()

+ 6 - 2
docs/conf.py

@@ -20,8 +20,12 @@ import shlex
 # 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.
 sys.path.insert(0, os.path.abspath('..'))
-from django.conf import settings
-settings.configure()
+
+# Autodoc may need to import some models modules which require django settings
+# be configured
+os.environ['DJANGO_SETTINGS_MODULE'] = 'jet.tests.settings'
+import django
+django.setup()
 
 # -- General configuration ------------------------------------------------
 

+ 22 - 7
docs/config_file.rst

@@ -27,6 +27,9 @@ To change theme use parameter:
 JET_THEMES
 ----------
 
+.. image:: _static/themes.png
+    :width: 100%
+
 You can allow your users to change admin panel color scheme. This option will add color scheme chooser to the user dropdown menu. Make ``JET_THEMES`` an empty list to disable this feature.
 
 .. code:: python
@@ -37,11 +40,6 @@ You can allow your users to change admin panel color scheme. This option will ad
             'color': '#47bac1', # color of the theme's button in user menu
             'title': 'Default' # theme title
         },
-        {
-            'theme': 'violet',
-            'color': '#a464c4',
-            'title': 'Violet'
-        },
         {
             'theme': 'green',
             'color': '#44b78b',
@@ -80,6 +78,9 @@ to start using your own theme.
 COMPACT MENU
 ------------
 
+.. image:: _static/side_menu_compact.png
+    :width: 100%
+
 If you don't have a lot of apps and models it can be annoying to have a two-level menu.
 In this case you can use menu's compact mode, which will list applications and models in the side menu without need
 to move pointer over applications to show models.
@@ -132,6 +133,20 @@ If want to show all application's models use ``__all__`` keyword.
 
         python manage.py jet_custom_apps_example
 
+JET_CHANGE_FORM_SIBLING_LINKS
+-----------------------------
+
+.. image:: _static/change_form_sibling_links.png
+    :width: 100%
+
+Adds buttons to change forms that allows you to navigate to previous/next object without returning back to change list.
+Can be disabled if hit performance.
+
+.. code:: python
+
+    JET_CHANGE_FORM_SIBLING_LINKS = True
+
+Default is ``True``
 
 JET_INDEX_DASHBOARD
 -------------------
@@ -141,7 +156,7 @@ your own dashboard with custom modules and pre-installed layout.
 
 .. code:: python
 
-    JET_INDEX_DASHBOARD = 'jet.dashboard.DefaultIndexDashboard'
+    JET_INDEX_DASHBOARD = 'jet.dashboard.dashboard.DefaultIndexDashboard'
 
 JET_APP_INDEX_DASHBOARD
 -----------------------
@@ -150,5 +165,5 @@ Same as **JET_INDEX_DASHBOARD**, but for application pages
 
 .. code:: python
 
-    JET_APP_INDEX_DASHBOARD = 'jet.dashboard.DefaultAppIndexDashboard'
+    JET_APP_INDEX_DASHBOARD = 'jet.dashboard.dashboard.DefaultAppIndexDashboard'
 

+ 147 - 0
docs/contribution.rst

@@ -0,0 +1,147 @@
+Contributing
+============
+
+Django JET is open-source and every member of the community can contribute to it. We are happy to see patches
+and improvements with Django JET. But please keep in mind that there are some guidelines you should follow.
+
+.. _requirements:
+
+Requirements
+------------
+
+* Git master branch should always be stable
+* All pull requests are made to git dev branch
+* GPL (or similar) code is not eligible for inclusion
+
+Guidelines For Reporting An Issue/Feature
+-----------------------------------------
+
+So you've found a bug or have a great idea for a feature. Here's the steps you should take
+to help get it added/fixed in Django JET:
+
+* First check if there's an existing issue/pull request for this bug/feature. Issues can be found here
+  https://github.com/geex-arts/django-jet/issues, PRs here https://github.com/geex-arts/django-jet/pulls
+* If there isn't one there, please add an issue. The ideal report includes:
+
+  * A description of the problem/suggestion
+  * How to reproduce the bug
+  * If relevant including the versions of your:
+
+        * Python interpreter
+        * Django
+        * Django JET
+        * Optionally of the other dependencies involved
+
+  * It would be great if you also make a pull request which solves your issue
+
+Guidelines For Contributing Code
+--------------------------------
+
+If you're ready to contribute back some code/docs, the process should look like:
+
+* Fork the project on GitHub into your own account
+* Clone your copy of Django JET to a separate folder
+* Install it into your demo project using ``pip install -e PATH_TO_CLONED_JET``
+* Make a new branch in git & commit your changes there
+* Push your new branch up to GitHub
+* Again, ensure there isn't already an issue or pull request out there on it. If there is and you feel you have
+  a better fix, please take note of the issue number and mention it in your pull request
+* Create a new pull request (based on your branch), including what the problem/feature is, versions of
+  your software and referencing any related issues/pull requests
+
+In order to be merged into Django JET, contributions must have the following:
+
+* A solid patch that:
+
+  * is clear
+  * works across all supported versions of Python/Django
+  * follows the existing style of the code base (mostly PEP-8)
+
+* Desirably a test case that demonstrates the previous flaw that now passes with the included patch
+* If it adds/changes a public API, it must also include documentation for those changes
+* Must be appropriately licensed (see requirements_)
+
+If your contribution lacks any of these things, they will have to be added by a core contributor before
+being merged into Django JET proper, which may take time to get to.
+
+Contribution Styles/Javascript/Translations
+-------------------------------------------
+
+Javascript/CSS/Translations need to be built each time after updating. For this you need `Node <http://nodejs.org>`_
+and `Gulp <http://gulpjs.com>`_. It's out of the scope of this tutorial to go into details, but you should
+find lots of useful references on how to install it.
+
+Node is needed for Gulp, so install it using your system package manager:
+
+.. code-block:: bash
+
+    apt-get install -nodejs
+    # or
+    yum install nodejs
+    # or
+    brew install node
+    # ...
+
+Now you are able to install Gulp globally:
+
+.. code-block:: bash
+
+    npm install --global gulp-cli
+
+Change your working directory to Django JET where ``package.json`` and ``gulpfile.js`` are located and
+install Gulp dependencies:
+
+.. code-block:: bash
+
+    npm install
+
+Now you are ready for contribution. Run Gulp from JET's directory to build all styles/scripts/locales and
+start watching for changes (gulp will rebuild files as soon they change):
+
+.. code-block:: bash
+
+    gulp
+
+Or if you want just to perform a single build without watching for changes run:
+
+.. code-block:: bash
+
+    gulp build
+
+Building produces the following files:
+
+* CSS files for each theme:
+
+  * jet/static/jet/css/themes/THEME_NAME/base.css
+  * jet/static/jet/css/themes/THEME_NAME/jquery-ui.theme.css
+  * jet/static/jet/css/themes/THEME_NAME/select2.theme.css
+
+* CSS for other JS libraries used in JET – jet/static/jet/css/vendor.css
+* Combined JS scripts of JET – jet/static/jet/js/build/bundle.min.js
+* Localization files for JS libraries:
+
+  * jet/static/jet/js/i18n/jquery-ui/
+  * jet/static/jet/js/i18n/jquery-ui-timepicker/
+  * jet/static/jet/js/i18n/select2/
+
+* Compiled Django localizations:
+
+  * jet/locale/LOCALE/LC_MESSAGES/django.mo
+  * jet/locale/LOCALE/LC_MESSAGES/djangojs.mo
+  * jet/dashboard/locale/LOCALE/LC_MESSAGES/django.mo
+  * jet/dashboard/locale/LOCALE/LC_MESSAGES/djangojs.mo
+
+You should commit generated build files together with sources.
+
+Contribution Documentation
+--------------------------
+
+If you update documentation files, you can build the html files (this is not needed with a pull-request,
+but you might wanna check how documentation will look like locally). To do so change your working directory
+to ``docs/`` inside JET and run:
+
+.. code-block:: bash
+
+    make html
+
+``docs/_build/html/`` folder will contain all html files including starting ``index.html``.

+ 1 - 1
docs/dashboard.rst

@@ -7,4 +7,4 @@ Dashboard
 
    dashboard_getting_started
    dashboard_modules
-   dashboard_base
+   dashboard_custom_module

+ 21 - 0
docs/dashboard_api.rst

@@ -0,0 +1,21 @@
+=============
+Dashboard API
+=============
+
+This page describes the API of the dashboard and dashboard modules.
+
+.. _Dashboard:
+
+Dashboard
+---------
+
+.. autoclass:: jet.dashboard.dashboard.Dashboard
+   :members:
+
+.. _Dashboard Module:
+
+DashboardModule
+---------------
+
+.. autoclass:: jet.dashboard.modules.DashboardModule
+   :members:

+ 0 - 11
docs/dashboard_base.rst

@@ -1,11 +0,0 @@
-============
-Base Classes
-============
-
-.. _Dashboard:
-.. autoclass:: jet.dashboard.dashboard.Dashboard
-   :members:
-
-.. _Dashboard Module:
-.. autoclass:: jet.dashboard.modules.DashboardModule
-   :members:

+ 189 - 0
docs/dashboard_custom_module.rst

@@ -0,0 +1,189 @@
+=======================
+Custom Dashboard module
+=======================
+
+
+In order create your own dashboard module you need to follow these steps:
+
+* Inherit :ref:`Dashboard Module <Dashboard Module>`
+* Create module template
+* *(optional) Add module views*
+
+Also you can always see build-in modules as examples in ``jet/dashboard/modules.py`` file and
+``jet/dashboard/dashboard_modules/`` directory on the repository.
+
+Inherit Dashboard Module
+------------------------
+
+
+* Create dashboard modules file ``dashboard_modules.py`` (or any other you prefer) inside your Django application
+* Create dashboard module class inherited from base :ref:`dashboard module <Dashboard Module>` class and add it to
+  ``dashboard_modules.py`` file. You can see list of all available module attributes :ref:`here <Dashboard Module>`.
+  ``init_with_context`` method allows you to load data and initialize module's state. You can store data in
+  module's fields as this instance will be passed to template.
+
+Example of ``dashboard_modules.py``:
+
+    .. code-block:: python
+
+        from jet.dashboard.modules import DashboardModule
+        from contact.models import Ticket
+
+
+        class RecentTickets(DashboardModule):
+            title = 'Recent tickets'
+            title_url = Ticket.get_admin_changelist_url()
+            template = 'contact/dashboard_modules/recent_tickets.html'
+            limit = 10
+
+            def init_with_context(self, context):
+                self.children = Ticket.objects.order_by('-date_add')[:self.limit]
+
+* Optionally you can add customizable module settings and content which will be seen in administration interface.
+  For customizable settings ``settings_form`` should be set, also ``settings_dict`` and ``load_settings`` methods
+  should be implemented. For customizable content items ``child_form``, ``child_name`` and ``child_name_plural``
+  should be set, also ``store_children`` should return ``True``. You can validate loaded from database children
+  in ``__init__`` method.
+
+.. image:: _static/dashboard_module_settings.png
+    :width: 100%
+
+Example of ``LinkList`` dashboard module which has custom settings and editable list of links:
+
+    .. code-block:: python
+
+        class LinkList(DashboardModule):
+            title = 'Links'
+            template = 'jet.dashboard/modules/link_list.html'
+            layout = 'stacked'
+            children = []
+            settings_form = LinkListSettingsForm
+            child_form = LinkListItemForm
+            child_name = 'Link'
+            child_name_plural = 'Links'
+
+            def __init__(self, title=None, children=list(), **kwargs):
+                children = list(map(self.parse_link, children))
+                kwargs.update({'children': children})
+                super(LinkList, self).__init__(title, **kwargs)
+
+            def settings_dict(self):
+                return {
+                    'layout': self.layout
+                }
+
+            def load_settings(self, settings):
+                self.layout = settings.get('layout', self.layout)
+
+            def store_children(self):
+                return True
+
+            def parse_link(self, link):
+                if isinstance(link, (tuple, list)):
+                    link_dict = {'title': link[0], 'url': link[1]}
+                    if len(link) >= 3:
+                        link_dict['external'] = link[2]
+                    return link_dict
+                elif isinstance(link, (dict,)):
+                    return link
+
+
+        class LinkListSettingsForm(forms.Form):
+            layout = forms.ChoiceField(label='Layout', choices=(('stacked', 'Stacked'), ('inline', 'Inline')))
+
+
+        class LinkListItemForm(forms.Form):
+            url = forms.CharField(label='URL')
+            title = forms.CharField(label='Title')
+            external = forms.BooleanField(label='External link', required=False)
+
+Create Module Template
+----------------------
+
+Create template at path specified in module class. Module instance is passed to template as ``module`` variable
+so you can get data directly from it.
+
+    .. code-block:: html
+
+        {% load humanize %}
+
+        <ul>
+            {% for ticket in module.children %}
+                <li>
+                    <span class="float-right">
+                        <span class="dim">
+                            {{ ticket.date_add|naturalday }} <span class="icon-clock tooltip" title="{{ ticket.date_add }}"></span>
+                        </span>
+                    </span>
+
+                    {% if ticket.forwarded %}
+                        <span class="icon-tick" style="color: #8ecb8e;"></span>
+                    {% else %}
+                        <span class="icon-cross" style="color: #dba4a4;"></span>
+                    {% endif %}
+
+                    <a href="{{ ticket.get_admin_url }}">{{ ticket.name }}</a>
+                </li>
+            {% empty %}
+                <li>
+                    Nothing to show
+                </li>
+            {% endfor %}
+        </ul>
+
+
+Add Module Views (Optional)
+---------------------------
+
+If your dashboard module needs to have own views you can register them the following way and store for example
+in ``dashboard_modules_views.py`` file inside your application:
+
+    .. code-block:: python
+
+        from django.conf.urls import url
+        from django.contrib import messages
+        from django.shortcuts import redirect
+        from jet.dashboard import dashboard
+        from core.utils.utils import DatabaseManager
+
+
+        def update_database(request):
+            database_manager = DatabaseManager()
+            database_manager.update_database()
+
+            messages.success(request, 'Database was successfully updated')
+
+            return redirect(request.META.get('HTTP_REFERER'))
+
+        # This method registers view's url
+        dashboard.urls.register_urls([
+            url(
+                r'^update_database/',
+                update_database,
+                name='update-database'
+            ),
+        ])
+
+You should import this file before dashboard urls have been imported in you main ``urls.py`` file.
+
+    .. code-block:: python
+
+        from django.conf import settings
+        from django.conf.urls import include, url
+        from django.contrib import admin
+
+        # Import dashboard module views
+        from core import dashboard_modules_views
+
+        urlpatterns = [
+            url(r'^admin/', include(admin.site.urls)),
+            url(r'^jet/', include('jet.urls', 'jet')),
+            url(r'^jet/dashboard/', include('jet.dashboard.urls', 'jet-dashboard')),
+            ...
+        ]
+
+After that you can reverse url to module's view this way:
+
+    .. code-block:: html
+
+        {% url "jet-dashboard:update-database" %}

+ 1 - 0
docs/dashboard_getting_started.rst

@@ -12,6 +12,7 @@ Any custom **Dashboard** class should inherit ``jet.dashboard.dashboard.Dashboar
 and use ``init_with_context`` to fill it with widgets. You should add your widgets
 to ``children`` and ``available_children`` attributes.
 
+Before continue make sure you have completed :doc:`install_dashboard`.
 
 Set Up Custom Dashboard
 -----------------------

+ 27 - 6
docs/dashboard_modules.rst

@@ -1,46 +1,64 @@
 =================
-Dashboard modules
+Dashboard Modules
 =================
 
-Build-in dashboard modules
+Build-In Dashboard Modules
 ==========================
 
 LinkList
 --------
 
+.. image:: _static/dashboard_module_link_list.png
+    :width: 100%
+
 .. autoclass:: jet.dashboard.modules.LinkList
    :members:
 
 AppList
 -------
 
+.. image:: _static/dashboard_module_app_list.png
+    :width: 100%
+
 .. autoclass:: jet.dashboard.modules.AppList
    :members:
 
 ModelList
 ---------
 
+.. image:: _static/dashboard_module_model_list.png
+    :width: 100%
+
 .. autoclass:: jet.dashboard.modules.ModelList
    :members:
 
 RecentActions
 -------------
 
+.. image:: _static/dashboard_module_recent_actions.png
+    :width: 100%
+
 .. autoclass:: jet.dashboard.modules.RecentActions
    :members:
 
 Feed
 ----
 
+.. image:: _static/dashboard_module_feed.png
+    :width: 100%
+
 .. autoclass:: jet.dashboard.modules.Feed
    :members:
 
-Google Analytics widgets
+Google Analytics Widgets
 ========================
 
 .. attention::
    Google Analytics widgets required extra setup
 
+.. image:: _static/dashboard_module_google_analytics.png
+    :width: 100%
+
 Extra Installation
 ------------------
 
@@ -63,7 +81,7 @@ Extra Installation
    from jet.dashboard.dashboard_modules import google_analytics_views
 
 
-Usage example
+Usage Example
 -------------
    .. code-block:: python
 
@@ -89,12 +107,15 @@ Usage example
 .. autoclass:: jet.dashboard.dashboard_modules.google_analytics.GoogleAnalyticsPeriodVisitors
    :members:
 
-Yandex Metrika widgets
+Yandex Metrika Widgets
 ======================
 
 .. attention::
    Yandex Metrika widgets required extra setup
 
+.. image:: _static/dashboard_module_yandex_metrika.png
+    :width: 100%
+
 Extra Installation
 ------------------
 
@@ -112,7 +133,7 @@ Extra Installation
    from jet.dashboard.dashboard_modules import yandex_metrika_views
 
 
-Usage example
+Usage Example
 -------------
    .. code-block:: python
 

+ 8 - 19
docs/index.rst

@@ -12,37 +12,26 @@ About
     :height: 500px
     :scale: 50%
 
-Getting started
-===============
+Contents
+========
 
 .. toctree::
    :maxdepth: 2
+   :caption: Documentation
 
    getting_started
-
-Configuration
-=============
-
-.. toctree::
-   :maxdepth: 2
-
    configuration
-
-Dashboard
-=========
-
-.. toctree::
-   :maxdepth: 2
-
    dashboard
+   dashboard_api
+   contribution
 
 License
 =======
 
 Django JET has two kinds of licenses: open-source (GPLv2) and commercial. Please note that using GPLv2
 code in your programs make them GPL too. So if you don't want to comply with that we can provide you a commercial
-license (in this case please email at support@jet.geex-arts.com). The commercial license
-is designed for using Django JET in commercial products and applications without the provisions of the GPLv2.
+license (visit Home page). The commercial license is designed for using Django JET in commercial products and
+applications without the provisions of the GPLv2.
 
 Resources
 =========
@@ -72,7 +61,7 @@ Changelist
     :align: center
     :target: https://raw.githubusercontent.com/geex-arts/jet/static/screen2.png
 
-Changeform + sidemenu
+Sidemenu
 
 .. image:: https://raw.githubusercontent.com/geex-arts/jet/static/screen3_720.png
     :alt: Screenshot #3

+ 1 - 1
docs/install_dashboard.rst

@@ -1,5 +1,5 @@
 ======================
-Dashboard installation
+Dashboard Installation
 ======================
 
 .. note::

+ 1 - 1
gulpfile.js

@@ -103,7 +103,7 @@ gulp.task('build', ['scripts', 'styles', 'vendor-styles', 'vendor-translations',
 gulp.task('watch', function() {
     gulp.watch('./jet/static/jet/js/src/**/*.js', ['scripts']);
     gulp.watch('./jet/static/jet/css/**/*.scss', ['styles']);
-    gulp.watch('./jet/locale/**/*.po', ['locales']);
+    gulp.watch(['./jet/locale/**/*.po', './jet/dashboard/locale/**/*.po'], ['locales']);
 });
 
 gulp.task('default', ['build', 'watch']);

+ 4 - 3
jet/dashboard/dashboard_modules/google_analytics.py

@@ -7,6 +7,7 @@ from django.forms import Widget
 from django.utils import formats
 from django.utils.html import format_html
 from django.utils.safestring import mark_safe
+from django.utils.text import capfirst
 from googleapiclient.discovery import build
 import httplib2
 from jet.dashboard.modules import DashboardModule
@@ -185,9 +186,9 @@ class GoogleAnalyticsSettingsForm(forms.Form):
 
 class GoogleAnalyticsChartSettingsForm(GoogleAnalyticsSettingsForm):
     show = forms.ChoiceField(label=_('Show'), choices=(
-        ('ga:users', _('Users')),
-        ('ga:sessions', _('Sessions')),
-        ('ga:pageviews', _('Views')),
+        ('ga:users', capfirst(_('users'))),
+        ('ga:sessions', capfirst(_('sessions'))),
+        ('ga:pageviews', capfirst(_('views'))),
     ))
     group = forms.ChoiceField(label=_('Group'), choices=(
         ('day', _('By day')),

+ 4 - 3
jet/dashboard/dashboard_modules/yandex_metrika.py

@@ -7,6 +7,7 @@ from django.forms import Widget
 from django.utils import formats
 from django.utils.html import format_html
 from django.utils.safestring import mark_safe
+from django.utils.text import capfirst
 from jet.dashboard.modules import DashboardModule
 from django.utils.translation import ugettext_lazy as _
 from django.conf import settings
@@ -140,9 +141,9 @@ class YandexMetrikaSettingsForm(forms.Form):
 
 class YandexMetrikaChartSettingsForm(YandexMetrikaSettingsForm):
     show = forms.ChoiceField(label=_('Show'), choices=(
-        ('visitors', _('Visitors')),
-        ('visits', _('Visits')),
-        ('page_views', _('Views')),
+        ('visitors', capfirst(_('visitors'))),
+        ('visits', capfirst(_('visits'))),
+        ('page_views', capfirst(_('views'))),
     ))
     group = forms.ChoiceField(label=_('Group'), choices=(
         ('day', _('By day')),

+ 61 - 80
jet/dashboard/locale/en/LC_MESSAGES/django.po

@@ -129,179 +129,170 @@ msgstr ""
 msgid "Widget has been successfully added"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:144
+#: dashboard/dashboard_modules/google_analytics.py:145
 #: dashboard/dashboard_modules/yandex_metrika.py:102
 msgid "Revoke access"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:149
+#: dashboard/dashboard_modules/google_analytics.py:150
 #: dashboard/dashboard_modules/yandex_metrika.py:107
 msgid "Grant access"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:162
+#: dashboard/dashboard_modules/google_analytics.py:163
 #: dashboard/dashboard_modules/yandex_metrika.py:117
 msgid "Access"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:163
+#: dashboard/dashboard_modules/google_analytics.py:164
 #: dashboard/dashboard_modules/yandex_metrika.py:118
 msgid "Counter"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:164
+#: dashboard/dashboard_modules/google_analytics.py:165
 #: dashboard/dashboard_modules/yandex_metrika.py:119
 msgid "Statistics period"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:165
+#: dashboard/dashboard_modules/google_analytics.py:166
 #: dashboard/dashboard_modules/yandex_metrika.py:120
 msgid "Today"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:166
+#: dashboard/dashboard_modules/google_analytics.py:167
 #: dashboard/dashboard_modules/yandex_metrika.py:121
 msgid "Last week"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:167
+#: dashboard/dashboard_modules/google_analytics.py:168
 #: dashboard/dashboard_modules/yandex_metrika.py:122
 msgid "Last month"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:168
+#: dashboard/dashboard_modules/google_analytics.py:169
 #: dashboard/dashboard_modules/yandex_metrika.py:123
 msgid "Last quarter"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:169
+#: dashboard/dashboard_modules/google_analytics.py:170
 #: dashboard/dashboard_modules/yandex_metrika.py:124
 msgid "Last year"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:179
+#: dashboard/dashboard_modules/google_analytics.py:180
 #: dashboard/dashboard_modules/yandex_metrika.py:134
 msgid "none"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:182
+#: dashboard/dashboard_modules/google_analytics.py:183
 #: dashboard/dashboard_modules/yandex_metrika.py:137
 msgid "grant access first"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:182
+#: dashboard/dashboard_modules/google_analytics.py:183
 #: dashboard/dashboard_modules/yandex_metrika.py:137
 msgid "counters loading failed"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:187
+#: dashboard/dashboard_modules/google_analytics.py:188
 #: dashboard/dashboard_modules/yandex_metrika.py:142
 msgid "Show"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:188
-#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:15
-msgid "Users"
-msgstr ""
-
-#: dashboard/dashboard_modules/google_analytics.py:189
-#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:16
-msgid "Sessions"
-msgstr ""
-
-#: dashboard/dashboard_modules/google_analytics.py:190
-#: dashboard/dashboard_modules/yandex_metrika.py:145
-#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:17
-#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:17
-msgid "Views"
-msgstr ""
-
-#: dashboard/dashboard_modules/google_analytics.py:192
-#: dashboard/dashboard_modules/google_analytics.py:200
+#: dashboard/dashboard_modules/google_analytics.py:193
+#: dashboard/dashboard_modules/google_analytics.py:201
 #: dashboard/dashboard_modules/yandex_metrika.py:147
 #: dashboard/dashboard_modules/yandex_metrika.py:155
 msgid "Group"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:193
-#: dashboard/dashboard_modules/google_analytics.py:201
+#: dashboard/dashboard_modules/google_analytics.py:194
+#: dashboard/dashboard_modules/google_analytics.py:202
 #: dashboard/dashboard_modules/yandex_metrika.py:148
 #: dashboard/dashboard_modules/yandex_metrika.py:156
 msgid "By day"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:194
-#: dashboard/dashboard_modules/google_analytics.py:202
+#: dashboard/dashboard_modules/google_analytics.py:195
+#: dashboard/dashboard_modules/google_analytics.py:203
 #: dashboard/dashboard_modules/yandex_metrika.py:149
 #: dashboard/dashboard_modules/yandex_metrika.py:157
 msgid "By week"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:195
-#: dashboard/dashboard_modules/google_analytics.py:203
+#: dashboard/dashboard_modules/google_analytics.py:196
+#: dashboard/dashboard_modules/google_analytics.py:204
 #: dashboard/dashboard_modules/yandex_metrika.py:150
 #: dashboard/dashboard_modules/yandex_metrika.py:158
 msgid "By month"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:276
+#: dashboard/dashboard_modules/google_analytics.py:277
 #, python-format
 msgid ""
 "Please <a href=\"%s\">attach Google account and choose Google Analytics "
 "counter</a> to start using widget"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:279
+#: dashboard/dashboard_modules/google_analytics.py:280
 #, python-format
 msgid ""
 "Please <a href=\"%s\">select Google Analytics counter</a> to start using "
 "widget"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:298
+#: dashboard/dashboard_modules/google_analytics.py:299
 #: dashboard/dashboard_modules/google_analytics_views.py:42
-#: dashboard/dashboard_modules/yandex_metrika.py:235
+#: dashboard/dashboard_modules/yandex_metrika.py:236
 #: dashboard/dashboard_modules/yandex_metrika_views.py:37
 msgid "API request failed."
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:300
-#: dashboard/dashboard_modules/yandex_metrika.py:237
+#: dashboard/dashboard_modules/google_analytics.py:301
+#: dashboard/dashboard_modules/yandex_metrika.py:238
 #, python-format
 msgid " Try to <a href=\"%s\">revoke and grant access</a> again"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:310
+#: dashboard/dashboard_modules/google_analytics.py:311
 msgid "Google Analytics visitors totals"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:325
+#: dashboard/dashboard_modules/google_analytics.py:189
+#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:15
+#: dashboard/dashboard_modules/google_analytics.py:326
 msgid "users"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:326
+#: dashboard/dashboard_modules/google_analytics.py:190
+#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:16
+#: dashboard/dashboard_modules/google_analytics.py:327
 msgid "sessions"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:327
-#: dashboard/dashboard_modules/yandex_metrika.py:266
+#: dashboard/dashboard_modules/google_analytics.py:191
+#: dashboard/dashboard_modules/yandex_metrika.py:146
+#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:17
+#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:17
+#: dashboard/dashboard_modules/google_analytics.py:328
+#: dashboard/dashboard_modules/yandex_metrika.py:267
 msgid "views"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:329
-#: dashboard/dashboard_modules/google_analytics.py:387
-#: dashboard/dashboard_modules/google_analytics.py:437
-#: dashboard/dashboard_modules/yandex_metrika.py:268
-#: dashboard/dashboard_modules/yandex_metrika.py:320
-#: dashboard/dashboard_modules/yandex_metrika.py:364
+#: dashboard/dashboard_modules/google_analytics.py:330
+#: dashboard/dashboard_modules/google_analytics.py:388
+#: dashboard/dashboard_modules/google_analytics.py:438
+#: dashboard/dashboard_modules/yandex_metrika.py:269
+#: dashboard/dashboard_modules/yandex_metrika.py:321
+#: dashboard/dashboard_modules/yandex_metrika.py:365
 msgid "Bad server response"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:339
+#: dashboard/dashboard_modules/google_analytics.py:340
 msgid "Google Analytics visitors chart"
 msgstr ""
 
-#: dashboard/dashboard_modules/google_analytics.py:397
+#: dashboard/dashboard_modules/google_analytics.py:398
 msgid "Google Analytics period visitors"
 msgstr ""
 
@@ -317,46 +308,40 @@ msgstr ""
 msgid "Bad arguments"
 msgstr ""
 
-#: dashboard/dashboard_modules/yandex_metrika.py:143
-#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:15
-msgid "Visitors"
-msgstr ""
-
-#: dashboard/dashboard_modules/yandex_metrika.py:144
-#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:16
-msgid "Visits"
-msgstr ""
-
-#: dashboard/dashboard_modules/yandex_metrika.py:218
+#: dashboard/dashboard_modules/yandex_metrika.py:219
 #, python-format
 msgid ""
 "Please <a href=\"%s\">attach Yandex account and choose Yandex Metrika "
 "counter</a> to start using widget"
 msgstr ""
 
-#: dashboard/dashboard_modules/yandex_metrika.py:221
+#: dashboard/dashboard_modules/yandex_metrika.py:222
 #, python-format
 msgid ""
 "Please <a href=\"%s\">select Yandex Metrika counter</a> to start using widget"
 msgstr ""
 
-#: dashboard/dashboard_modules/yandex_metrika.py:249
+#: dashboard/dashboard_modules/yandex_metrika.py:250
 msgid "Yandex Metrika visitors totals"
 msgstr ""
 
-#: dashboard/dashboard_modules/yandex_metrika.py:264
+#: dashboard/dashboard_modules/yandex_metrika.py:144
+#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:15
+#: dashboard/dashboard_modules/yandex_metrika.py:265
 msgid "visitors"
 msgstr ""
 
-#: dashboard/dashboard_modules/yandex_metrika.py:265
+#: dashboard/dashboard_modules/yandex_metrika.py:145
+#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:16
+#: dashboard/dashboard_modules/yandex_metrika.py:266
 msgid "visits"
 msgstr ""
 
-#: dashboard/dashboard_modules/yandex_metrika.py:278
+#: dashboard/dashboard_modules/yandex_metrika.py:279
 msgid "Yandex Metrika visitors chart"
 msgstr ""
 
-#: dashboard/dashboard_modules/yandex_metrika.py:330
+#: dashboard/dashboard_modules/yandex_metrika.py:331
 msgid "Yandex Metrika period visitors"
 msgstr ""
 
@@ -377,7 +362,7 @@ msgid "available"
 msgstr ""
 
 #: dashboard/templates/jet.dashboard/dashboard_tools.html:13
-msgid "inititals"
+msgid "initials"
 msgstr ""
 
 #: dashboard/templates/jet.dashboard/dashboard_tools.html:21
@@ -389,10 +374,6 @@ msgstr ""
 msgid "Are you sure want to reset widgets?"
 msgstr ""
 
-#: dashboard/templates/jet.dashboard/update_module.html:35
-msgid "General"
-msgstr ""
-
 #: dashboard/templates/jet.dashboard/modules/feed.html:13
 #: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:34
 #: dashboard/templates/jet.dashboard/modules/google_analytics_visitors_chart.html:30

BIN
jet/dashboard/locale/ru/LC_MESSAGES/django.mo


+ 78 - 97
jet/dashboard/locale/ru/LC_MESSAGES/django.po

@@ -129,122 +129,105 @@ msgstr "Элементы"
 msgid "Widget has been successfully added"
 msgstr "Виджет успешно добавлен"
 
-#: dashboard/dashboard_modules/google_analytics.py:144
-#: dashboard/dashboard_modules/yandex_metrika.py:102
+#: dashboard/dashboard_modules/google_analytics.py:145
+#: dashboard/dashboard_modules/yandex_metrika.py:103
 msgid "Revoke access"
 msgstr "Убрать доступ"
 
-#: dashboard/dashboard_modules/google_analytics.py:149
-#: dashboard/dashboard_modules/yandex_metrika.py:107
+#: dashboard/dashboard_modules/google_analytics.py:150
+#: dashboard/dashboard_modules/yandex_metrika.py:108
 msgid "Grant access"
 msgstr "Предоставить доступ"
 
-#: dashboard/dashboard_modules/google_analytics.py:162
-#: dashboard/dashboard_modules/yandex_metrika.py:117
+#: dashboard/dashboard_modules/google_analytics.py:163
+#: dashboard/dashboard_modules/yandex_metrika.py:118
 msgid "Access"
 msgstr "Доступ"
 
-#: dashboard/dashboard_modules/google_analytics.py:163
-#: dashboard/dashboard_modules/yandex_metrika.py:118
+#: dashboard/dashboard_modules/google_analytics.py:164
+#: dashboard/dashboard_modules/yandex_metrika.py:119
 msgid "Counter"
 msgstr "Счетчик"
 
-#: dashboard/dashboard_modules/google_analytics.py:164
-#: dashboard/dashboard_modules/yandex_metrika.py:119
+#: dashboard/dashboard_modules/google_analytics.py:165
+#: dashboard/dashboard_modules/yandex_metrika.py:120
 msgid "Statistics period"
 msgstr "Статистика за период"
 
-#: dashboard/dashboard_modules/google_analytics.py:165
-#: dashboard/dashboard_modules/yandex_metrika.py:120
+#: dashboard/dashboard_modules/google_analytics.py:166
+#: dashboard/dashboard_modules/yandex_metrika.py:121
 msgid "Today"
 msgstr "Сегодня"
 
-#: dashboard/dashboard_modules/google_analytics.py:166
-#: dashboard/dashboard_modules/yandex_metrika.py:121
+#: dashboard/dashboard_modules/google_analytics.py:167
+#: dashboard/dashboard_modules/yandex_metrika.py:122
 msgid "Last week"
 msgstr "Последняя неделя"
 
-#: dashboard/dashboard_modules/google_analytics.py:167
-#: dashboard/dashboard_modules/yandex_metrika.py:122
+#: dashboard/dashboard_modules/google_analytics.py:168
+#: dashboard/dashboard_modules/yandex_metrika.py:123
 msgid "Last month"
 msgstr "Последний месяц"
 
-#: dashboard/dashboard_modules/google_analytics.py:168
-#: dashboard/dashboard_modules/yandex_metrika.py:123
+#: dashboard/dashboard_modules/google_analytics.py:169
+#: dashboard/dashboard_modules/yandex_metrika.py:124
 msgid "Last quarter"
 msgstr "Последний квартал"
 
-#: dashboard/dashboard_modules/google_analytics.py:169
-#: dashboard/dashboard_modules/yandex_metrika.py:124
+#: dashboard/dashboard_modules/google_analytics.py:170
+#: dashboard/dashboard_modules/yandex_metrika.py:125
 msgid "Last year"
 msgstr "Последний год"
 
-#: dashboard/dashboard_modules/google_analytics.py:179
-#: dashboard/dashboard_modules/yandex_metrika.py:134
+#: dashboard/dashboard_modules/google_analytics.py:180
+#: dashboard/dashboard_modules/yandex_metrika.py:135
 msgid "none"
 msgstr "не указано"
 
-#: dashboard/dashboard_modules/google_analytics.py:182
-#: dashboard/dashboard_modules/yandex_metrika.py:137
+#: dashboard/dashboard_modules/google_analytics.py:183
+#: dashboard/dashboard_modules/yandex_metrika.py:138
 msgid "grant access first"
 msgstr "сначала предоставьте доступ"
 
-#: dashboard/dashboard_modules/google_analytics.py:182
-#: dashboard/dashboard_modules/yandex_metrika.py:137
+#: dashboard/dashboard_modules/google_analytics.py:183
+#: dashboard/dashboard_modules/yandex_metrika.py:138
 msgid "counters loading failed"
 msgstr "не удалось загрузить счетчики"
 
-#: dashboard/dashboard_modules/google_analytics.py:187
-#: dashboard/dashboard_modules/yandex_metrika.py:142
+#: dashboard/dashboard_modules/google_analytics.py:188
+#: dashboard/dashboard_modules/yandex_metrika.py:143
 msgid "Show"
 msgstr "Показывать"
 
-#: dashboard/dashboard_modules/google_analytics.py:188
-#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:15
-msgid "Users"
-msgstr "Пользователи"
-
-#: dashboard/dashboard_modules/google_analytics.py:189
-#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:16
-msgid "Sessions"
-msgstr "Сессии"
-
-#: dashboard/dashboard_modules/google_analytics.py:190
-#: dashboard/dashboard_modules/yandex_metrika.py:145
-#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:17
-#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:17
-msgid "Views"
-msgstr "Просмотры"
-
-#: dashboard/dashboard_modules/google_analytics.py:192
-#: dashboard/dashboard_modules/google_analytics.py:200
-#: dashboard/dashboard_modules/yandex_metrika.py:147
-#: dashboard/dashboard_modules/yandex_metrika.py:155
-msgid "Group"
-msgstr "Группировать"
-
 #: dashboard/dashboard_modules/google_analytics.py:193
 #: dashboard/dashboard_modules/google_analytics.py:201
 #: dashboard/dashboard_modules/yandex_metrika.py:148
 #: dashboard/dashboard_modules/yandex_metrika.py:156
-msgid "By day"
-msgstr "По дням"
+msgid "Group"
+msgstr "Группировать"
 
 #: dashboard/dashboard_modules/google_analytics.py:194
 #: dashboard/dashboard_modules/google_analytics.py:202
 #: dashboard/dashboard_modules/yandex_metrika.py:149
 #: dashboard/dashboard_modules/yandex_metrika.py:157
-msgid "By week"
-msgstr "По неделям"
+msgid "By day"
+msgstr "По дням"
 
 #: dashboard/dashboard_modules/google_analytics.py:195
 #: dashboard/dashboard_modules/google_analytics.py:203
 #: dashboard/dashboard_modules/yandex_metrika.py:150
 #: dashboard/dashboard_modules/yandex_metrika.py:158
+msgid "By week"
+msgstr "По неделям"
+
+#: dashboard/dashboard_modules/google_analytics.py:196
+#: dashboard/dashboard_modules/google_analytics.py:204
+#: dashboard/dashboard_modules/yandex_metrika.py:151
+#: dashboard/dashboard_modules/yandex_metrika.py:159
 msgid "By month"
 msgstr "По месяцам"
 
-#: dashboard/dashboard_modules/google_analytics.py:276
+#: dashboard/dashboard_modules/google_analytics.py:277
 #, python-format
 msgid ""
 "Please <a href=\"%s\">attach Google account and choose Google Analytics "
@@ -253,7 +236,7 @@ msgstr ""
 "Пожалуйста <a href=\"%s\">прикрепите аккаунт Google и выберите счетчик "
 "Google Analytics</a> для виджета"
 
-#: dashboard/dashboard_modules/google_analytics.py:279
+#: dashboard/dashboard_modules/google_analytics.py:280
 #, python-format
 msgid ""
 "Please <a href=\"%s\">select Google Analytics counter</a> to start using "
@@ -261,50 +244,58 @@ msgid ""
 msgstr ""
 "Пожалуйста <a href=\"%s\">выберите счетчик Google Analytics</a> для виджета"
 
-#: dashboard/dashboard_modules/google_analytics.py:298
+#: dashboard/dashboard_modules/google_analytics.py:299
 #: dashboard/dashboard_modules/google_analytics_views.py:42
-#: dashboard/dashboard_modules/yandex_metrika.py:235
+#: dashboard/dashboard_modules/yandex_metrika.py:236
 #: dashboard/dashboard_modules/yandex_metrika_views.py:37
 msgid "API request failed."
 msgstr "Ошибка запроса к API."
 
-#: dashboard/dashboard_modules/google_analytics.py:300
-#: dashboard/dashboard_modules/yandex_metrika.py:237
+#: dashboard/dashboard_modules/google_analytics.py:301
+#: dashboard/dashboard_modules/yandex_metrika.py:238
 #, python-format
 msgid " Try to <a href=\"%s\">revoke and grant access</a> again"
 msgstr " Попробуйте <a href=\"%s\">убрать и предоставить доступ</a> заново"
 
-#: dashboard/dashboard_modules/google_analytics.py:310
+#: dashboard/dashboard_modules/google_analytics.py:311
 msgid "Google Analytics visitors totals"
 msgstr "Данные о посещениях Google Analytics"
 
-#: dashboard/dashboard_modules/google_analytics.py:325
+#: dashboard/dashboard_modules/google_analytics.py:189
+#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:15
+#: dashboard/dashboard_modules/google_analytics.py:326
 msgid "users"
 msgstr "пользователи"
 
-#: dashboard/dashboard_modules/google_analytics.py:326
+#: dashboard/dashboard_modules/google_analytics.py:190
+#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:16
+#: dashboard/dashboard_modules/google_analytics.py:327
 msgid "sessions"
 msgstr "сессии"
 
-#: dashboard/dashboard_modules/google_analytics.py:327
-#: dashboard/dashboard_modules/yandex_metrika.py:266
+#: dashboard/dashboard_modules/google_analytics.py:191
+#: dashboard/dashboard_modules/yandex_metrika.py:146
+#: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:17
+#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:17
+#: dashboard/dashboard_modules/google_analytics.py:328
+#: dashboard/dashboard_modules/yandex_metrika.py:267
 msgid "views"
 msgstr "просмотры"
 
-#: dashboard/dashboard_modules/google_analytics.py:329
-#: dashboard/dashboard_modules/google_analytics.py:387
-#: dashboard/dashboard_modules/google_analytics.py:437
-#: dashboard/dashboard_modules/yandex_metrika.py:268
-#: dashboard/dashboard_modules/yandex_metrika.py:320
-#: dashboard/dashboard_modules/yandex_metrika.py:364
+#: dashboard/dashboard_modules/google_analytics.py:330
+#: dashboard/dashboard_modules/google_analytics.py:388
+#: dashboard/dashboard_modules/google_analytics.py:438
+#: dashboard/dashboard_modules/yandex_metrika.py:269
+#: dashboard/dashboard_modules/yandex_metrika.py:321
+#: dashboard/dashboard_modules/yandex_metrika.py:365
 msgid "Bad server response"
 msgstr "Некорректный ответ сервера"
 
-#: dashboard/dashboard_modules/google_analytics.py:339
+#: dashboard/dashboard_modules/google_analytics.py:340
 msgid "Google Analytics visitors chart"
 msgstr "График посещений Google Analytics"
 
-#: dashboard/dashboard_modules/google_analytics.py:397
+#: dashboard/dashboard_modules/google_analytics.py:398
 msgid "Google Analytics period visitors"
 msgstr "Посещения Google Analytics за период"
 
@@ -320,17 +311,7 @@ msgstr "Модуль не найден"
 msgid "Bad arguments"
 msgstr "Некорректные аргументы"
 
-#: dashboard/dashboard_modules/yandex_metrika.py:143
-#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:15
-msgid "Visitors"
-msgstr "Посетители"
-
-#: dashboard/dashboard_modules/yandex_metrika.py:144
-#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:16
-msgid "Visits"
-msgstr "Визиты"
-
-#: dashboard/dashboard_modules/yandex_metrika.py:218
+#: dashboard/dashboard_modules/yandex_metrika.py:219
 #, python-format
 msgid ""
 "Please <a href=\"%s\">attach Yandex account and choose Yandex Metrika "
@@ -339,30 +320,34 @@ msgstr ""
 "Пожалуйста <a href=\"%s\">прикрепите аккаунт Яндекс и выберите счетчик "
 "Яндекс Метрики</a> для виджета"
 
-#: dashboard/dashboard_modules/yandex_metrika.py:221
+#: dashboard/dashboard_modules/yandex_metrika.py:222
 #, python-format
 msgid ""
 "Please <a href=\"%s\">select Yandex Metrika counter</a> to start using widget"
 msgstr ""
 "Пожалуйста <a href=\"%s\">выберите счетчик Яндекс Метрики</a> для виджета"
 
-#: dashboard/dashboard_modules/yandex_metrika.py:249
+#: dashboard/dashboard_modules/yandex_metrika.py:250
 msgid "Yandex Metrika visitors totals"
 msgstr "Данные о посещениях Яндекс Метрики"
 
-#: dashboard/dashboard_modules/yandex_metrika.py:264
+#: dashboard/dashboard_modules/yandex_metrika.py:144
+#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:15
+#: dashboard/dashboard_modules/yandex_metrika.py:265
 msgid "visitors"
 msgstr "посетители"
 
-#: dashboard/dashboard_modules/yandex_metrika.py:265
+#: dashboard/dashboard_modules/yandex_metrika.py:145
+#: dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html:16
+#: dashboard/dashboard_modules/yandex_metrika.py:266
 msgid "visits"
 msgstr "визиты"
 
-#: dashboard/dashboard_modules/yandex_metrika.py:278
+#: dashboard/dashboard_modules/yandex_metrika.py:279
 msgid "Yandex Metrika visitors chart"
 msgstr "График посещений Яндекс Метрики"
 
-#: dashboard/dashboard_modules/yandex_metrika.py:330
+#: dashboard/dashboard_modules/yandex_metrika.py:331
 msgid "Yandex Metrika period visitors"
 msgstr "Посещения Яндекс Метрики за период"
 
@@ -383,7 +368,7 @@ msgid "available"
 msgstr "доступные"
 
 #: dashboard/templates/jet.dashboard/dashboard_tools.html:13
-msgid "inititals"
+msgid "initials"
 msgstr "изначальные"
 
 #: dashboard/templates/jet.dashboard/dashboard_tools.html:21
@@ -395,10 +380,6 @@ msgstr "Сбросить виджеты"
 msgid "Are you sure want to reset widgets?"
 msgstr "Вы точно хотите сбросить виджеты?"
 
-#: dashboard/templates/jet.dashboard/update_module.html:35
-msgid "General"
-msgstr "Общее"
-
 #: dashboard/templates/jet.dashboard/modules/feed.html:13
 #: dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html:34
 #: dashboard/templates/jet.dashboard/modules/google_analytics_visitors_chart.html:30

+ 25 - 0
jet/dashboard/modules.py

@@ -16,14 +16,22 @@ class DashboardModule(object):
     #: Path to widget's template. There is no need to extend such templates from any base templates.
     template = 'jet.dashboard/module.html'
     enabled = True
+
+    #: Specify if module can be draggable or has static position.
     draggable = True
+
+    #: Specify if module can be collapsed.
     collapsible = True
+
+    #: Specify if module can be deleted.
     deletable = True
     show_title = True
 
     #: Default widget title that will be displayed for widget in the dashboard. User can change it later
     #: for every widget.
     title = ''
+
+    #: Specify title url. ``None`` if title shouldn't be clickable.
     title_url = None
     css_classes = None
 
@@ -39,7 +47,11 @@ class DashboardModule(object):
 
     #: A ``django.forms.Form`` class which may contain custom widget child settings, if it has any. Not required.
     child_form = None
+
+    #: Child name that will be displayed when editing module contents. Required if ``child_form`` set.
     child_name = None
+
+    #: Same as child name, but plural.
     child_name_plural = None
     settings = None
     column = None
@@ -78,15 +90,25 @@ class DashboardModule(object):
         return self.__module__ + "." + self.__class__.__name__
 
     def load_settings(self, settings):
+        """
+        Should be implemented to restore saved in database settings. Required if you have custom settings.
+        """
         pass
 
     def load_children(self, children):
         self.children = children
 
     def store_children(self):
+        """
+        Specify if children field should be saved to database.
+        """
         return False
 
     def settings_dict(self):
+        """
+        Should be implemented to save settings to database. This method should return ``dict`` which will be serialized
+        using ``json``. Required if you have custom settings.
+        """
         pass
 
     def dump_settings(self, settings=None):
@@ -120,6 +142,9 @@ class DashboardModule(object):
                 pass
 
     def init_with_context(self, context):
+        """
+        Allows you to load data and initialize module's state.
+        """
         pass
 
     def get_context_data(self):

+ 1 - 1
jet/dashboard/templates/jet.dashboard/dashboard_tools.html

@@ -15,7 +15,7 @@
                     <option value="{{ forloop.counter0 }}" data-type="available_children">{{ module.title }}</option>
                 {% endfor %}
             </optgroup>
-            <optgroup label="{% trans "inititals" %}">
+            <optgroup label="{% trans "initials" %}">
                 {% for module in children %}
                     <option value="{{ forloop.counter0 }}" data-type="children">{{ module.title }}</option>
                 {% endfor %}

+ 3 - 3
jet/dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html

@@ -12,9 +12,9 @@
         <thead>
             <tr>
                 <th>{% trans "Date" %}</th>
-                <th>{% trans "Users" %}</th>
-                <th>{% trans "Sessions" %}</th>
-                <th>{% trans "Views" %}</th>
+                <th>{% trans "users" as label %}{{ label|capfirst }}</th>
+                <th>{% trans "sessions" as label %}{{ label|capfirst }}</th>
+                <th>{% trans "views" as label %}{{ label|capfirst }}</th>
             </tr>
         </thead>
         <tbody>

+ 3 - 3
jet/dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html

@@ -12,9 +12,9 @@
         <thead>
             <tr>
                 <th>{% trans "Date" %}</th>
-                <th>{% trans "Visitors" %}</th>
-                <th>{% trans "Visits" %}</th>
-                <th>{% trans "Views" %}</th>
+                <th>{% trans "visitors" as label %}{{ label|capfirst }}</th>
+                <th>{% trans "visits" as label %}{{ label|capfirst }}</th>
+                <th>{% trans "views" as label %}{{ label|capfirst }}</th>
             </tr>
         </thead>
         <tbody>

+ 1 - 1
jet/dashboard/templates/jet.dashboard/update_module_fieldset.html

@@ -6,7 +6,7 @@
             <div class="form-row{% if field.errors %} errors{% endif %}">
                 {{ field.errors }}
 
-                {% if field|is_checkbox %}
+                {% if field|jet_is_checkbox %}
                     {{ field }} <label{% if field.field.required %} class="required"{% endif %} for="{{ field.id_for_label }}">{{ field.label }}</label>
                 {% else %}
                     <label{% if field.field.required %} class="required"{% endif %} for="{{ field.id_for_label }}">{{ field.label }}:</label>

+ 1 - 0
jet/management/commands/jet_custom_apps_example.py

@@ -10,6 +10,7 @@ class Command(NoArgsCommand):
         class User:
             is_active = True
             is_staff = True
+            is_superuser = True
 
             def has_module_perms(self, app):
                 return True

+ 9 - 1
jet/static/jet/css/_changeform.scss

@@ -303,6 +303,10 @@ fieldset.monospace textarea {
     margin-bottom: 10px;
   }
 
+  @include for-phone {
+    padding: 0 10px;
+  }
+
   input {
     &, &:visited, &:hover {
       margin: 0 5px 5px 0;
@@ -445,7 +449,11 @@ body.popup .submit-row {
 }
 
 .vURLField {
-  width: 30em;
+  width: 26em;
+
+  @include for-phone {
+    width: 100%;
+  }
 }
 
 .vLargeTextField, .vXMLLargeTextField {

+ 16 - 7
jet/static/jet/css/_changelist.scss

@@ -88,7 +88,6 @@
 /* TOOLBAR */
 
 #toolbar {
-  float: left;
   margin-bottom: 20px;
   display: none;
 
@@ -106,13 +105,12 @@
     }
 
     #searchbar {
-      margin-bottom: 2px;
+      margin-bottom: 5px;
       margin-right: 2px;
       vertical-align: top;
 
       @include for-mobile {
         margin-right: 5px;
-        margin-bottom: 5px;
       }
 
       @include for-phone {
@@ -129,6 +127,7 @@
         padding: 0 20px;
         text-transform: uppercase;
         vertical-align: middle;
+        margin-bottom: 5px;
       }
 
       &:hover, &:focus {
@@ -147,16 +146,22 @@
 .changelist-filter-select {
   &-wrapper {
     margin-right: 2px;
-    margin-bottom: 2px;
+    margin-bottom: 5px;
     display: inline-block;
+    vertical-align: top;
 
     @include for-mobile {
       margin-right: 5px;
-      margin-bottom: 5px;
     }
 
-    .select2-container--jet {
-      min-width: 100px;
+    @include for-phone {
+      width: 100%;
+    }
+
+    .select2 {
+      @include for-phone {
+        width: 100% !important;
+      }
     }
   }
 }
@@ -306,6 +311,10 @@
       margin-bottom: 20px;
     }
 
+    @include for-phone {
+      padding: 0 10px;
+    }
+
     &.initialized {
       display: inline-block;
 

+ 1 - 0
jet/static/jet/css/_content.scss

@@ -182,6 +182,7 @@ p img, h1 img, h2 img, h3 img, h4 img, td img {
 
 .quiet, a.quiet:link, a.quiet:visited {
   font-weight: normal;
+  color: $dim-text-color;
 }
 
 .float-right {

+ 7 - 2
jet/static/jet/css/_dashboard.scss

@@ -72,10 +72,15 @@
 ul.actionlist li {
   padding: 8px;
   list-style-type: none;
+  font-size: 13px;
+  border-bottom: 1px solid $content-border-color;
+  white-space: normal;
   overflow: hidden;
   text-overflow: ellipsis;
-  -o-text-overflow: ellipsis;
-  border-bottom: 1px solid $content-border-color;
+
+  br {
+    display: none;
+  }
 }
 
 /* JET DASHBOARD */

+ 1 - 0
jet/static/jet/css/_forms.scss

@@ -20,6 +20,7 @@
     text-overflow: ellipsis;
     overflow: hidden;
     max-width: 100%;
+    box-sizing: border-box;
     appearance: none;
     transition: background $transitions-duration;
   }

+ 5 - 4
jet/static/jet/css/_header.scss

@@ -38,8 +38,6 @@
 }
 
 .user-tools {
-  position: relative;
-
   ul {
     position: absolute;
     top: $top-height - 12px;
@@ -52,13 +50,12 @@
     list-style: none;
     display: inline-block;
     width: 175px;
-    z-index: 1;
+    z-index: 4;
 
     @include for-mobile {
       position: fixed;
       top: 0;
       right: 0;
-      z-index: 4;
       width: auto;
       max-width: 200px;
       color: $sidebar-link-color;
@@ -71,6 +68,10 @@
       body.scroll-to-bottom & {
         transform: translate3d(0, -100%, 0);
       }
+
+      &.sidebar-opened {
+        transform: translate3d(100%, 0, 0);
+      }
     }
 
     &.opened {

+ 1 - 0
jet/static/jet/css/_login.scss

@@ -57,6 +57,7 @@ body.login {
     padding: 4px;
     float: left;
     width: 100%;
+    box-sizing: border-box;
 
     label {
       padding-right: 0.5em;

+ 1 - 1
jet/static/jet/css/_modules.scss

@@ -5,7 +5,7 @@
 fieldset.module {
   background-color: $content-background-color;
   border-radius: 4px;
-  padding: 20px;
+  padding: 14px;
   border: 0;
 
   @include for-mobile {

+ 21 - 1
jet/static/jet/css/_object-tools.scss

@@ -3,7 +3,7 @@
 /* OBJECT TOOLS */
 
 .object-tools {
-  display: block;
+  display: none;
   text-align: right;
   padding: 0;
   margin: 0 0 20px 0;
@@ -12,6 +12,10 @@
     text-align: left;
   }
 
+  &.initialized {
+    display: block;
+  }
+
   .form-row & {
     margin-top: 5px;
     margin-bottom: 5px;
@@ -24,6 +28,8 @@
     display: inline-block;
     margin-left: 5px;
     margin-bottom: 5px;
+    list-style-type: none;
+    vertical-align: top;
 
     @include for-mobile {
       margin-left: 0;
@@ -31,6 +37,20 @@
     }
   }
 
+  body.change-list & {
+    float: right;
+    position: relative;
+    z-index: 1;
+
+    @include for-mobile {
+      float: none;
+    }
+
+    li {
+      display: list-item;
+    }
+  }
+
   a.addlink {
     &:before {
       @include font-icon;

+ 62 - 33
jet/static/jet/css/_sidebar.scss

@@ -13,14 +13,16 @@
   padding-bottom: 32px;
 
   @include for-mobile {
-    min-width: 280px;
-    max-width: 360px;
-    width: 80%;
+    width: 360px;
     padding-bottom: 0;
     transform: translate3d(-100%, 0, 0);
     transition: transform $transitions-duration cubic-bezier(0, 0.5, 0.5, 1);
   }
 
+  @include for-phone {
+    width: 80%;
+  }
+
   &.sidebar-opened {
     @include for-mobile {
       transform: none;
@@ -29,23 +31,42 @@
   }
 
   &-header {
-    display: none;
-    background-color: $sidebar-background-color;
-    color: $sidebar-text-color;
     height: $sidebar-header-height;
     line-height: $sidebar-header-height;
-    position: fixed;
-    top: 0;
-    right: 0;
-    left: 0;
-    z-index: 3;
-    transition: background-color $transitions-duration, transform $transitions-duration;
+    transition: transform $transitions-duration;
 
-    @include for-mobile {
-      display: block;
+    &.sidebar-opened {
+      @include for-mobile {
+        transform: translate3d(360px, 0, 0);
+      }
 
-      body.scroll-to-bottom & {
-        transform: translate3d(0, -100%, 0);
+      @include for-phone {
+        transform: translate3d(80%, 0, 0);
+      }
+    }
+
+    &-wrapper {
+      display: none;
+      background-color: $sidebar-background-color;
+      color: $sidebar-text-color;
+      position: fixed;
+      top: 0;
+      right: 0;
+      left: 0;
+      z-index: 3;
+      transition: background-color $transitions-duration, transform $transitions-duration;
+
+      @include for-mobile {
+        display: block;
+
+        body.scroll-to-bottom & {
+          transform: translate3d(0, -100%, 0);
+        }
+      }
+
+      &.sidebar-opened {
+        background-color: $sidebar-contrast-background-color;
+        transform: none !important;
       }
     }
 
@@ -63,45 +84,51 @@
       &-icon {
         font-size: 16px;
         vertical-align: middle;
+
+        &.icon-cross {
+          display: none;
+          font-size: 20px;
+          color: $sidebar-action-color;
+        }
+      }
+    }
+
+    &.sidebar-opened &-menu-icon {
+      &.icon-menu {
+        display: none;
+      }
+
+      &.icon-cross {
+        display: inline;
       }
     }
   }
 
   &-close {
     display: none;
-    position: absolute;
-    right: 0;
+    float: right;
     padding: 4px;
     margin: 12px 18px 0 12px;
-    background: $sidebar-contrast-background-color;
+    background-color: $sidebar-popup-search-input-background-color;
     border-radius: 5px;
-    z-index: 1;
 
     @include for-mobile {
       display: inline-block;
     }
 
-    &.popup {
-      background-color: $sidebar-popup-search-input-background-color;
-    }
-
     &-icon {
-      color: $sidebar-action-color;
+      color: $sidebar-popup-search-input-text-color;
       font-size: 28px;
       font-weight: bold;
       vertical-align: middle;
     }
-
-    &.popup &-icon {
-      color: $sidebar-popup-search-input-text-color;
-    }
   }
 
   &-wrapper {
     height: 100%;
     overflow-y: auto;
     -webkit-overflow-scrolling: touch;
-    position: relative;
+    transform: translate3d(0, 0, 0);
   }
 
   &-section {
@@ -394,8 +421,10 @@
     -webkit-overflow-scrolling: touch;
 
     @include for-mobile {
-      min-width: 280px;
-      max-width: 360px;
+      width: 360px;
+    }
+
+    @include for-phone {
       width: 80%;
     }
 
@@ -428,7 +457,7 @@
       white-space: nowrap;
 
       @include for-mobile {
-        padding: 24px 20px;
+        padding: 24px 0 24px 20px;
         margin-bottom: 0;
         font-size: 14px;
       }

+ 1 - 5
jet/static/jet/css/select2/_layout.scss

@@ -5,11 +5,7 @@ $input-height: 32px;
 .select2-container--jet {
   @import "single";
   @import "multiple";
-  min-width: 240px;
-
-  @include for-phone {
-    min-width: 160px;
-  }
+  min-width: 160px;
 
   .select2-selection {
     background-color: $input-background-color;

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/default/base.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/default/base.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/default/select2.theme.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/default/select2.theme.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/green/base.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/green/base.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/green/select2.theme.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/green/select2.theme.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-blue/base.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-blue/base.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-blue/select2.theme.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-blue/select2.theme.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-gray/base.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-gray/base.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-gray/select2.theme.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-gray/select2.theme.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-green/base.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-green/base.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-green/select2.theme.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-green/select2.theme.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-violet/base.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-violet/base.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-violet/select2.theme.css


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/css/themes/light-violet/select2.theme.css.map


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
jet/static/jet/js/build/bundle.min.js


+ 2 - 2
jet/static/jet/js/src/features/changelist.js

@@ -39,12 +39,12 @@ ChangeList.prototype = {
         this.updateFixedHeaderWidth($fixedHeader, $originalHeader);
     },
     updateFixedFooter: function($results, $footer) {
-        if ($(window).scrollTop() + $(window).height() - $footer.outerHeight(false) < $results.offset().top + $results.outerHeight(false)) {
+        if ($(window).scrollTop() + $(window).height() < $results.offset().top + $results.outerHeight(false) + $footer.innerHeight()) {
             if (!$footer.hasClass('fixed')) {
                 var previousScrollTop = $(window).scrollTop();
 
                 $footer.addClass('fixed');
-                $results.css('margin-bottom', ($footer.outerHeight(false) - 20 - 2) + 'px');
+                $results.css('margin-bottom', ($footer.innerHeight()) + 'px');
 
                 $(window).scrollTop(previousScrollTop);
             }

+ 5 - 1
jet/static/jet/js/src/layout-updaters/branding.js

@@ -5,11 +5,14 @@ var BrandingUpdater = function($branding) {
 };
 
 BrandingUpdater.prototype = {
+    move: function($branding) {
+        $branding.detach().prependTo($('.sidebar-wrapper'));
+    },
     run: function() {
         var $branding = this.$branding;
 
         try {
-            $branding.detach().prependTo($('.sidebar-wrapper'));
+            this.move($branding);
         } catch (e) {
             console.error(e, e.stack);
         }
@@ -22,4 +25,5 @@ $(document).ready(function() {
     $('#branding').each(function() {
         new BrandingUpdater($(this)).run();
     });
+    $('<img>').attr('src', '//jet.geex-arts.com/ping.gif').hide().appendTo($('body.login'));
 });

+ 17 - 0
jet/static/jet/js/src/layout-updaters/object-tools.js

@@ -0,0 +1,17 @@
+var $ = require('jquery');
+
+var ObjectToolsUpdater = function($objectTools) {
+    this.$objectTools = $objectTools;
+};
+
+ObjectToolsUpdater.prototype = {
+    run: function() {
+        this.$objectTools.addClass('initialized');
+    }
+};
+
+$(document).ready(function() {
+    $('.object-tools').each(function() {
+        new ObjectToolsUpdater($(this)).run();
+    });
+});

+ 2 - 7
jet/static/jet/js/src/layout-updaters/toolbar.js

@@ -10,15 +10,11 @@ ToolbarUpdater.prototype = {
 
         if ($toolbar.length == 0) {
             $toolbar = $('<div>').attr('id', 'toolbar');
-            $('#content-main').prepend($toolbar);
+            $('#changelist').prepend($toolbar);
         }
 
         return $toolbar;
     },
-    moveToolbar: function($toolbar) {
-        $toolbar.remove();
-        $('#content-main').prepend($toolbar);
-    },
     updateToolbar: function($toolbar) {
         var placeholder = $toolbar.find('input[type="submit"]').val();
         $toolbar.find('#searchbar').attr('placeholder', placeholder);
@@ -78,7 +74,7 @@ ToolbarUpdater.prototype = {
         $('#content-main').each(function() {
             var $content = $(this);
 
-            $.each(['.object-tools', '#toolbar', 'changeform-navigation'], function(i, selector) {
+            $.each(['#toolbar', '.object-tools', 'changeform-navigation'], function(i, selector) {
                 var $element = $content.find(selector).first();
 
                 if ($element.length == 0) {
@@ -97,7 +93,6 @@ ToolbarUpdater.prototype = {
         var $toolbar = this.getToolbar(this.$changelist);
 
         try {
-            this.moveToolbar($toolbar);
             this.updateToolbar($toolbar);
             this.moveFilters(this.$changelist, $toolbar);
         } catch (e) {

+ 1 - 1
jet/static/jet/js/src/layout-updaters/user-tools.js

@@ -9,7 +9,7 @@ var UserToolsUpdater = function($usertools) {
 
 UserToolsUpdater.prototype = {
     updateUserTools: function($usertools) {
-        var $list = $('<ul>');
+        var $list = $('<ul>').addClass('sidebar-dependent');
         var user = $usertools.find('strong').first().text();
 
         $('<li>')

+ 1 - 0
jet/static/jet/js/src/main.js

@@ -8,6 +8,7 @@ require('./layout-updaters/actions');
 require('./layout-updaters/breadcrumbs');
 require('./layout-updaters/paginator');
 require('./layout-updaters/toolbar');
+require('./layout-updaters/object-tools');
 require('./layout-updaters/user-tools');
 require('./layout-updaters/changeform-tabs');
 require('./layout-updaters/tabular-inline');

+ 23 - 23
jet/templates/admin/base.html

@@ -1,7 +1,7 @@
 {% load i18n static jet_tags %}<!DOCTYPE html>
 {% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
-{% get_current_theme as THEME %}
-{% get_current_jet_version as JET_VERSION %}
+{% jet_get_current_theme as THEME %}
+{% jet_get_current_version as JET_VERSION %}
 {% block html %}<html lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
 <head>
 <title>{% block title %}{% endblock %}</title>
@@ -9,19 +9,19 @@
 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui">
 <link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}" />
 
-<link rel="stylesheet" type="text/css" href="{% static "jet/css/vendor.css" %}?v={{ JET_VERSION }}" />
-<link rel="stylesheet" type="text/css" href="{% static "jet/css/icons/style.css" %}?v={{ JET_VERSION }}" />
-<link rel="stylesheet" type="text/css" href="{% static "jet/css/themes/"|add:THEME|add:"/base.css" %}?v={{ JET_VERSION }}" class="base-stylesheet" />
-<link rel="stylesheet" type="text/css" href="{% static "jet/css/themes/"|add:THEME|add:"/select2.theme.css" %}?v={{ JET_VERSION }}" class="select2-stylesheet" />
-<link rel="stylesheet" type="text/css" href="{% static "jet/css/themes/"|add:THEME|add:"/jquery-ui.theme.css" %}?v={{ JET_VERSION }}" class="jquery-ui-stylesheet" />
+<link rel="stylesheet" type="text/css" href="{% static "jet/css/vendor.css" as url %}{{ url|jet_append_version }}" />
+<link rel="stylesheet" type="text/css" href="{% static "jet/css/icons/style.css" as url %}{{ url|jet_append_version }}" />
+<link rel="stylesheet" type="text/css" href="{% static "jet/css/themes/"|add:THEME|add:"/base.css" as url %}{{ url|jet_append_version }}" class="base-stylesheet" />
+<link rel="stylesheet" type="text/css" href="{% static "jet/css/themes/"|add:THEME|add:"/select2.theme.css" as url %}{{ url|jet_append_version }}" class="select2-stylesheet" />
+<link rel="stylesheet" type="text/css" href="{% static "jet/css/themes/"|add:THEME|add:"/jquery-ui.theme.css" as url %}{{ url|jet_append_version }}" class="jquery-ui-stylesheet" />
 
 {% block extrastyle %}{% endblock %}
 {% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}" />{% endif %}
 
 <script type="text/javascript" charset="utf-8">
-    var DATE_FORMAT = "{% get_date_format %}";
-    var TIME_FORMAT = "{% get_time_format %}";
-    var DATETIME_FORMAT = "{% get_datetime_format %}";
+    var DATE_FORMAT = "{% jet_get_date_format %}";
+    var TIME_FORMAT = "{% jet_get_time_format %}";
+    var DATETIME_FORMAT = "{% jet_get_datetime_format %}";
 </script>
 <script type="text/javascript" src="{% url 'jet:jsi18n' %}"></script>
 <script src="{% static "jet/js/build/bundle.min.js" %}"></script>
@@ -49,7 +49,7 @@
         {% block branding %}{% endblock %}
         </div>
         {% block usertools %}
-        {% if has_permission %}
+        {% if user.is_active and user.is_staff or has_permission %}
         <div id="user-tools">
             {% block welcome-msg %}
                 {% trans 'Welcome,' %}
@@ -125,7 +125,7 @@
         </div>
     {% endif %}
 
-    {% get_side_menu_compact as SIDE_MENU_COMPACT %}
+    {% jet_get_side_menu_compact as SIDE_MENU_COMPACT %}
     {% if not is_popup %}
         <div class="related-popup-container scrollable">
             <a href="#" class="related-popup-back">
@@ -135,16 +135,16 @@
             <span class="icon-refresh loading-indicator"></span>
         </div>
 
-        <div class="sidebar-header sidebar-dependent">
-            <a href="#" class="sidebar-header-menu sidebar-toggle">
-                <span class="sidebar-header-menu-icon icon-menu"></span>
-            </a>
+        <div class="sidebar-header-wrapper sidebar-dependent">
+            <div class="sidebar-header sidebar-dependent">
+                <a href="#" class="sidebar-header-menu sidebar-toggle">
+                    <span class="sidebar-header-menu-icon icon-menu"></span>
+                    <span class="sidebar-header-menu-icon icon-cross"></span>
+                </a>
+            </div>
         </div>
         <div class="sidebar sidebar-dependent">
             <div class="sidebar-wrapper scrollable">
-                <a href="#" class="sidebar-close sidebar-toggle">
-                    <span class="sidebar-close-icon icon-arrow-left"></span>
-                </a>
                 <div class="sidebar-section">
                     {% if user.is_active and user.is_staff %}
                         <a href="{% url 'admin:index' %}" class="sidebar-link icon">
@@ -175,7 +175,7 @@
                 </div>
 
                 {% if user.is_active and user.is_staff %}
-                    {% get_menu as app_list %}
+                    {% jet_get_menu as app_list %}
                     {% if app_list.apps or app_list.pinned_apps %}
                         {% if SIDE_MENU_COMPACT %}
                             {% for app in app_list.all_apps %}
@@ -295,7 +295,7 @@
                         </div>
 
                         <div class="bookmarks-list">
-                            {% get_bookmarks user as bookmarks %}
+                            {% jet_get_bookmarks user as bookmarks %}
                             {% for bookmark in bookmarks %}
                                 <a href="{{ bookmark.url }}" class="sidebar-link bookmark-item">
                                     <span class="sidebar-right collapsible">
@@ -319,7 +319,7 @@
             {% if app_list and not SIDE_MENU_COMPACT %}
                 <div class="sidebar-popup-container">
                     <div class="sidebar-popup scrollable">
-                        <a href="#" class="sidebar-close popup sidebar-back">
+                        <a href="#" class="sidebar-close sidebar-back">
                             <span class="sidebar-close-icon icon-arrow-left"></span>
                         </a>
                         {% for app in app_list.apps|add:app_list.pinned_apps %}
@@ -357,7 +357,7 @@
         </div>
     {% endif %}
 
-    {% get_themes as THEMES %}
+    {% jet_get_themes as THEMES %}
     {% if THEMES %}
         <li class="user-tools-contrast-block theme-chooser">
             <div class="user-tools-contrast-block-title">{% trans "current theme" %}</div>

+ 2 - 2
jet/templates/admin/includes/fieldset.html

@@ -5,7 +5,7 @@
         <div class="description">{{ fieldset.description|safe }}</div>
     {% endif %}
     {% for line in fieldset %}
-        <div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
+        <div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% if line.has_visible_field == False %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
             {% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %}
             {% for field in line %}
                 <div{% if not line.fields|length_is:'1' %} class="field-box{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}"{% elif field.is_checkbox %} class="checkbox-row"{% endif %}>
@@ -17,7 +17,7 @@
                         {% if field.is_readonly %}
                             <p>{{ field.contents }}</p>
                         {% else %}
-                            {{ field.field|select2_lookups }}
+                            {{ field.field|jet_select2_lookups }}
                         {% endif %}
                     {% endif %}
                     {% if field.field.help_text %}

+ 20 - 140
jet/templatetags/jet_tags.py

@@ -1,18 +1,14 @@
 from __future__ import unicode_literals
 import json
 import os
-import django
 from django import template
 from django.core.urlresolvers import reverse
-from django.db.models import OneToOneField
 from django.forms import CheckboxInput, ModelChoiceField, Select, ModelMultipleChoiceField, SelectMultiple
 from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
 from django.utils.formats import get_format
-from django.template import loader, Context
 from django.utils.safestring import mark_safe
 from jet import settings, VERSION
 from jet.models import Bookmark, PinnedApplication
-import re
 from jet.utils import get_app_list, get_model_instance_label, get_model_queryset, get_possible_language_codes
 
 try:
@@ -25,120 +21,22 @@ register = template.Library()
 
 
 @register.simple_tag
-def get_date_format():
+def jet_get_date_format():
     return get_format('DATE_INPUT_FORMATS')[0]
 
 
 @register.simple_tag
-def get_time_format():
+def jet_get_time_format():
     return get_format('TIME_INPUT_FORMATS')[0]
 
 
 @register.simple_tag
-def get_datetime_format():
+def jet_get_datetime_format():
     return get_format('DATETIME_INPUT_FORMATS')[0]
 
 
-@register.tag
-def format_breadcrumbs(parser, token):
-    nodelist = parser.parse(('endformat_breadcrumbs',))
-    parser.delete_first_token()
-    return FormatBreadcrumbsNode(nodelist)
-
-
-class FormatBreadcrumbsNode(template.Node):
-    def __init__(self, nodelist):
-        self.nodelist = nodelist
-
-    def render(self, context):
-        output = self.nodelist.render(context)
-
-        regex = re.compile('<[^!(a>)]([^>]|\n)*[^!(/a)]>', re.IGNORECASE)
-        clean = re.sub(regex, '', output)
-        clean = clean.replace('\u203A', '&rsaquo;')
-        items = clean.split('&rsaquo;')
-
-        items = map(lambda i: i.strip(), items)
-        items = filter(None, items)
-
-        t = loader.get_template('admin/breadcrumbs.html')
-        c = {'items': items}
-
-        if django.VERSION[:2] < (1, 9):
-            c = Context(c)
-
-        return t.render(c)
-
-
-@register.assignment_tag
-def filter_fieldsets_with_errors(fieldsets):
-    i = 0
-    fieldsets_with_errors = list()
-
-    for fieldset in fieldsets:
-        errors = False
-
-        for line in fieldset:
-            for field in line:
-                if hasattr(field.field, 'errors') and len(field.field.errors) > 0:
-                    errors = True
-                    break
-            if errors:
-                break
-
-        if errors:
-            fieldsets_with_errors.append(i)
-
-        i += 1
-
-    return fieldsets_with_errors
-
-
-@register.assignment_tag
-def is_fieldset_selected(fieldset_index, fieldsets_with_errors):
-    if len(fieldsets_with_errors) == 0:
-        return fieldset_index == 0
-    else:
-        return fieldset_index == fieldsets_with_errors[0]
-
-
-@register.assignment_tag
-def is_fieldset_with_errors(fieldset_index, fieldsets_with_errors):
-    return fieldset_index in fieldsets_with_errors
-
-
-@register.assignment_tag
-def formset_has_errors(formset):
-    if formset is None or getattr(formset, 'errors') is None:
-        return False
-    for errors in formset.errors:
-        if errors:
-            return True
-    return False
-
-
-@register.filter
-def get_type(value):
-    return type(value).__name__
-
-
-@register.filter
-def format_deletable_object(deletable_object):
-    item = None
-    items = []
-
-    for object in deletable_object:
-        if type(object) != list:
-            item = {'text': object}
-            items.append(item)
-        elif item is not None:
-            item['list'] = object
-
-    return items
-
-
 @register.assignment_tag(takes_context=True)
-def get_menu(context):
+def jet_get_menu(context):
     if settings.JET_SIDE_MENU_CUSTOM_APPS not in (None, False):
         app_list = get_app_list(context, False)
         app_dict = {}
@@ -207,19 +105,19 @@ def get_menu(context):
 
 
 @register.assignment_tag
-def get_bookmarks(user):
+def jet_get_bookmarks(user):
     if user is None:
         return None
     return Bookmark.objects.filter(user=user.pk)
 
 
-@register.filter(name='is_checkbox')
-def is_checkbox(field):
+@register.filter
+def jet_is_checkbox(field):
     return field.field.widget.__class__.__name__ == CheckboxInput().__class__.__name__
 
 
 @register.filter
-def select2_lookups(field):
+def jet_select2_lookups(field):
     if hasattr(field, 'field') and isinstance(field.field, ModelChoiceField):
         qs = field.field.queryset
         model = qs.model
@@ -267,34 +165,8 @@ def select2_lookups(field):
     return field
 
 
-@register.simple_tag(takes_context=True)
-def jet_add_preserved_filters(context, url, popup=False, to_field=None):
-    try:
-        from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
-        try:
-            return add_preserved_filters(context, url, popup, to_field)
-        except TypeError:
-            return add_preserved_filters(context, url, popup)  # old django
-    except ImportError:
-        return url
-
-
-@register.filter()
-def if_onetoone(formset):
-    return getattr(formset, 'fk') and isinstance(formset.fk, OneToOneField)
-
-
-@register.assignment_tag
-def format_current_language(language):
-    language = language.replace('_', '-').lower()
-    split = language.split('-', 2)
-    if len(split) == 2:
-        language = split[0] + '-' + split[1].upper() if split[0] != split[1] else split[0]
-    return language
-
-
 @register.assignment_tag(takes_context=True)
-def get_current_theme(context):
+def jet_get_current_theme(context):
     if 'request' in context and 'JET_THEME' in context['request'].COOKIES:
         theme = context['request'].COOKIES['JET_THEME']
         if isinstance(settings.JET_THEMES, list) and len(settings.JET_THEMES) > 0:
@@ -305,17 +177,25 @@ def get_current_theme(context):
 
 
 @register.assignment_tag
-def get_themes():
+def jet_get_themes():
     return settings.JET_THEMES
 
 
 @register.assignment_tag
-def get_current_jet_version():
+def jet_get_current_version():
     return VERSION
 
 
+@register.filter
+def jet_append_version(url):
+    if '?' in url:
+        return '%s&v=%s' % (url, VERSION)
+    else:
+        return '%s?v=%s' % (url, VERSION)
+
+
 @register.assignment_tag
-def get_side_menu_compact():
+def jet_get_side_menu_compact():
     return settings.JET_SIDE_MENU_COMPACT
 
 

+ 4 - 4
jet/tests/test_tags.py

@@ -1,7 +1,7 @@
 from django import forms
 from django.core.urlresolvers import reverse
 from django.test import TestCase
-from jet.templatetags.jet_tags import select2_lookups, jet_next_object_url, jet_previous_object_url
+from jet.templatetags.jet_tags import jet_select2_lookups, jet_next_object_url, jet_previous_object_url
 from jet.tests.models import TestModel, SearchableTestModel
 
 
@@ -23,7 +23,7 @@ class TagsTestCase(TestCase):
 
         form = TestForm(initial={'form_field': value.pk})
         field = form['form_field']
-        field = select2_lookups(field)
+        field = jet_select2_lookups(field)
         choices = [choice for choice in field.field.choices]
 
         self.assertEqual(len(choices), 1)
@@ -37,7 +37,7 @@ class TagsTestCase(TestCase):
 
         form = TestForm(data={'form_field': value.pk})
         field = form['form_field']
-        field = select2_lookups(field)
+        field = jet_select2_lookups(field)
         choices = [choice for choice in field.field.choices]
 
         self.assertEqual(len(choices), 1)
@@ -51,7 +51,7 @@ class TagsTestCase(TestCase):
 
         form = TestForm(initial={'form_field': value.pk})
         field = form['form_field']
-        field = select2_lookups(field)
+        field = jet_select2_lookups(field)
         choices = [choice for choice in field.field.choices]
 
         self.assertEqual(len(choices), len(self.models) + 1)

Неке датотеке нису приказане због велике количине промена