├── .coveragerc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── nexus ├── __init__.py ├── apps.py ├── checks.py ├── compat.py ├── conf.py ├── media │ ├── css │ │ ├── facebox.css │ │ └── nexus.css │ ├── img │ │ ├── admin │ │ │ ├── icon_add.gif │ │ │ ├── icon_change.gif │ │ │ ├── icon_delete.gif │ │ │ └── nav-bg.gif │ │ ├── facebox │ │ │ ├── closelabel.png │ │ │ └── loading.gif │ │ ├── favicon.png │ │ ├── loading.gif │ │ └── nexus_logo.png │ └── js │ │ ├── lib │ │ ├── facebox │ │ │ └── facebox.js │ │ └── jquery.js │ │ └── nexus.js ├── modules.py ├── sites.py ├── templates │ └── nexus │ │ ├── base.html │ │ ├── dashboard.html │ │ ├── module.html │ │ └── navigation.html └── templatetags │ ├── __init__.py │ └── nexus_helpers.py ├── pytest.ini ├── requirements.in ├── requirements.txt ├── runtests.py ├── screenshot.png ├── setup.cfg ├── setup.py ├── tests ├── README.rst ├── manage.py ├── settings │ ├── __init__.py │ ├── base.py │ ├── dev.py │ └── test.py ├── testapp │ ├── __init__.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── runserver.py │ ├── models.py │ ├── nexus_modules.py │ ├── templates │ │ └── nexus │ │ │ ├── example │ │ │ ├── dashboard.html │ │ │ └── index.html │ │ │ ├── styleguide │ │ │ └── index.html │ │ │ └── system-stats │ │ │ └── dashboard.html │ ├── templatetags │ │ ├── __init__.py │ │ └── test_nexus_helpers.py │ ├── test_checks.py │ ├── test_conf.py │ ├── test_nexus_modules │ │ ├── __init__.py │ │ └── test_hello_world.py │ └── test_views.py └── urls.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = nexus/compat.py 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.py] 14 | line_length=120 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .cache 27 | .coverage 28 | .coverage* 29 | .pytest_cache 30 | .tox 31 | nosetests.xml 32 | htmlcov 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | 42 | # Complexity 43 | output/*.html 44 | output/*/index.html 45 | 46 | # Sphinx 47 | docs/_build 48 | 49 | # Tests 50 | tests/dev-database.sqlite3 51 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: xenial 3 | 4 | notifications: 5 | email: false 6 | 7 | language: python 8 | python: 3.6 9 | cache: pip 10 | 11 | install: 12 | - pip install coveralls tox 13 | 14 | script: 15 | - tox 16 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | .. :changelog: 2 | 3 | ======= 4 | History 5 | ======= 6 | 7 | Pending Release 8 | --------------- 9 | 10 | .. Insert new release notes below this line 11 | 12 | 2.1.2 (2019-05-17) 13 | ------------------ 14 | 15 | * **Retired: this project is no longer maintained.** I (Adam Johnson) no longer 16 | have time to continue maintaining this. I was doing so to support 17 | `gargoyle-yplan `__, a fork for my 18 | ex-employer YPlan. If you'd like to sponsor ongoing maintenance or take it 19 | over yourself, please contact me@adamj.eu. 20 | 21 | 2.1.1 (2019-04-28) 22 | ------------------ 23 | 24 | * Tested on Django 2.2. No changes were needed for compatibility. 25 | 26 | 2.1.0 (2019-04-14) 27 | ------------------ 28 | 29 | * Drop Django 1.8, 1.9, and 1.10 support. Only Django 1.11+ is supported now. 30 | 31 | 2.0.0 (2019-03-02) 32 | ------------------ 33 | 34 | * Drop Python 2 support, only Python 3.4+ is supported now. 35 | 36 | 1.6.1 (2018-10-17) 37 | ------------------ 38 | 39 | * Fix Django deprecation warnings 40 | 41 | 1.6.0 (2017-10-28) 42 | ------------------ 43 | 44 | * Added Django 2.0b1 support 45 | * Tested on Django 1.11 (no changes were necessary) 46 | 47 | 1.5.0 (2017-01-19) 48 | ------------------ 49 | 50 | * Added CSRF token on the base template to make AJAX requests work when Django's ``CSRF_COOKIE_HTTPONLY`` setting is 51 | set 52 | * Removed the now obsolete template tag ``nexus_csrf_cookie_name`` 53 | 54 | 1.4.0 (2016-05-24) 55 | ------------------ 56 | 57 | * Fixed footer appearance on long pages 58 | * Fixed the tab highlighting and removed ``NexusModule.get_request`` which existed only to support the old broken code. 59 | * 'jQuery Templates' is no longer included in Nexus - this was done for Gargoyle, which now uses its own bundled 60 | templating code instead. 61 | 62 | 1.3.1 (2016-02-25) 63 | ------------------ 64 | 65 | * Theme updated a bit more to make buttons look nice 66 | 67 | 1.3.0 (2016-02-24) 68 | ------------------ 69 | 70 | * New Logo and theme, thanks @emache. 71 | * The login logic no longer sends users without permission to see Nexus through a redirect loop, thanks @ChunYin for 72 | the report. 73 | 74 | 1.2.0 (2016-02-12) 75 | ------------------ 76 | 77 | * Removed support for Django 1.7 78 | * Removed the need to add a call to ``nexus.autodiscover()`` in your URLConf by using the ``AppConfig``, similar to 79 | Django Admin from Django 1.7+ 80 | * Upgraded jQuery to 1.6.4 81 | * Upgraded Facebox to its master version 82 | 83 | 1.1.0 (2016-01-13) 84 | ------------------ 85 | 86 | * Removed support for old Django versions 87 | * Fixed all deprecation warnings on Django 1.7 and 1.8 88 | * Added Django 1.9 support 89 | * Added Python 3.4 and 3.5 support 90 | * Historically Nexus had a module that embedded Django Admin; this has not worked since Django 1.3 due to removal of 91 | the ``adminmedia`` template tag that the templates were still using. Because it seems that no one has been using it, 92 | it has been removed. Users are encourage to just use the normal Django Admin instead. Nexus thus ships with no 93 | modules included. 94 | * Removed the login/logout pages, which were copied and adapted from an old version of Django Admin, and likely no 95 | longer secure. If you are not logged in Nexus will now redirect you to Django Admin - thus Django Admin is now 96 | required by Nexus. 97 | * Fixed Nexus CSRF protection to work if you have changed the CSRF cookie name, 98 | thanks to a PR on the original Nexus from Github users @karech and 99 | @graingert. 100 | * Removed all inline javascript, thanks @graingert. 101 | 102 | 1.0.0 (2015-12-09) 103 | ------------------ 104 | 105 | * First publication on PyPI as ``nexus-yplan`` 106 | * Django 1.8 compatibility 107 | 108 | 0.3.1 (2015-01-22) 109 | ------------------ 110 | 111 | * Better support for Django 1.7 and Python 3 112 | 113 | 0.2.3 (2011-12-19) 114 | ------------------ 115 | 116 | * Ensure on exempt views we still send . 117 | * Downgrade CSRF ajax to work with older versions of jQuery. 118 | 119 | 0.2.2 (2011-12-19) 120 | ------------------ 121 | 122 | * Update AJAX CSRF set to work against correct origins. 123 | 124 | 0.2.1 (2011-12-19) 125 | ------------------ 126 | 127 | * Added version to Nexus footer. 128 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2010 DISQUS 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include setup.py README.rst HISTORY.rst MANIFEST.in LICENSE 2 | recursive-include nexus/templates * 3 | recursive-include nexus/media * 4 | global-exclude *~ 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Nexus 3 | ===== 4 | 5 | .. image:: https://img.shields.io/pypi/v/nexus-yplan.svg 6 | :target: https://pypi.python.org/pypi/nexus-yplan 7 | 8 | .. image:: https://travis-ci.org/adamchainz/nexus.svg?branch=master 9 | :target: https://travis-ci.org/adamchainz/nexus 10 | 11 | **Retired: this project is no longer maintained.** I (Adam Johnson) no longer 12 | have time to continue maintaining this. I was doing so to support 13 | `gargoyle-yplan `__, a fork for my 14 | ex-employer YPlan. If you'd like to sponsor ongoing maintenance or take it over 15 | yourself, please contact me@adamj.eu. 16 | 17 | Nexus is a pluggable admin application in Django. It's designed to give you a simple design and architecture for 18 | building admin applications. 19 | 20 | It was originally created by `Disqus `_, but due to the inactivity we at YPlan have taken over maintenance on this fork. 21 | 22 | Screenshot 23 | ---------- 24 | 25 | .. image:: https://raw.github.com/adamchainz/nexus/master/screenshot.png 26 | 27 | Requirements 28 | ------------ 29 | 30 | Tested with all combinations of: 31 | 32 | * Python: 3.6 33 | * Django: 1.11, 2.0, 2.1, 2.2 34 | 35 | Python 3.4+ supported. 36 | 37 | Installation 38 | ------------ 39 | 40 | Install it with **pip**: 41 | 42 | .. code-block:: bash 43 | 44 | pip install nexus-yplan 45 | 46 | Make sure you ``pip uninstall nexus`` first if you're upgrading from the original to this fork - the packages clash. 47 | 48 | You'll need to enable it much like you would ``django.contrib.admin``. 49 | 50 | First, add it to your ``INSTALLED_APPS`` setting: 51 | 52 | .. code-block:: python 53 | 54 | INSTALLED_APPS = ( 55 | ... 56 | 'nexus', 57 | ) 58 | 59 | ``nexus`` has three dependencies from core Django - ``django.contrib.admin``, ``django.contrib.auth``, and 60 | ``django.contrib.sessions``. If these applications are not in your ``INSTALLED_APPS``, add them; or if you are using a 61 | custom auth system you can skip these requirements by adding the line ``NEXUS_SKIP_INSTALLED_APPS_REQUIREMENTS = True`` 62 | to your settings. 63 | 64 | Second, include nexus at some url in your ``urls.py``: 65 | 66 | .. code-block:: python 67 | 68 | import nexus 69 | 70 | # urls.py 71 | urlpatterns = patterns('', 72 | ('^nexus/', include(nexus.site.urls)), 73 | ) 74 | 75 | Nexus has autodiscovery similar to Django Admin - it will look in each of your ``INSTALLED_APPS`` for a 76 | ``nexus_modules`` submodule, and import that. This is where the app should declare a ``NexusModule`` subclass and use 77 | ``nexus.site.register`` to add it to the main Nexus site. Thus to add functionality you should install some packages 78 | with modules, or write your own. 79 | 80 | 81 | Available Modules 82 | ----------------- 83 | 84 | The following modules are tested against ``nexus-yplan``: 85 | 86 | * `Gargoyle (YPlan fork) `_ 87 | 88 | There are also some older applications that provide Nexus modules, however these were only developed against Disqus' 89 | Nexus and not this fork; your mileage may vary: 90 | 91 | * `nexus-memcache `_ 92 | * `nexus-redis `_ 93 | 94 | If you want to write a module, there are a couple of example modules in ``tests/testapp/nexus_modules.py``, with 95 | templates in ``tests/testapp/templates/nexus/example``. Also the source code shouldn't be too hard to understand. 96 | 97 | 98 | A Note on Login 99 | --------------- 100 | 101 | Until Version 1.1, Nexus included a login/logout functionality. Unfortunately these were copied and adapted from an old 102 | version of the code in Django Admin, and were thus not up to date with security changes in newer Django versions. Since 103 | keeping them up to date would be a burden, and most sites use Django Admin for adminstrator login, the login/logout 104 | functions have been removed. 105 | 106 | Nexus now relies on Django Admin login, or for users to visit it having logged in through another route. 107 | 108 | 109 | Settings 110 | -------- 111 | 112 | Nexus' behaviour can be customized by adding some values to your Django settings. 113 | 114 | Media 115 | ~~~~~ 116 | 117 | By default Nexus serves its media files itself through Python, avoiding any configuration to integrate with your 118 | project. This is convenient but can be slow. You can control where the media files are served from with the setting 119 | ``NEXUS_MEDIA_PREFIX``, for example: 120 | 121 | .. code-block:: python 122 | 123 | NEXUS_MEDIA_PREFIX = '/served/here/' 124 | 125 | This will make Nexus write its media URLs using this prefix, where it assumes you have set up serving its files. 126 | -------------------------------------------------------------------------------- /nexus/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Nexus 3 | ~~~~~ 4 | """ 5 | from django.utils.module_loading import autodiscover_modules 6 | 7 | from nexus.modules import NexusModule 8 | from nexus.sites import NexusSite, site 9 | 10 | __version__ = '2.1.2' 11 | VERSION = __version__ 12 | __all__ = ('autodiscover', 'NexusSite', 'NexusModule', 'site') 13 | 14 | default_app_config = 'nexus.apps.NexusAppConfig' 15 | 16 | 17 | def autodiscover(): 18 | autodiscover_modules('nexus_modules', register_to=site) 19 | -------------------------------------------------------------------------------- /nexus/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class NexusAppConfig(AppConfig): 5 | name = 'nexus' 6 | verbose_name = "Nexus" 7 | 8 | def ready(self): 9 | from nexus.checks import register_checks 10 | register_checks() 11 | 12 | self.module.autodiscover() 13 | -------------------------------------------------------------------------------- /nexus/checks.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.checks import Error, Tags, register 3 | 4 | 5 | def register_checks(): 6 | register(Tags.compatibility)(check_requirements) 7 | 8 | 9 | def check_requirements(app_configs, **kwargs): 10 | errors = [] 11 | 12 | if getattr(settings, 'NEXUS_SKIP_INSTALLED_APPS_REQUIREMENTS', False): 13 | reqs = () 14 | else: 15 | reqs = ('django.contrib.admin', 'django.contrib.auth', 'django.contrib.sessions') 16 | 17 | for req in reqs: 18 | if req not in settings.INSTALLED_APPS: 19 | errors.append(Error( 20 | "Nexus depends on '{}'".format(req), 21 | id='nexus.E001', 22 | hint="Add '{}' to INSTALLED_APPS or set NEXUS_SKIP_INSTALLED_APPS_REQUIREMENTS = True.".format(req), 23 | )) 24 | 25 | return errors 26 | -------------------------------------------------------------------------------- /nexus/compat.py: -------------------------------------------------------------------------------- 1 | # Django 2.0 2 | 3 | try: 4 | from django.urls import reverse # noqa pragma: no cover 5 | except ImportError: 6 | from django.core.urlresolvers import reverse # noqa pragma: no cover 7 | -------------------------------------------------------------------------------- /nexus/conf.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | class Settings(object): 5 | """ 6 | Shadow Django's settings with a little logic 7 | """ 8 | @property 9 | def MEDIA_PREFIX(self): 10 | prefix = getattr(settings, 'NEXUS_MEDIA_PREFIX', '/nexus/media/') 11 | if getattr(settings, 'NEXUS_USE_DJANGO_MEDIA_URL', False): 12 | prefix = getattr(settings, 'MEDIA_URL', prefix) 13 | return prefix 14 | 15 | 16 | nexus_settings = Settings() 17 | -------------------------------------------------------------------------------- /nexus/media/css/facebox.css: -------------------------------------------------------------------------------- 1 | #facebox { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | z-index: 100; 6 | text-align: left; 7 | } 8 | 9 | 10 | #facebox .popup{ 11 | position:relative; 12 | border:3px solid rgba(0,0,0,0); 13 | -webkit-border-radius:5px; 14 | -moz-border-radius:5px; 15 | border-radius:5px; 16 | -webkit-box-shadow:0 0 18px rgba(0,0,0,0.4); 17 | -moz-box-shadow:0 0 18px rgba(0,0,0,0.4); 18 | box-shadow:0 0 18px rgba(0,0,0,0.4); 19 | } 20 | 21 | #facebox .content { 22 | display:table; 23 | width: 370px; 24 | padding: 10px; 25 | background: #fff; 26 | -webkit-border-radius:4px; 27 | -moz-border-radius:4px; 28 | border-radius:4px; 29 | } 30 | 31 | #facebox .content > p:first-child{ 32 | margin-top:0; 33 | } 34 | #facebox .content > p:last-child{ 35 | margin-bottom:0; 36 | } 37 | 38 | #facebox .close{ 39 | position:absolute; 40 | top:5px; 41 | right:5px; 42 | padding:2px; 43 | background:#fff; 44 | } 45 | #facebox .close img{ 46 | opacity:0.3; 47 | } 48 | #facebox .close:hover img{ 49 | opacity:1.0; 50 | } 51 | 52 | #facebox .loading { 53 | text-align: center; 54 | } 55 | 56 | #facebox .image { 57 | text-align: center; 58 | } 59 | 60 | #facebox img { 61 | border: 0; 62 | margin: 0; 63 | } 64 | 65 | #facebox_overlay { 66 | position: fixed; 67 | top: 0px; 68 | left: 0px; 69 | height:100%; 70 | width:100%; 71 | } 72 | 73 | .facebox_hide { 74 | z-index:-100; 75 | } 76 | 77 | .facebox_overlayBG { 78 | background-color: #000; 79 | z-index: 99; 80 | } -------------------------------------------------------------------------------- /nexus/media/css/nexus.css: -------------------------------------------------------------------------------- 1 | /* http://www.positioniseverything.net/easyclearing.html */ 2 | .clearfix:after { 3 | content: "."; 4 | display: block; 5 | height: 0; 6 | clear: both; 7 | visibility: hidden; 8 | } 9 | .clearfix {display: inline-block;} 10 | /* Hides from IE-mac \*/ 11 | .clearfix {display: block;} 12 | * html .clearfix {height: 1%;} 13 | /* end hide from IE-mac */ 14 | 15 | html * { padding:0; margin:0; } 16 | body * * { padding:0; } 17 | body { font:small sans-serif; background: #E5E5E5; 18 | font-family: "Lucida Grande", "Lucida Sans", verdana, sans-serif; 19 | } 20 | 21 | h1 { font-weight:normal; } 22 | h2 { font-weight: normal; } 23 | h3 { font-weight: normal; } 24 | h4 { font-weight: normal; } 25 | h1 a { color: inherit; text-decoration: none; } 26 | h2 span { font-size:80%; color:#666; font-weight:normal; } 27 | 28 | h1, h2, h3, h4, h5, h6, p, ul, 29 | table, form, pre, ol { margin-bottom: 20px; } 30 | ul { margin: 0 0 20px 20px; } 31 | 32 | a { color: #333; } 33 | a:visited { color: inherit; } 34 | 35 | pre { 36 | display: block; 37 | white-space: pre-wrap; /* CSS-3 */ 38 | white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ 39 | white-space: -pre-wrap; /* Opera 4-6 */ 40 | white-space: -o-pre-wrap; /* Opera 7 */ 41 | word-wrap: break-word; /* Internet Explorer 5.5+ */ 42 | } 43 | 44 | body { 45 | background-color: #fafafc; 46 | } 47 | 48 | input[type=text], input[type=password], select, textarea { 49 | border: 1px solid #cdd2d6; 50 | border-radius: 4px; 51 | -webkit-border-radius: 4px; 52 | -moz-border-radius: 4px; 53 | -webkit-box-shadow: inset 0 1px 3px rgba(0,0,0,.15); 54 | -moz-box-shadow: inset 0 1px 3px rgba(0,0,0,.15); 55 | padding: 2px 4px; 56 | } 57 | 58 | fieldset { 59 | border: 1px solid #ccc; 60 | margin-bottom: 20px; 61 | } 62 | fieldset h2 { 63 | padding: 10px 10px 0; 64 | margin-bottom: 10px; 65 | font-size: 1.2em; 66 | color: #5A7C9D; 67 | } 68 | .help { 69 | color: #999; 70 | } 71 | 72 | .wrapper { 73 | position: relative; 74 | min-width: 900px; 75 | max-width: 1000px; 76 | margin: 0 auto; 77 | } 78 | 79 | #header { 80 | background-color: #ffffff; 81 | padding: 20px; 82 | margin: 0 0 20px; 83 | border-bottom: 2px solid #d5d9db; 84 | } 85 | 86 | #header h1, #header h2 { 87 | font-family: 'Raleway', arial, sans-serif; 88 | color: #084e80; 89 | display: inline; 90 | font-size: 20pt; 91 | font-weight: normal; 92 | } 93 | 94 | #header h1 { 95 | margin: -5px 0 -10px; 96 | padding: 5px 0 5px 60px; 97 | background-repeat: no-repeat; 98 | background-position: top middle; 99 | } 100 | 101 | #header h1 img { 102 | position: absolute; 103 | top: -10px; 104 | left: -15px; 105 | z-index: 999; 106 | height: 50px; 107 | width: 58px; 108 | display: block; 109 | } 110 | 111 | #header h2 { 112 | color: #ccc; 113 | } 114 | 115 | #header h2::before { 116 | content: '\007C'; 117 | font-size: 25px; 118 | color: #f2f2f2; 119 | margin-right: 5px; 120 | } 121 | 122 | #navigation { 123 | padding: 0 15px; 124 | z-index: 100; 125 | } 126 | 127 | #navigation ul { 128 | list-style: none; 129 | margin: 0; 130 | text-align: right; 131 | } 132 | 133 | #navigation li { 134 | display: inline-block; 135 | } 136 | 137 | #navigation .wrapper > ul > li > a, 138 | #navigation .wrapper > ul > li > span { 139 | color: #666; 140 | display: inline-block; 141 | cursor: pointer; 142 | padding: 10px 20px; 143 | text-decoration: none; 144 | border: 1px solid #d5d9db; 145 | background-color: #eeeeee; 146 | border-bottom: none 147 | } 148 | 149 | #navigation li ul { 150 | position: absolute; 151 | z-index: 101; 152 | display: none; 153 | background: #fff; 154 | } 155 | #navigation li:hover ul { 156 | display: block; 157 | } 158 | #navigation li ul li a { 159 | display: block; 160 | padding: 10px 20px; 161 | text-decoration: none; 162 | } 163 | 164 | #navigation li ul li.selected a { 165 | background: #fff !important; 166 | color: #5A7C9D !important; 167 | } 168 | 169 | #navigation li ul li a:hover { 170 | color: #000; 171 | background: #f3f3f3; 172 | } 173 | 174 | #navigation .wrapper > ul > li.selected > a, 175 | #navigation .wrapper > ul > li.selected > span { 176 | background: #fff !important; 177 | color: #5A7C9D !important; 178 | } 179 | #navigation .wrapper > ul > li > a:hover, 180 | #navigation .wrapper > ul > li:hover > span { 181 | color: #000; 182 | background: #f3f3f3; 183 | } 184 | 185 | #container { 186 | padding: 0 10px 50px 10px; 187 | } 188 | #container .wrapper { 189 | margin: 0 auto 20px; 190 | padding: 20px; 191 | background-color: #fff; 192 | border: 1px solid #d5d9db; 193 | } 194 | 195 | #status { 196 | position: absolute; 197 | right: 20px; 198 | top: 28px; 199 | background: #437ba3 url('../img/loading.gif') 8px center no-repeat; 200 | color: #fff; 201 | width: 25px; 202 | padding: 2px 3px; 203 | -webkit-border-radius: 4px; 204 | -moz-border-radius: 4px; 205 | border-radius: 4px; 206 | text-indent: -10000em; 207 | display: none; 208 | } 209 | 210 | #footer { 211 | background-color: #ffffff; 212 | border-top: 1px solid #d5d9db; 213 | padding: 20px 0; 214 | position: fixed; 215 | bottom: 0; 216 | width: 100%; 217 | } 218 | 219 | #facebox h2 { 220 | font-size: 100%; 221 | font-weight: bold; 222 | text-align: center; 223 | margin-bottom: 20px; 224 | } 225 | 226 | #facebox table.switchForm { 227 | width: 100%; 228 | border-collapse: collapse; 229 | margin: 0; 230 | } 231 | 232 | #facebox table.switchForm th, #facebox table.switchForm td { 233 | vertical-align: top; 234 | } 235 | 236 | #facebox table.switchForm th { 237 | width: 100px; 238 | font-weight: normal; 239 | text-align: right; 240 | padding-right: 10px; 241 | } 242 | 243 | #facebox table.switchForm td input, 244 | #facebox table.switchForm td textarea { 245 | width: 230px; 246 | padding: 2px 3px; 247 | } 248 | 249 | #facebox table.switchForm td p { 250 | margin-top: 5px; 251 | font-size: 80%; 252 | color: #666; 253 | } 254 | 255 | #facebox table.switchForm td textarea { 256 | height: 30px; 257 | } 258 | 259 | #facebox table.switchForm td.buttons { 260 | text-align: right; 261 | } 262 | 263 | /* 264 | * Styles for customized buttons. 265 | * (taken from Disqus code, author Chris Jennings) 266 | */ 267 | 268 | .button { 269 | display: inline-block; 270 | margin: 0 1px; 271 | font-size: 13px; 272 | border: 1px solid #94afbd; 273 | text-align: center; 274 | -webkit-border-radius: 4px; 275 | -moz-border-radius: 4px; 276 | border-radius: 4px; 277 | color: #5a7c9d; 278 | font-weight: bold; 279 | background: #FFFFFF; 280 | padding: 8px; 281 | font-family: Arial, Helvetica, Calibri, sans-serif; 282 | position: relative; 283 | vertical-align: text-middle; 284 | text-decoration: none; 285 | } 286 | 287 | .button { 288 | cursor: pointer; 289 | } 290 | 291 | .paging li span, 292 | .button.disabled { 293 | color: #C9D5DF; 294 | border-color: #C9D5DF; 295 | } 296 | 297 | .paging li a:hover, 298 | .button:focus, 299 | .button:hover { 300 | border-color: #7b9cad; 301 | color: #436586; 302 | background: #f0f0f0; 303 | } 304 | 305 | .button.delete:hover, 306 | .button.delete:focus, 307 | .button.delete.hover { 308 | border-color: red; 309 | filter: none; 310 | background: #E25F53; 311 | color: #fff; 312 | text-shadow: 0 1px 0 red; 313 | } 314 | 315 | .button img { 316 | position: relative; 317 | display: inline-block; 318 | vertical-align: text-bottom; 319 | } 320 | 321 | .button.small { 322 | padding: 2px 8px; 323 | font-size: 11px; 324 | line-height: 14px; 325 | } 326 | 327 | .button.small:hover { 328 | background-position: right -126px; 329 | } 330 | 331 | .button.xtrasmall { 332 | padding: 1px 4px; 333 | font-size: 11px; 334 | line-height: 14px; 335 | } 336 | 337 | .button.toggled, 338 | .button.toggled:hover, 339 | .button.toggled:focus { 340 | background: #437ba3; 341 | color: #fff; 342 | border-color: #537789; 343 | } 344 | 345 | #container.login-area .wrapper { 346 | min-width: 0; 347 | width: 300px; 348 | } 349 | #container.login-area p { 350 | margin-bottom: 10px; 351 | } 352 | #container.login-area table { width: 295px; } 353 | #container.login-area td { padding: 2px 0; } 354 | #container.login-area form { 355 | margin-bottom: 0; 356 | } 357 | #container.login-area form .submit { 358 | text-align: right; 359 | } 360 | #container.login-area form td textarea, 361 | #container.login-area form td input { 362 | width: 100%; 363 | } 364 | 365 | #account { 366 | position: absolute; 367 | font-size: 0.9em; 368 | right: 0px; 369 | top: 9px; 370 | } 371 | 372 | .nav { 373 | list-style: none; 374 | margin-left: 0; 375 | } 376 | .nav a { color: inherit; } 377 | .nav li { display: inline; } 378 | .nav li a { display: inline-block; } 379 | 380 | /* breadcrumbs */ 381 | 382 | #breadcrumbs { 383 | font-size: 0.9em; 384 | height: 16px; 385 | line-height: 16px; 386 | margin: 0 0 20px; 387 | } 388 | #breadcrumbs li { 389 | list-style: none; 390 | display: inline; 391 | float: left; 392 | } 393 | #breadcrumbs a, 394 | #breadcrumbs span { 395 | font-weight: normal; 396 | text-decoration: none; 397 | } 398 | #breadcrumbs li:after { 399 | content: " > "; 400 | margin-right: 4px; 401 | } 402 | #breadcrumbs li:last-child:after { 403 | content: ""; 404 | } 405 | #breadcrumbs a:hover, 406 | #breadcrumbs a:focus { 407 | text-decoration: underline; 408 | } 409 | #breadcrumbs li:last-child { 410 | background: none; 411 | font-weight: bold; 412 | } 413 | #breadcrumbs li:last-child a, 414 | #breadcrumbs li:last-child span { 415 | background: none; 416 | text-decoration: none; 417 | font-weight: inherit; 418 | padding-right: 0; 419 | margin-right: 0; 420 | } 421 | #breadcrumbs li:last-child a:hover, 422 | #breadcrumbs li:last-child a:focus { 423 | text-decoration: underline; 424 | } 425 | 426 | /* dashboard styles */ 427 | #dashboard { 428 | text-align: center; 429 | } 430 | 431 | .dashboard-module { 432 | display: inline-block; 433 | border-radius: 6px; 434 | padding: 10px; 435 | margin: 5px; 436 | width: 415px; 437 | vertical-align: top; 438 | } 439 | .dashboard-module-title { 440 | font-weight: bold; 441 | color: #084e80; 442 | border-bottom: 1px solid #d5d9db; 443 | padding-bottom: 10px; 444 | margin-bottom: 10px; 445 | } 446 | .dashboard-module-title .go-link { 447 | margin-left: 5px; 448 | color: #ccc; 449 | font-weight: normal; 450 | font-size: 0.9em; 451 | text-decoration: none; 452 | text-transform: lowercase; 453 | } 454 | .dashboard-module-content { 455 | 456 | } 457 | table { 458 | width: 100%; 459 | } 460 | caption { 461 | margin-bottom: 5px; 462 | } 463 | table tbody th { 464 | padding: 3px; 465 | text-align: right; 466 | } 467 | 468 | .capacity-graph { 469 | height: 1.2em; 470 | margin-bottom: 5px; 471 | background-color: #437ba3; 472 | -webkit-box-shadow: 0.1em 0.1em 0.2em #aaa; 473 | -moz-box-shadow: 0.1em 0.1em 0.2em #aaa; 474 | position: relative; 475 | } 476 | 477 | .capacity-graph div { 478 | height: 1.2em; 479 | background-color: #E84A2F; 480 | } 481 | 482 | .capacity-graph strong { 483 | height: 1.2em; 484 | display: block; 485 | vertical-align: middle; 486 | line-height: 1.7em; 487 | position: absolute; 488 | left: 0; 489 | right: 0; 490 | top: 0; 491 | bottom: 0; 492 | color: #fff; 493 | font-size: 9px; 494 | text-align: center; 495 | } 496 | -------------------------------------------------------------------------------- /nexus/media/img/admin/icon_add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/nexus/media/img/admin/icon_add.gif -------------------------------------------------------------------------------- /nexus/media/img/admin/icon_change.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/nexus/media/img/admin/icon_change.gif -------------------------------------------------------------------------------- /nexus/media/img/admin/icon_delete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/nexus/media/img/admin/icon_delete.gif -------------------------------------------------------------------------------- /nexus/media/img/admin/nav-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/nexus/media/img/admin/nav-bg.gif -------------------------------------------------------------------------------- /nexus/media/img/facebox/closelabel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/nexus/media/img/facebox/closelabel.png -------------------------------------------------------------------------------- /nexus/media/img/facebox/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/nexus/media/img/facebox/loading.gif -------------------------------------------------------------------------------- /nexus/media/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/nexus/media/img/favicon.png -------------------------------------------------------------------------------- /nexus/media/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/nexus/media/img/loading.gif -------------------------------------------------------------------------------- /nexus/media/img/nexus_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/nexus/media/img/nexus_logo.png -------------------------------------------------------------------------------- /nexus/media/js/lib/facebox/facebox.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Facebox (for jQuery) 3 | * version: 1.3 4 | * @requires jQuery v1.2 or later 5 | * @homepage https://github.com/defunkt/facebox 6 | * 7 | * Licensed under the MIT: 8 | * http://www.opensource.org/licenses/mit-license.php 9 | * 10 | * Copyright Forever Chris Wanstrath, Kyle Neath 11 | * 12 | * Usage: 13 | * 14 | * jQuery(document).ready(function() { 15 | * jQuery('a[rel*=facebox]').facebox() 16 | * }) 17 | * 18 | * Terms 19 | * Loads the #terms div in the box 20 | * 21 | * Terms 22 | * Loads the terms.html page in the box 23 | * 24 | * Terms 25 | * Loads the terms.png image in the box 26 | * 27 | * 28 | * You can also use it programmatically: 29 | * 30 | * jQuery.facebox('some html') 31 | * jQuery.facebox('some html', 'my-groovy-style') 32 | * 33 | * The above will open a facebox with "some html" as the content. 34 | * 35 | * jQuery.facebox(function($) { 36 | * $.get('blah.html', function(data) { $.facebox(data) }) 37 | * }) 38 | * 39 | * The above will show a loading screen before the passed function is called, 40 | * allowing for a better ajaxy experience. 41 | * 42 | * The facebox function can also display an ajax page, an image, or the contents of a div: 43 | * 44 | * jQuery.facebox({ ajax: 'remote.html' }) 45 | * jQuery.facebox({ ajax: 'remote.html' }, 'my-groovy-style') 46 | * jQuery.facebox({ image: 'stairs.jpg' }) 47 | * jQuery.facebox({ image: 'stairs.jpg' }, 'my-groovy-style') 48 | * jQuery.facebox({ div: '#box' }) 49 | * jQuery.facebox({ div: '#box' }, 'my-groovy-style') 50 | * 51 | * Want to close the facebox? Trigger the 'close.facebox' document event: 52 | * 53 | * jQuery(document).trigger('close.facebox') 54 | * 55 | * Facebox also has a bunch of other hooks: 56 | * 57 | * loading.facebox 58 | * beforeReveal.facebox 59 | * reveal.facebox (aliased as 'afterReveal.facebox') 60 | * init.facebox 61 | * afterClose.facebox 62 | * 63 | * Simply bind a function to any of these hooks: 64 | * 65 | * $(document).bind('reveal.facebox', function() { ...stuff to do after the facebox and contents are revealed... }) 66 | * 67 | */ 68 | (function($) { 69 | $.facebox = function(data, klass) { 70 | $.facebox.loading(data.settings || []) 71 | 72 | if (data.ajax) fillFaceboxFromAjax(data.ajax, klass) 73 | else if (data.image) fillFaceboxFromImage(data.image, klass) 74 | else if (data.div) fillFaceboxFromHref(data.div, klass) 75 | else if ($.isFunction(data)) data.call($) 76 | else $.facebox.reveal(data, klass) 77 | } 78 | 79 | /* 80 | * Public, $.facebox methods 81 | */ 82 | 83 | $.extend($.facebox, { 84 | settings: { 85 | opacity : 0.2, 86 | overlay : true, 87 | loadingImage : '/facebox/loading.gif', 88 | closeImage : '/facebox/closelabel.png', 89 | imageTypes : [ 'png', 'jpg', 'jpeg', 'gif' ], 90 | faceboxHtml : '\ 91 | ' 98 | }, 99 | 100 | loading: function() { 101 | init() 102 | if ($('#facebox .loading').length == 1) return true 103 | showOverlay() 104 | 105 | $('#facebox .content').empty(). 106 | append('
') 107 | 108 | $('#facebox').show().css({ 109 | top: getPageScroll()[1] + (getPageHeight() / 10), 110 | left: $(window).width() / 2 - ($('#facebox .popup').outerWidth() / 2) 111 | }) 112 | 113 | $(document).bind('keydown.facebox', function(e) { 114 | if (e.keyCode == 27) $.facebox.close() 115 | return true 116 | }) 117 | $(document).trigger('loading.facebox') 118 | }, 119 | 120 | reveal: function(data, klass) { 121 | $(document).trigger('beforeReveal.facebox') 122 | if (klass) $('#facebox .content').addClass(klass) 123 | $('#facebox .content').empty().append(data) 124 | $('#facebox .popup').children().fadeIn('normal') 125 | $('#facebox').css('left', $(window).width() / 2 - ($('#facebox .popup').outerWidth() / 2)) 126 | $(document).trigger('reveal.facebox').trigger('afterReveal.facebox') 127 | }, 128 | 129 | close: function() { 130 | $(document).trigger('close.facebox') 131 | return false 132 | } 133 | }) 134 | 135 | /* 136 | * Public, $.fn methods 137 | */ 138 | 139 | $.fn.facebox = function(settings) { 140 | if ($(this).length == 0) return 141 | 142 | init(settings) 143 | 144 | function clickHandler() { 145 | $.facebox.loading(true) 146 | 147 | // support for rel="facebox.inline_popup" syntax, to add a class 148 | // also supports deprecated "facebox[.inline_popup]" syntax 149 | var klass = this.rel.match(/facebox\[?\.(\w+)\]?/) 150 | if (klass) klass = klass[1] 151 | 152 | fillFaceboxFromHref(this.href, klass) 153 | return false 154 | } 155 | 156 | return this.bind('click.facebox', clickHandler) 157 | } 158 | 159 | /* 160 | * Private methods 161 | */ 162 | 163 | // called one time to setup facebox on this page 164 | function init(settings) { 165 | if ($.facebox.settings.inited) return true 166 | else $.facebox.settings.inited = true 167 | 168 | $(document).trigger('init.facebox') 169 | makeCompatible() 170 | 171 | var imageTypes = $.facebox.settings.imageTypes.join('|') 172 | $.facebox.settings.imageTypesRegexp = new RegExp('\\.(' + imageTypes + ')(\\?.*)?$', 'i') 173 | 174 | if (settings) $.extend($.facebox.settings, settings) 175 | $('body').append($.facebox.settings.faceboxHtml) 176 | 177 | var preload = [ new Image(), new Image() ] 178 | preload[0].src = $.facebox.settings.closeImage 179 | preload[1].src = $.facebox.settings.loadingImage 180 | 181 | $('#facebox').find('.b:first, .bl').each(function() { 182 | preload.push(new Image()) 183 | preload.slice(-1).src = $(this).css('background-image').replace(/url\((.+)\)/, '$1') 184 | }) 185 | 186 | $('#facebox .close') 187 | .click($.facebox.close) 188 | .append('') 191 | } 192 | 193 | // getPageScroll() by quirksmode.com 194 | function getPageScroll() { 195 | var xScroll, yScroll; 196 | if (self.pageYOffset) { 197 | yScroll = self.pageYOffset; 198 | xScroll = self.pageXOffset; 199 | } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict 200 | yScroll = document.documentElement.scrollTop; 201 | xScroll = document.documentElement.scrollLeft; 202 | } else if (document.body) {// all other Explorers 203 | yScroll = document.body.scrollTop; 204 | xScroll = document.body.scrollLeft; 205 | } 206 | return new Array(xScroll,yScroll) 207 | } 208 | 209 | // Adapted from getPageSize() by quirksmode.com 210 | function getPageHeight() { 211 | var windowHeight 212 | if (self.innerHeight) { // all except Explorer 213 | windowHeight = self.innerHeight; 214 | } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode 215 | windowHeight = document.documentElement.clientHeight; 216 | } else if (document.body) { // other Explorers 217 | windowHeight = document.body.clientHeight; 218 | } 219 | return windowHeight 220 | } 221 | 222 | // Backwards compatibility 223 | function makeCompatible() { 224 | var $s = $.facebox.settings 225 | 226 | $s.loadingImage = $s.loading_image || $s.loadingImage 227 | $s.closeImage = $s.close_image || $s.closeImage 228 | $s.imageTypes = $s.image_types || $s.imageTypes 229 | $s.faceboxHtml = $s.facebox_html || $s.faceboxHtml 230 | } 231 | 232 | // Figures out what you want to display and displays it 233 | // formats are: 234 | // div: #id 235 | // image: blah.extension 236 | // ajax: anything else 237 | function fillFaceboxFromHref(href, klass) { 238 | // div 239 | if (href.match(/#/)) { 240 | var url = window.location.href.split('#')[0] 241 | var target = href.replace(url,'') 242 | if (target == '#') return 243 | $.facebox.reveal($(target).html(), klass) 244 | 245 | // image 246 | } else if (href.match($.facebox.settings.imageTypesRegexp)) { 247 | fillFaceboxFromImage(href, klass) 248 | // ajax 249 | } else { 250 | fillFaceboxFromAjax(href, klass) 251 | } 252 | } 253 | 254 | function fillFaceboxFromImage(href, klass) { 255 | var image = new Image() 256 | image.onload = function() { 257 | $.facebox.reveal('
', klass) 258 | } 259 | image.src = href 260 | } 261 | 262 | function fillFaceboxFromAjax(href, klass) { 263 | $.facebox.jqxhr = $.get(href, function(data) { $.facebox.reveal(data, klass) }) 264 | } 265 | 266 | function skipOverlay() { 267 | return $.facebox.settings.overlay == false || $.facebox.settings.opacity === null 268 | } 269 | 270 | function showOverlay() { 271 | if (skipOverlay()) return 272 | 273 | if ($('#facebox_overlay').length == 0) 274 | $("body").append('
') 275 | 276 | $('#facebox_overlay').hide().addClass("facebox_overlayBG") 277 | .css('opacity', $.facebox.settings.opacity) 278 | .click(function() { $(document).trigger('close.facebox') }) 279 | .fadeIn(200) 280 | return false 281 | } 282 | 283 | function hideOverlay() { 284 | if (skipOverlay()) return 285 | 286 | $('#facebox_overlay').fadeOut(200, function(){ 287 | $("#facebox_overlay").removeClass("facebox_overlayBG") 288 | $("#facebox_overlay").addClass("facebox_hide") 289 | $("#facebox_overlay").remove() 290 | }) 291 | 292 | return false 293 | } 294 | 295 | /* 296 | * Bindings 297 | */ 298 | 299 | $(document).bind('close.facebox', function() { 300 | if ($.facebox.jqxhr) { 301 | $.facebox.jqxhr.abort() 302 | $.facebox.jqxhr = null 303 | } 304 | $(document).unbind('keydown.facebox') 305 | $('#facebox').fadeOut(function() { 306 | $('#facebox .content').removeClass().addClass('content') 307 | $('#facebox .loading').remove() 308 | $(document).trigger('afterClose.facebox') 309 | }) 310 | hideOverlay() 311 | }) 312 | 313 | })(jQuery); 314 | -------------------------------------------------------------------------------- /nexus/media/js/lib/jquery.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v1.6.4 http://jquery.com/ | http://jquery.org/license */ 2 | (function(a,b){function cu(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cr(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cq(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cp(){cn=b}function co(){setTimeout(cp,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bv(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bl(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bd,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bk(a){f.nodeName(a,"input")?bj(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bj)}function bj(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bi(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bh(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bg(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function U(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function M(a,b){return(a&&a!=="*"?a+".":"")+b.replace(y,"`").replace(z,"&")}function L(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function J(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function D(){return!0}function C(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function K(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(K,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z]|[0-9])/ig,x=/^-ms-/,y=function(a,b){return(b+"").toUpperCase()},z=d.userAgent,A,B,C,D=Object.prototype.toString,E=Object.prototype.hasOwnProperty,F=Array.prototype.push,G=Array.prototype.slice,H=String.prototype.trim,I=Array.prototype.indexOf,J={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.4",length:0,size:function(){return this.length},toArray:function(){return G.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?F.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),B.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(G.apply(this,arguments),"slice",G.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:F,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;B.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!B){B=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",C,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",C),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&K()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):J[D.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!E.call(a,"constructor")&&!E.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||E.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(x,"ms-").replace(w,y)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},m&&f.extend(p,{position:"absolute",left:"-1000px",top:"-1000px"});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i=f.expando,j=typeof c=="string",k=a.nodeType,l=k?f.cache:a,m=k?a[f.expando]:a[f.expando]&&f.expando;if((!m||e&&m&&l[m]&&!l[m][i])&&j&&d===b)return;m||(k?a[f.expando]=m=++f.uuid:m=f.expando),l[m]||(l[m]={},k||(l[m].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?l[m][i]=f.extend(l[m][i],c):l[m]=f.extend(l[m],c);g=l[m],e&&(g[i]||(g[i]={}),g=g[i]),d!==b&&(g[f.camelCase(c)]=d);if(c==="events"&&!g[c])return g[i]&&g[i].events;j?(h=g[c],h==null&&(h=g[f.camelCase(c)])):h=g;return h}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e=f.expando,g=a.nodeType,h=g?f.cache:a,i=g?a[f.expando]:f.expando;if(!h[i])return;if(b){d=c?h[i][e]:h[i];if(d){d[b]||(b=f.camelCase(b)),delete d[b];if(!l(d))return}}if(c){delete h[i][e];if(!l(h[i]))return}var j=h[i][e];f.support.deleteExpando||!h.setInterval?delete h[i]:h[i]=null,j?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=j):g&&(f.support.deleteExpando?delete a[f.expando]:a.removeAttribute?a.removeAttribute(f.expando):a[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=v:u&&(i=u)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.attr(a,b,""),a.removeAttribute(b),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(u&&f.nodeName(a,"button"))return u.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(u&&f.nodeName(a,"button"))return u.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==null?g:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabIndex=f.propHooks.tabIndex,v={get:function(a,c){var d;return f.prop(a,c)===!0||(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(u=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var w=/\.(.*)$/,x=/^(?:textarea|input|select)$/i,y=/\./g,z=/ /g,A=/[^\w\s.|`]/g,B=function(a){return a.replace(A,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=C;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=C);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),B).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},I=function(c){var d=c.target,e,g;if(!!x.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=H(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:I,beforedeactivate:I,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&I.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&I.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",H(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in G)f.event.add(this,c+".specialChange",G[c]);return x.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return x.test(this.nodeName)}},G=f.event.special.change.filters,G.focus=G.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=S.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(U(c[0])||U(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=R.call(arguments);N.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!T[a]?f.unique(e):e,(this.length>1||P.test(d))&&O.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};be.optgroup=be.option,be.tbody=be.tfoot=be.colgroup=be.caption=be.thead,be.th=be.td,f.support.htmlSerialize||(be._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!be[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bh(a,d),e=bi(a),g=bi(d);for(h=0;e[h];++h)g[h]&&bh(e[h],g[h])}if(b){bg(a,d);if(c){e=bi(a),g=bi(d);for(h=0;e[h];++h)bg(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=be[l]||be._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bn.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bm,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bm.test(g)?g.replace(bm,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bv(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bw=function(a,c){var d,e,g;c=c.replace(bo,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bx=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bp.test(d)&&bq.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bv=bw||bx,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bz=/%20/g,bA=/\[\]$/,bB=/\r?\n/g,bC=/#.*$/,bD=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bE=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bF=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bG=/^(?:GET|HEAD)$/,bH=/^\/\//,bI=/\?/,bJ=/)<[^<]*)*<\/script>/gi,bK=/^(?:select|textarea)/i,bL=/\s+/,bM=/([?&])_=[^&]*/,bN=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bO=f.fn.load,bP={},bQ={},bR,bS,bT=["*/"]+["*"];try{bR=e.href}catch(bU){bR=c.createElement("a"),bR.href="",bR=bR.href}bS=bN.exec(bR.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bO)return bO.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bJ,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bK.test(this.nodeName)||bE.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bB,"\r\n")}}):{name:b.name,value:c.replace(bB,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?bX(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),bX(a,b);return a},ajaxSettings:{url:bR,isLocal:bF.test(bS[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bT},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bV(bP),ajaxTransport:bV(bQ),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?bZ(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=b$(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bD.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bC,"").replace(bH,bS[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bL),d.crossDomain==null&&(r=bN.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bS[1]&&r[2]==bS[2]&&(r[3]||(r[1]==="http:"?80:443))==(bS[3]||(bS[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bW(bP,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bG.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bI.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bM,"$1_="+x);d.url=y+(y===d.url?(bI.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bT+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bW(bQ,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){s<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bz,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cq("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=ct.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!ct.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cu(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cu(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNaN(j)?i:j}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); 5 | -------------------------------------------------------------------------------- /nexus/media/js/nexus.js: -------------------------------------------------------------------------------- 1 | // AJAX CSRF setup. Source: https://docs.djangoproject.com/en/1.10/ref/csrf/#ajax 2 | 3 | jQuery.ajaxSetup({ 4 | beforeSend: function(xhr, settings) { 5 | function sameOrigin(url) { 6 | // url could be relative or scheme relative or absolute 7 | var host = document.location.host; // host + port 8 | var protocol = document.location.protocol; 9 | var sr_origin = '//' + host; 10 | var origin = protocol + sr_origin; 11 | // Allow absolute or scheme relative URLs to same origin 12 | return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || 13 | (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || 14 | // or any other URL that isn't scheme relative or absolute i.e relative. 15 | !(/^(\/\/|http:|https:).*/.test(url)); 16 | } 17 | function safeMethod(method) { 18 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 19 | } 20 | 21 | if (!safeMethod(settings.type) && sameOrigin(settings.url)) { 22 | var csrfToken = $('#nexus-constants').data('csrfToken'); 23 | xhr.setRequestHeader("X-CSRFToken", csrfToken); 24 | } 25 | } 26 | }); 27 | 28 | /* Facebox setup as per instructions at https://github.com/defunkt/facebox */ 29 | 30 | (function () { 31 | 32 | var nexusMediaPrefix = $('#nexus-constants').data('nexusMediaPrefix'), 33 | loadingImage = nexusMediaPrefix + '/nexus/img/facebox/loading.gif', 34 | closeImage = nexusMediaPrefix + '/nexus/img/facebox/closelabel.png'; 35 | 36 | $.facebox.settings.closeImage = closeImage; 37 | $.facebox.settings.loadingImage = loadingImage; 38 | 39 | })(); 40 | -------------------------------------------------------------------------------- /nexus/modules.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import logging 3 | import os 4 | import threading 5 | 6 | from nexus.compat import reverse 7 | 8 | 9 | class NexusModule(object): 10 | # base url (pattern name) to show in navigation 11 | home_url = None 12 | 13 | # generic permission required 14 | permission = None 15 | 16 | media_root = None 17 | 18 | logger_name = None 19 | 20 | # list of active sites within process 21 | _globals = {} 22 | 23 | def __init__(self, site, category=None, name=None, app_name=None): 24 | self.category = category 25 | self.site = site 26 | self.name = name 27 | self.app_name = app_name 28 | 29 | # Set up default logging for this module 30 | if not self.logger_name: 31 | self.logger_name = 'nexus.%s' % (self.name) 32 | self.logger = logging.getLogger(self.logger_name) 33 | 34 | if not self.media_root: 35 | mod = __import__(self.__class__.__module__) 36 | self.media_root = os.path.normpath(os.path.join(os.path.dirname(mod.__file__), 'media')) 37 | 38 | def __getattribute__(self, name): 39 | NexusModule.set_global('site', object.__getattribute__(self, 'site')) 40 | return object.__getattribute__(self, name) 41 | 42 | @classmethod 43 | def set_global(cls, key, value): 44 | ident = threading.get_ident() 45 | if ident not in cls._globals: 46 | cls._globals[ident] = {} 47 | cls._globals[ident][key] = value 48 | 49 | @classmethod 50 | def get_global(cls, key): 51 | return cls._globals.get(threading.get_ident(), {}).get(key) 52 | 53 | def render_to_string(self, template, context={}, request=None): 54 | context.update(self.get_context(request)) 55 | return self.site.render_to_string(template, context, request, current_app=self.name) 56 | 57 | def render_to_response(self, template, context={}, request=None): 58 | context.update(self.get_context(request)) 59 | return self.site.render_to_response(template, context, request, current_app=self.name) 60 | 61 | def as_view(self, *args, **kwargs): 62 | if 'extra_permission' not in kwargs: 63 | kwargs['extra_permission'] = self.permission 64 | return self.site.as_view(*args, **kwargs) 65 | 66 | def get_context(self, request): 67 | title = self.get_title() 68 | return { 69 | 'title': title, 70 | 'module_title': title, 71 | 'trail_bits': self.get_trail(request), 72 | } 73 | 74 | def get_namespace(self): 75 | return hashlib.md5(self.__class__.__module__ + '.' + self.__class__.__name__).hexdigest() 76 | 77 | def get_title(self): 78 | return self.__class__.__name__ 79 | 80 | def get_dashboard_title(self): 81 | return self.get_title() 82 | 83 | def get_urls(self): 84 | return [] 85 | 86 | @property 87 | def urls(self): 88 | if self.app_name and self.name: 89 | return self.get_urls(), self.app_name, self.name 90 | return self.get_urls() 91 | 92 | def get_trail(self, request): 93 | return [ 94 | (self.get_title(), self.get_home_url(request)), 95 | ] 96 | 97 | def get_home_url(self, request): 98 | if self.home_url: 99 | if self.app_name: 100 | home_url_name = '%s:%s' % (self.app_name, self.home_url) 101 | else: 102 | home_url_name = self.home_url 103 | 104 | home_url = reverse(home_url_name, current_app=self.name) 105 | else: 106 | home_url = None 107 | 108 | return home_url 109 | -------------------------------------------------------------------------------- /nexus/sites.py: -------------------------------------------------------------------------------- 1 | # Core site concept heavily inspired by django.contrib.sites 2 | import mimetypes 3 | import os 4 | import posixpath 5 | import stat 6 | import urllib 7 | from collections import OrderedDict 8 | from functools import update_wrapper, wraps 9 | 10 | from django.conf.urls import url 11 | from django.contrib.auth import REDIRECT_FIELD_NAME 12 | from django.core.exceptions import PermissionDenied 13 | from django.http import Http404, HttpResponse, HttpResponseNotModified, HttpResponseRedirect 14 | from django.shortcuts import render 15 | from django.template import context_processors 16 | from django.template.loader import render_to_string 17 | from django.utils.http import http_date 18 | from django.views.decorators.cache import never_cache 19 | from django.views.decorators.csrf import csrf_protect, ensure_csrf_cookie 20 | from django.views.static import was_modified_since 21 | 22 | from nexus.compat import reverse 23 | from nexus.conf import nexus_settings 24 | 25 | NEXUS_ROOT = os.path.normpath(os.path.dirname(__file__)) 26 | 27 | 28 | class NexusSite(object): 29 | def __init__(self, name=None, app_name='nexus'): 30 | self._registry = {} 31 | self._categories = OrderedDict() 32 | if name is None: 33 | self.name = 'nexus' 34 | else: 35 | self.name = name 36 | self.app_name = app_name 37 | 38 | def register_category(self, category, label, index=None): 39 | if index: 40 | self._categories.insert(index, category, label) 41 | else: 42 | self._categories[category] = label 43 | 44 | def register(self, module, namespace=None, category=None): 45 | module = module(self, category) 46 | if not namespace: 47 | namespace = module.get_namespace() 48 | if namespace: 49 | module.app_name = module.name = namespace 50 | self._registry[namespace] = (module, category) 51 | return module 52 | 53 | def unregister(self, namespace): 54 | if namespace in self._registry: 55 | del self._registry[namespace] 56 | 57 | def get_urls(self): 58 | base_urls = ( 59 | [ 60 | url(r'^media/(?P[^/]+)/(?P.+)$', self.media, name='media'), 61 | url(r'^$', self.as_view(self.dashboard), name='index'), 62 | ], 63 | self.app_name, 64 | self.name, 65 | ) 66 | 67 | urlpatterns = [ 68 | url(r'^', base_urls), 69 | ] 70 | for namespace, module in self.get_modules(): 71 | urlpatterns += [ 72 | url(r'^%s/' % namespace, module.urls), 73 | ] 74 | 75 | return urlpatterns 76 | 77 | @property 78 | def urls(self): 79 | return self.get_urls() 80 | 81 | def has_permission(self, request, extra_permission=None): 82 | """ 83 | Returns True if the given HttpRequest has permission to view 84 | *at least one* page in the admin site. 85 | """ 86 | permission = request.user.is_active and request.user.is_staff 87 | if extra_permission: 88 | permission = permission and request.user.has_perm(extra_permission) 89 | return permission 90 | 91 | def as_view(self, view, cacheable=False, extra_permission=None): 92 | """ 93 | Wraps a view in authentication/caching logic 94 | 95 | extra_permission can be used to require an extra permission for this view, such as a module permission 96 | """ 97 | @wraps(view) 98 | def inner(request, *args, **kwargs): 99 | if not request.user.is_authenticated: 100 | return self.login(request) 101 | elif not self.has_permission(request, extra_permission): 102 | raise PermissionDenied() 103 | return view(request, *args, **kwargs) 104 | 105 | # Mark it as never_cache 106 | if not cacheable: 107 | inner = never_cache(inner) 108 | 109 | # We add csrf_protect here so this function can be used as a utility 110 | # function for any view, without having to repeat 'csrf_protect'. 111 | if not getattr(view, 'csrf_exempt', False): 112 | inner = csrf_protect(inner) 113 | 114 | inner = ensure_csrf_cookie(inner) 115 | 116 | return update_wrapper(inner, view) 117 | 118 | def get_context(self, request): 119 | context = context_processors.csrf(request) 120 | context.update({ 121 | 'request': request, 122 | 'nexus_site': self, 123 | 'nexus_media_prefix': nexus_settings.MEDIA_PREFIX.rstrip('/'), 124 | }) 125 | return context 126 | 127 | def get_modules(self): 128 | for k, v in self._registry.items(): 129 | yield k, v[0] 130 | 131 | def get_module(self, module): 132 | return self._registry[module][0] 133 | 134 | def get_categories(self): 135 | for k, v in self._categories.items(): 136 | yield k, v 137 | 138 | def get_category_label(self, category): 139 | return self._categories.get(category, category.title().replace('_', ' ')) 140 | 141 | def render_to_string(self, template, context, request, current_app=None): 142 | if not current_app: 143 | current_app = self.name 144 | else: 145 | current_app = '%s:%s' % (self.name, current_app) 146 | 147 | if request: 148 | request.current_app = current_app 149 | 150 | context.update(self.get_context(request)) 151 | 152 | return render_to_string(template, context=context, request=request) 153 | 154 | def render_to_response(self, template, context, request, current_app=None): 155 | "Shortcut for rendering to response and default context instances" 156 | if not current_app: 157 | current_app = self.name 158 | else: 159 | current_app = '%s:%s' % (self.name, current_app) 160 | 161 | if request: 162 | request.current_app = current_app 163 | 164 | context.update(self.get_context(request)) 165 | 166 | return render(request, template, context=context) 167 | 168 | # Our views 169 | 170 | def media(self, request, module, path): 171 | """ 172 | Serve static files below a given point in the directory structure. 173 | """ 174 | if module == 'nexus': 175 | document_root = os.path.join(NEXUS_ROOT, 'media') 176 | else: 177 | document_root = self.get_module(module).media_root 178 | 179 | path = posixpath.normpath(urllib.parse.unquote(path)) 180 | path = path.lstrip('/') 181 | newpath = '' 182 | for part in path.split('/'): 183 | if not part: 184 | # Strip empty path components. 185 | continue 186 | drive, part = os.path.splitdrive(part) 187 | head, part = os.path.split(part) 188 | if part in (os.curdir, os.pardir): 189 | # Strip '.' and '..' in path. 190 | continue 191 | newpath = os.path.join(newpath, part).replace('\\', '/') 192 | if newpath and path != newpath: 193 | return HttpResponseRedirect(newpath) 194 | fullpath = os.path.join(document_root, newpath) 195 | if os.path.isdir(fullpath): 196 | raise Http404("Directory indexes are not allowed here.") 197 | if not os.path.exists(fullpath): 198 | raise Http404('"%s" does not exist' % fullpath) 199 | # Respect the If-Modified-Since header. 200 | statobj = os.stat(fullpath) 201 | mimetype = mimetypes.guess_type(fullpath)[0] or 'application/octet-stream' 202 | if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), 203 | statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): 204 | return HttpResponseNotModified(content_type=mimetype) 205 | contents = open(fullpath, 'rb').read() 206 | response = HttpResponse(contents, content_type=mimetype) 207 | response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) 208 | response["Content-Length"] = len(contents) 209 | return response 210 | 211 | @never_cache 212 | def login(self, request, form_class=None): 213 | "Login form" 214 | if request.user.is_authenticated: 215 | return HttpResponseRedirect(reverse('nexus:index', current_app=self.name)) 216 | 217 | return HttpResponseRedirect( 218 | '{login}?{get}'.format( 219 | login=reverse('admin:login'), 220 | get=urllib.parse.urlencode({REDIRECT_FIELD_NAME: request.path}), 221 | ), 222 | ) 223 | 224 | def dashboard(self, request): 225 | "Basic dashboard panel" 226 | module_set = [] 227 | for namespace, module in self.get_modules(): 228 | home_url = module.get_home_url(request) 229 | 230 | if hasattr(module, 'render_on_dashboard'): 231 | # Show by default, unless a permission is required 232 | if not module.permission or request.user.has_perm(module.permission): 233 | module_set.append((module.get_dashboard_title(), module.render_on_dashboard(request), home_url)) 234 | 235 | return self.render_to_response('nexus/dashboard.html', { 236 | 'module_set': module_set, 237 | }, request) 238 | 239 | 240 | # setup the default site 241 | site = NexusSite() 242 | -------------------------------------------------------------------------------- /nexus/templates/nexus/base.html: -------------------------------------------------------------------------------- 1 | {% load nexus_helpers %} 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}{% if title %}{{ title }} | {% endif %}Nexus{% endblock %} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | {% block head %} 27 | {% endblock %} 28 | 29 | 30 | 31 | 47 | 48 | {% block navigation %} 49 | {% show_navigation %} 50 | {% endblock %} 51 | 52 | {% block container %} 53 |
54 |
55 |
Saving...
56 | {% if messages %} 57 |
    58 | {% for message in messages %} 59 |
  • {{ message }}
  • 60 | {% endfor %} 61 |
62 | {% endif %} 63 | {% block breadcrumbs %} 64 | 72 | {% endblock %} 73 |
74 | {% block base_content %} 75 | {% block pretitle %}{% endblock %} 76 | {% block content %} 77 | {% block object-tools %}{% endblock %} 78 | {{ content }} 79 | {% endblock %} 80 | {% block sidebar %}{% endblock %} 81 |
82 | {% endblock %} 83 |
84 |
85 |
86 | {% endblock %} 87 | 88 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /nexus/templates/nexus/dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends "nexus/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block heading %}{% trans "Dashboard" %}{% endblock %} 6 | 7 | {% block breadcrumbs %}{% endblock %} 8 | 9 | {% block content %} 10 |
11 | {% for title, content, link in module_set %} 12 |
13 |
14 | {{ title }} 15 | {% if link %} 16 | More › 17 | {% endif %} 18 |
19 |
20 |
21 | {{ content }} 22 |
23 |
24 |
25 | {% empty %} 26 |

We did not detect any Nexus dashboard modules.

27 | {% endfor %} 28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /nexus/templates/nexus/module.html: -------------------------------------------------------------------------------- 1 | {% extends "nexus/base.html" %} 2 | 3 | {% block content %} 4 | {% if link_set %} 5 |
    6 | {% for title, link in link_set %} 7 |
  • {{ title }}
  • 8 | {% endfor %} 9 |
10 | {% endif %} 11 | {% for title, content in module_set %} 12 |
13 |
14 | {{ title }} 15 |
16 |
17 | {{ content }} 18 |
19 |
20 | {% endfor %} 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /nexus/templates/nexus/navigation.html: -------------------------------------------------------------------------------- 1 | {% load nexus_helpers %} 2 | 30 | -------------------------------------------------------------------------------- /nexus/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/nexus/templatetags/__init__.py -------------------------------------------------------------------------------- /nexus/templatetags/nexus_helpers.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from django import template 4 | 5 | import nexus 6 | from nexus.conf import nexus_settings 7 | from nexus.modules import NexusModule 8 | 9 | register = template.Library() 10 | 11 | 12 | @register.simple_tag 13 | def nexus_media_prefix(): 14 | return nexus_settings.MEDIA_PREFIX.rstrip('/') 15 | 16 | 17 | @register.simple_tag 18 | def nexus_version(): 19 | return nexus.__version__ 20 | 21 | 22 | @register.inclusion_tag('nexus/navigation.html', takes_context=True) 23 | def show_navigation(context): 24 | site = context.get('nexus_site', NexusModule.get_global('site')) 25 | request = context['request'] 26 | 27 | category_link_set = OrderedDict([(k, { 28 | 'label': v, 29 | 'links': [], 30 | }) for k, v in site.get_categories()]) 31 | 32 | for namespace, module in site._registry.items(): 33 | module, category = module 34 | 35 | if module.permission and not request.user.has_perm(module.permission): 36 | continue 37 | 38 | home_url = module.get_home_url(context['request']) 39 | 40 | if not home_url: 41 | continue 42 | 43 | active = request.path.startswith(home_url) 44 | 45 | if category not in category_link_set: 46 | if category: 47 | label = site.get_category_label(category) 48 | else: 49 | label = None 50 | category_link_set[category] = { 51 | 'label': label, 52 | 'links': [], 53 | } 54 | 55 | category_link_set[category]['links'].append((module.get_title(), home_url, active)) 56 | category_link_set[category]['active'] = active 57 | 58 | return { 59 | 'nexus_site': site, 60 | 'category_link_set': list(category_link_set.values()), 61 | } 62 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=nexus 3 | --cov-report term-missing 4 | --cov-fail-under 90 5 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | beautifulsoup4 2 | docutils 3 | flake8 4 | flake8-commas 5 | isort 6 | multilint 7 | Pygments 8 | pytest-cov 9 | pytest-django 10 | pytest 11 | pytz 12 | sqlparse 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # __main__.py compile -r -U 6 | # 7 | atomicwrites==1.3.0 # via pytest 8 | attrs==19.1.0 # via pytest 9 | beautifulsoup4==4.7.1 10 | coverage==4.5.3 # via pytest-cov 11 | docutils==0.14 12 | entrypoints==0.3 # via flake8 13 | flake8-commas==2.0.0 14 | flake8==3.7.7 15 | isort==4.3.18 16 | mccabe==0.6.1 # via flake8 17 | more-itertools==7.0.0 # via pytest 18 | multilint==2.4.0 19 | pluggy==0.11.0 # via pytest 20 | py==1.8.0 # via pytest 21 | pycodestyle==2.5.0 # via flake8 22 | pyflakes==2.1.1 # via flake8 23 | pygments==2.4.0 24 | pytest-cov==2.7.1 25 | pytest-django==3.4.8 26 | pytest==4.4.1 27 | pytz==2019.1 28 | six==1.12.0 # via multilint, pytest 29 | soupsieve==1.9.1 # via beautifulsoup4 30 | sqlparse==0.3.0 31 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | import pytest 6 | 7 | 8 | def main(): 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.test') 10 | sys.path.insert(0, 'tests') 11 | return pytest.main() 12 | 13 | 14 | if __name__ == '__main__': 15 | sys.exit(main()) 16 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/screenshot.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | 4 | [isort] 5 | include_trailing_comma = True 6 | known_first_party = nexus,testapp 7 | known_third_party = django 8 | line_length = 120 9 | multi_line_output = 5 10 | not_skip = __init__.py 11 | 12 | [metadata] 13 | license_file = LICENSE 14 | 15 | [tool:multilint] 16 | paths = nexus 17 | runtests.py 18 | setup.py 19 | tests 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | from setuptools import find_packages, setup 5 | 6 | 7 | def get_version(filename): 8 | with open(filename, 'r') as fp: 9 | contents = fp.read() 10 | return re.search(r"__version__ = ['\"]([^'\"]+)['\"]", contents).group(1) 11 | 12 | 13 | version = get_version(os.path.join('nexus', '__init__.py')) 14 | 15 | with open('README.rst', 'r') as readme_file: 16 | readme = readme_file.read() 17 | 18 | with open('HISTORY.rst', 'r') as history_file: 19 | history = history_file.read().replace('.. :changelog:', '') 20 | 21 | setup( 22 | name='nexus-yplan', 23 | version=version, 24 | author='Disqus', 25 | author_email='opensource@disqus.com', 26 | maintainer='Adam Johnson', 27 | maintainer_email='me@adamj.eu', 28 | url='https://github.com/adamchainz/nexus', 29 | description=( 30 | 'Nexus is a pluggable admin application in Django. ' 31 | 'It\'s designed to give you a simple design and architecture for building admin applications.' 32 | ), 33 | long_description=readme + '\n\n' + history, 34 | packages=find_packages(exclude=['tests', 'tests.*']), 35 | zip_safe=False, 36 | install_requires=[ 37 | 'Django>=1.11', 38 | ], 39 | python_requires='>=3.4', 40 | license='Apache License 2.0', 41 | include_package_data=True, 42 | classifiers=[ 43 | 'Development Status :: 7 - Inactive', 44 | 'Framework :: Django', 45 | 'Framework :: Django :: 1.11', 46 | 'Framework :: Django :: 2.0', 47 | 'Framework :: Django :: 2.1', 48 | 'Framework :: Django :: 2.2', 49 | 'Intended Audience :: Developers', 50 | 'Intended Audience :: System Administrators', 51 | 'License :: OSI Approved :: Apache Software License', 52 | 'Natural Language :: English', 53 | 'Operating System :: OS Independent', 54 | 'Programming Language :: Python :: 3 :: Only', 55 | 'Programming Language :: Python :: 3', 56 | 'Programming Language :: Python :: 3.4', 57 | 'Programming Language :: Python :: 3.5', 58 | 'Programming Language :: Python :: 3.6', 59 | 'Topic :: Software Development', 60 | ], 61 | ) 62 | -------------------------------------------------------------------------------- /tests/README.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Tests 3 | ===== 4 | 5 | This directory is a Django project, with ``testapp`` is an example app that 6 | contains the tests and support code. 7 | 8 | You can run the tests with the ``runtests.py`` script in the root of the repo. 9 | 10 | You can also run Nexus locally to check changes in the browser with a simple 11 | SQLite database with: 12 | 13 | .. code-block:: bash 14 | 15 | ./manage.py runserver 16 | 17 | This automatically adds the username 'admin' with password 'password' so you 18 | can login locally at ``/admin/`` and peruse nexus at ``/nexus/``. 19 | 20 | You can also manipulate ``PYTHONPATH`` to add other modules you might be 21 | testing, e.g. if you have a ``gargoyle`` checkout in the same folder as 22 | your ``nexus`` checkout, use: 23 | 24 | .. code-block:: bash 25 | 26 | PYTHONPATH=../../gargoyle ./manage.py runserver 27 | 28 | You'll need to change ``settings/base.py`` to add it there too, plus make sure 29 | any dependencies are available as well. 30 | -------------------------------------------------------------------------------- /tests/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Used for running this install of nexus locally 4 | """ 5 | import os 6 | import sys 7 | 8 | MODULE_DIR = os.path.dirname(__file__) 9 | 10 | if __name__ == "__main__": 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.dev") 12 | sys.path.insert(0, os.path.abspath(os.path.join(MODULE_DIR, '..'))) 13 | 14 | from django.core.management import execute_from_command_line 15 | 16 | execute_from_command_line(sys.argv) 17 | -------------------------------------------------------------------------------- /tests/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/tests/settings/__init__.py -------------------------------------------------------------------------------- /tests/settings/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 4 | 5 | DEBUG = True 6 | 7 | SECRET_KEY = 'NOTASECRET' 8 | 9 | DATABASES = { 10 | 'default': { 11 | 'ENGINE': 'django.db.backends.sqlite3', 12 | 'NAME': 'dev-database.sqlite3', 13 | }, 14 | } 15 | 16 | ALLOWED_HOSTS = [] 17 | 18 | INSTALLED_APPS = ( 19 | 'testapp', 20 | 'nexus', 21 | 'django.contrib.admin', 22 | 'django.contrib.auth', 23 | 'django.contrib.contenttypes', 24 | 'django.contrib.sessions', 25 | 'django.contrib.messages', 26 | 'django.contrib.staticfiles', 27 | ) 28 | 29 | MIDDLEWARE = ( 30 | 'django.middleware.common.CommonMiddleware', 31 | 'django.middleware.csrf.CsrfViewMiddleware', 32 | 'django.contrib.sessions.middleware.SessionMiddleware', 33 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 34 | 'django.contrib.messages.middleware.MessageMiddleware', 35 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 36 | ) 37 | 38 | ROOT_URLCONF = 'urls' 39 | LANGUAGE_CODE = 'en-us' 40 | TIME_ZONE = 'UTC' 41 | USE_I18N = True 42 | USE_L10N = True 43 | 44 | TEMPLATES = [ 45 | { 46 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 47 | 'DIRS': [], 48 | 'APP_DIRS': True, 49 | 'OPTIONS': { 50 | 'context_processors': [ 51 | 'django.template.context_processors.debug', 52 | 'django.template.context_processors.request', 53 | 'django.contrib.auth.context_processors.auth', 54 | 'django.contrib.messages.context_processors.messages', 55 | ], 56 | 'debug': True, 57 | }, 58 | }, 59 | ] 60 | 61 | STATIC_URL = '/static/' 62 | -------------------------------------------------------------------------------- /tests/settings/dev.py: -------------------------------------------------------------------------------- 1 | """ 2 | Used to run nexus locally 3 | """ 4 | from .base import * # noqa 5 | -------------------------------------------------------------------------------- /tests/settings/test.py: -------------------------------------------------------------------------------- 1 | """ 2 | Used for the test suite run. 3 | """ 4 | from copy import deepcopy 5 | 6 | from .base import * # noqa 7 | from .base import DATABASES 8 | 9 | DEBUG = False 10 | 11 | DATABASES = deepcopy(DATABASES) 12 | del DATABASES['default']['NAME'] 13 | -------------------------------------------------------------------------------- /tests/testapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/tests/testapp/__init__.py -------------------------------------------------------------------------------- /tests/testapp/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/tests/testapp/management/__init__.py -------------------------------------------------------------------------------- /tests/testapp/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/tests/testapp/management/commands/__init__.py -------------------------------------------------------------------------------- /tests/testapp/management/commands/runserver.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exists to ensure you can always log in with admin/password when running in 3 | 'dev' mode - see tests/README.rst 4 | """ 5 | from django.contrib.auth.models import User 6 | from django.contrib.staticfiles.management.commands.runserver import Command as BaseCommand 7 | from django.core.management import call_command 8 | 9 | 10 | class Command(BaseCommand): 11 | def check_migrations(self): 12 | call_command('migrate', interactive=True) 13 | if not User.objects.exists(): 14 | self.stdout.write( 15 | "Welcome to Nexus test mode\n" 16 | "Login with username 'admin', password 'password'", 17 | ) 18 | user = User(username='admin', is_superuser=True, is_staff=True) 19 | user.set_password('password') 20 | user.save() 21 | -------------------------------------------------------------------------------- /tests/testapp/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/tests/testapp/models.py -------------------------------------------------------------------------------- /tests/testapp/nexus_modules.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import platform 3 | 4 | from django.conf.urls import url 5 | 6 | import nexus 7 | 8 | 9 | class HelloWorldModule(nexus.NexusModule): 10 | home_url = 'index' 11 | name = 'hello-world' 12 | 13 | def get_title(self): 14 | return 'Hello World' 15 | 16 | def get_urls(self): 17 | return [ 18 | url(r'^$', self.as_view(self.index), name='index'), 19 | ] 20 | 21 | def render_on_dashboard(self, request): 22 | return self.render_to_string('nexus/example/dashboard.html', { 23 | 'title': 'Hello World', 24 | }) 25 | 26 | def index(self, request): 27 | return self.render_to_response("nexus/example/index.html", { 28 | 'title': 'Hello World', 29 | }, request) 30 | 31 | 32 | nexus.site.register(HelloWorldModule, 'hello-world') 33 | 34 | # optionally you may specify a category 35 | # nexus.site.register(HelloWorldModule, 'hello-world', category='cache') 36 | 37 | 38 | class SystemStatsModule(nexus.NexusModule): 39 | """ 40 | Modules don't need to have URLs, they can just appear on the dashboard - 41 | simply don't name `home_url` or implement `get_urls()`. 42 | """ 43 | name = 'system-stats' 44 | 45 | def get_title(self): 46 | return 'System Stats' 47 | 48 | def render_on_dashboard(self, request): 49 | return self.render_to_string('nexus/system-stats/dashboard.html', { 50 | 'stats': { 51 | 'num_cpus': multiprocessing.cpu_count(), 52 | 'platform': platform, 53 | }, 54 | }) 55 | 56 | 57 | nexus.site.register(SystemStatsModule, 'system-stats') 58 | 59 | 60 | class StyleGuideModule(nexus.NexusModule): 61 | """ 62 | Modules can also not appear on the dashboard, but not implementing 63 | render_on_dashboard, and only have URLs. 64 | """ 65 | home_url = 'index' 66 | name = 'style-guide' 67 | 68 | def get_title(self): 69 | return 'Style Guide' 70 | 71 | def get_urls(self): 72 | return [ 73 | url(r'^$', self.as_view(self.index), name='index'), 74 | ] 75 | 76 | def index(self, request): 77 | return self.render_to_response('nexus/styleguide/index.html', { 78 | 'title': 'Style Guide', 79 | }, request) 80 | 81 | 82 | nexus.site.register(StyleGuideModule, 'style-guide') 83 | -------------------------------------------------------------------------------- /tests/testapp/templates/nexus/example/dashboard.html: -------------------------------------------------------------------------------- 1 |

This is an example Nexus module!

-------------------------------------------------------------------------------- /tests/testapp/templates/nexus/example/index.html: -------------------------------------------------------------------------------- 1 | {% extends "nexus/module.html" %} 2 | 3 | {% block content %} 4 |

{{ title }}

5 | 6 |

This is an example Nexus module!

7 | {% endblock %} -------------------------------------------------------------------------------- /tests/testapp/templates/nexus/styleguide/index.html: -------------------------------------------------------------------------------- 1 | {% extends "nexus/module.html" %} 2 | 3 | {% block content %} 4 |

H1 Lorem Ipsum

5 | 6 |

H2 Lorem Ipsum

7 | 8 |

9 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dignissim malesuada iaculis. Morbi vitae turpis eget libero pellentesque tincidunt. Vestibulum blandit a erat sed pretium. Aenean aliquet purus ipsum, at elementum risus eleifend vitae. Integer eu lorem quam. Aliquam et tellus tincidunt, lobortis massa id, euismod dui. Vivamus lectus ligula, placerat in velit feugiat, molestie tempus enim. Nunc interdum dignissim nunc ac blandit. Integer lobortis ultrices dui nec sagittis. Etiam lobortis lacinia maximus. Donec interdum tempus justo, eget sagittis leo sagittis vel. Donec tincidunt congue ipsum, eu fringilla ligula porttitor eget. 10 |

11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 |
24 | 25 | 26 |
27 | 28 |

29 | 30 |

Lots of Ipsum to make the page extend beyond one screen to test scrolling behaviour

31 | 32 |

33 | Veggies es bonus vobis, proinde vos postulo essum magis kohlrabi welsh onion daikon amaranth tatsoi tomatillo melon azuki bean garlic. 34 |

35 | 36 |

37 | Gumbo beet greens corn soko endive gumbo gourd. Parsley shallot courgette tatsoi pea sprouts fava bean collard greens dandelion okra wakame tomato. Dandelion cucumber earthnut pea peanut soko zucchini. 38 |

39 | 40 |

41 | Turnip greens yarrow ricebean rutabaga endive cauliflower sea lettuce kohlrabi amaranth water spinach avocado daikon napa cabbage asparagus winter purslane kale. Celery potato scallion desert raisin horseradish spinach carrot soko. Lotus root water spinach fennel kombu maize bamboo shoot green bean swiss chard seakale pumpkin onion chickpea gram corn pea. Brussels sprout coriander water chestnut gourd swiss chard wakame kohlrabi beetroot carrot watercress. Corn amaranth salsify bunya nuts nori azuki bean chickweed potato bell pepper artichoke. 42 |

43 | 44 |

45 | Nori grape silver beet broccoli kombu beet greens fava bean potato quandong celery. Bunya nuts black-eyed pea prairie turnip leek lentil turnip greens parsnip. Sea lettuce lettuce water chestnut eggplant winter purslane fennel azuki bean earthnut pea sierra leone bologi leek soko chicory celtuce parsley jícama salsify. 46 |

47 | 48 |

49 | Celery quandong swiss chard chicory earthnut pea potato. Salsify taro catsear garlic gram celery bitterleaf wattle seed collard greens nori. Grape wattle seed kombu beetroot horseradish carrot squash brussels sprout chard. 50 |

51 | 52 |

53 | Pea horseradish azuki bean lettuce avocado asparagus okra. Kohlrabi radish okra azuki bean corn fava bean mustard tigernut jícama green bean celtuce collard greens avocado quandong fennel gumbo black-eyed pea. Grape silver beet watercress potato tigernut corn groundnut. Chickweed okra pea winter purslane coriander yarrow sweet pepper radish garlic brussels sprout groundnut summer purslane earthnut pea tomato spring onion azuki bean gourd. Gumbo kakadu plum komatsuna black-eyed pea green bean zucchini gourd winter purslane silver beet rock melon radish asparagus spinach. 54 |

55 | 56 |

57 | Beetroot water spinach okra water chestnut ricebean pea catsear courgette summer purslane. Water spinach arugula pea tatsoi aubergine spring onion bush tomato kale radicchio turnip chicory salsify pea sprouts fava bean. Dandelion zucchini burdock yarrow chickpea dandelion sorrel courgette turnip greens tigernut soybean radish artichoke wattle seed endive groundnut broccoli arugula. 58 |

59 | 60 |

61 | Soko radicchio bunya nuts gram dulse silver beet parsnip napa cabbage lotus root sea lettuce brussels sprout cabbage. Catsear cauliflower garbanzo yarrow salsify chicory garlic bell pepper napa cabbage lettuce tomato kale arugula melon sierra leone bologi rutabaga tigernut. Sea lettuce gumbo grape kale kombu cauliflower salsify kohlrabi okra sea lettuce broccoli celery lotus root carrot winter purslane turnip greens garlic. Jícama garlic courgette coriander radicchio plantain scallion cauliflower fava bean desert raisin spring onion chicory bunya nuts. Sea lettuce water spinach gram fava bean leek dandelion silver beet eggplant bush tomato. 62 |

63 | 64 | {% endblock %} 65 | -------------------------------------------------------------------------------- /tests/testapp/templates/nexus/system-stats/dashboard.html: -------------------------------------------------------------------------------- 1 |

2 | CPU: {{ stats.platform.processor }} 3 |

4 | 5 |

6 | CPU Cores: {{ stats.num_cpus }} 7 |

8 | 9 |

10 | OS: {{ stats.platform.platform }} 11 |

12 | 13 |

14 | Python: 15 | {{ stats.platform.python_implementation }} 16 | {{ stats.platform.python_version }} 17 |

18 | -------------------------------------------------------------------------------- /tests/testapp/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/tests/testapp/templatetags/__init__.py -------------------------------------------------------------------------------- /tests/testapp/templatetags/test_nexus_helpers.py: -------------------------------------------------------------------------------- 1 | from bs4 import BeautifulSoup 2 | from django.template import Context, RequestContext, Template 3 | from django.test import RequestFactory, SimpleTestCase 4 | 5 | import nexus 6 | from nexus.sites import site 7 | 8 | 9 | class NexusHelpersTests(SimpleTestCase): 10 | 11 | request_factory = RequestFactory() 12 | 13 | def test_nexus_media_prefix(self): 14 | out = Template(''' 15 | {% load nexus_media_prefix from nexus_helpers %} 16 | {% nexus_media_prefix %} 17 | ''').render(Context()).strip() 18 | assert out == '/nexus/media' 19 | 20 | def test_nexus_version(self): 21 | out = Template(''' 22 | {% load nexus_version from nexus_helpers %} 23 | {% nexus_version %} 24 | ''').render(Context()).strip() 25 | assert out == nexus.__version__ 26 | 27 | def test_show_navigation(self): 28 | request = self.request_factory.get('/') 29 | out = Template(''' 30 | {% load show_navigation from nexus_helpers %} 31 | {% show_navigation %} 32 | ''').render(RequestContext(request, {'nexus_site': site})).strip() 33 | BeautifulSoup(out, 'html.parser') # checks it is valid HTML 34 | -------------------------------------------------------------------------------- /tests/testapp/test_checks.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.test import TestCase 3 | from django.test.utils import override_settings 4 | 5 | from nexus.checks import check_requirements 6 | 7 | INSTALLED_APPS_WITHOUT_AUTH = list(set(settings.INSTALLED_APPS) - {'django.contrib.auth'}) 8 | 9 | 10 | class ChecksTests(TestCase): 11 | 12 | def test_requirements_pass(self): 13 | assert check_requirements([]) == [] 14 | 15 | @override_settings(INSTALLED_APPS=INSTALLED_APPS_WITHOUT_AUTH) 16 | def test_requirements_fail(self): 17 | assert len(check_requirements([])) == 1 18 | 19 | @override_settings( 20 | NEXUS_SKIP_INSTALLED_APPS_REQUIREMENTS=True, 21 | INSTALLED_APPS=INSTALLED_APPS_WITHOUT_AUTH, 22 | ) 23 | def test_requirements_fail_suppressed(self): 24 | assert check_requirements([]) == [] 25 | -------------------------------------------------------------------------------- /tests/testapp/test_conf.py: -------------------------------------------------------------------------------- 1 | from django.test import SimpleTestCase 2 | from django.test.utils import override_settings 3 | 4 | from nexus.conf import nexus_settings 5 | 6 | 7 | class NexusSettingsTests(SimpleTestCase): 8 | 9 | @override_settings(NEXUS_MEDIA_PREFIX='/mynexusprefix/') 10 | def test_with_override_settings(self): 11 | assert nexus_settings.MEDIA_PREFIX == '/mynexusprefix/' 12 | 13 | @override_settings( 14 | NEXUS_USE_DJANGO_MEDIA_URL=True, 15 | MEDIA_URL='/a-big-test/', 16 | ) 17 | def test_use_django_media_url(self): 18 | assert nexus_settings.MEDIA_PREFIX == '/a-big-test/' 19 | -------------------------------------------------------------------------------- /tests/testapp/test_nexus_modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamchainz/nexus/543aa06ad8b04108d8ccaa65106a0ef301f88561/tests/testapp/test_nexus_modules/__init__.py -------------------------------------------------------------------------------- /tests/testapp/test_nexus_modules/test_hello_world.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.test import TestCase 3 | 4 | 5 | class HelloWorldModuleTests(TestCase): 6 | 7 | def setUp(self): 8 | self.user = User(username='user', is_staff=True) 9 | self.user.set_password('password') 10 | self.user.save() 11 | self.client.login(username='user', password='password') 12 | 13 | def test_is_on_dashboard(self): 14 | resp = self.client.get('/nexus/') 15 | assert resp.status_code == 200 16 | assert "Hello World" in resp.content.decode('utf-8') 17 | 18 | def test_index_page(self): 19 | resp = self.client.get('/nexus/hello-world/') 20 | assert resp.status_code == 200 21 | assert "Hello World" in resp.content.decode('utf-8') 22 | -------------------------------------------------------------------------------- /tests/testapp/test_views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.test import TestCase 3 | 4 | 5 | class ViewTests(TestCase): 6 | 7 | def setUp(self): 8 | self.user = User(username='user', is_staff=True) 9 | self.user.set_password('password') 10 | self.user.save() 11 | self.client.login(username='user', password='password') 12 | 13 | def test_dashboard_not_logged_in(self): 14 | self.client.logout() 15 | resp = self.client.get('/nexus/', follow=False) 16 | assert resp.status_code == 302 17 | assert '/admin/login/' in resp['Location'] 18 | 19 | def test_dashboard_logged_in(self): 20 | resp = self.client.get('/nexus/') 21 | assert resp.status_code == 200 22 | assert "Dashboard" in resp.content.decode('utf-8') 23 | assert 'csrftoken' in resp.cookies 24 | 25 | def test_dashboard_not_staff(self): 26 | self.user.is_staff = False 27 | self.user.save() 28 | resp = self.client.get('/nexus/') 29 | assert resp.status_code == 403 30 | 31 | def test_media_logo(self): 32 | resp = self.client.get('/nexus/media/nexus/img/nexus_logo.png') 33 | assert resp.status_code == 200 34 | assert 'Last-Modified' in resp 35 | assert 'Content-Length' in resp 36 | 37 | def test_media_slash_slash_ignored(self): 38 | resp = self.client.get('/nexus/media/nexus/img//nexus_logo.png') 39 | assert resp.status_code == 200 40 | 41 | def test_media_modified_since(self): 42 | resp = self.client.get('/nexus/media/nexus/img/nexus_logo.png', 43 | HTTP_IF_MODIFIED_SINCE='Wed, 25 Feb 2065 17:42:04 GMT') 44 | assert resp.status_code == 304 45 | 46 | def test_media_non_existent(self): 47 | resp = self.client.get('/nexus/media/nexus/img/doesnotexist.png') 48 | assert resp.status_code == 404 49 | 50 | def test_media_directory_not_allowed(self): 51 | resp = self.client.get('/nexus/media/nexus/img/doesnotexist.png') 52 | assert resp.status_code == 404 53 | -------------------------------------------------------------------------------- /tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | from django.contrib import admin 3 | 4 | import nexus 5 | 6 | urlpatterns = [ 7 | url(r'^admin/', admin.site.urls), 8 | url(r'^nexus/', include(nexus.site.urls)), 9 | ] 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py3-django{111,20,21,22}, 4 | py3-codestyle 5 | 6 | [testenv] 7 | setenv = 8 | PYTHONDONTWRITEBYTECODE=1 9 | install_command = pip install --no-deps {opts} {packages} 10 | deps = 11 | django111: Django>=1.11a1,<2.0 12 | django20: Django>=2.0,<2.1 13 | django21: Django>=2.1,<2.2 14 | django22: Django>=2.2,<2.3 15 | -rrequirements.txt 16 | commands = ./runtests.py {posargs} 17 | 18 | [testenv:py3-codestyle] 19 | deps = -rrequirements.txt 20 | skip_install = true 21 | commands = multilint 22 | --------------------------------------------------------------------------------