├── .coveragerc ├── .github └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.rst ├── ckanext ├── __init__.py └── showcase │ ├── __init__.py │ ├── assets │ ├── build │ │ └── ckeditor.js │ ├── ckanext_showcase.css │ ├── ckeditor-content-style.css │ ├── js │ │ └── showcase-ckeditor.js │ ├── src │ │ └── ckeditor.js │ └── webassets.yml │ ├── cli.py │ ├── i18n │ ├── __init__.py │ ├── ckanext-showcase.pot │ ├── de │ │ └── LC_MESSAGES │ │ │ ├── ckanext-showcase.mo │ │ │ └── ckanext-showcase.po │ ├── en_AU │ │ └── LC_MESSAGES │ │ │ ├── ckanext-showcase.mo │ │ │ └── ckanext-showcase.po │ ├── es │ │ └── LC_MESSAGES │ │ │ ├── ckanext-showcase.mo │ │ │ └── ckanext-showcase.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ ├── ckanext-showcase.mo │ │ │ └── ckanext-showcase.po │ └── zh_Hant_TW │ │ └── LC_MESSAGES │ │ ├── ckanext-showcase.mo │ │ └── ckanext-showcase.po │ ├── logic │ ├── __init__.py │ ├── action │ │ ├── __init__.py │ │ ├── create.py │ │ ├── delete.py │ │ ├── get.py │ │ └── update.py │ ├── auth.py │ ├── converters.py │ ├── helpers.py │ ├── schema.py │ └── validators.py │ ├── migration │ └── showcase │ │ ├── README │ │ ├── alembic.ini │ │ ├── env.py │ │ ├── script.py.mako │ │ └── versions │ │ └── 02b006cb222c_add_ckanext_showcase_tables.py │ ├── model │ └── __init__.py │ ├── plugin.py │ ├── public │ └── .gitignore │ ├── templates │ ├── .gitignore │ ├── admin │ │ ├── base.html │ │ ├── confirm_remove_showcase_admin.html │ │ └── manage_showcase_admins.html │ ├── header.html │ ├── home │ │ └── snippets │ │ │ └── stats.html │ ├── package │ │ ├── dataset_showcase_list.html │ │ └── read_base.html │ └── showcase │ │ ├── add_datasets.html │ │ ├── confirm_delete.html │ │ ├── edit.html │ │ ├── edit_base.html │ │ ├── manage_datasets.html │ │ ├── new.html │ │ ├── new_package_form.html │ │ ├── read.html │ │ ├── search.html │ │ └── snippets │ │ ├── helper.html │ │ ├── showcase_info.html │ │ ├── showcase_item.html │ │ ├── showcase_list.html │ │ ├── showcase_search_form.html │ │ ├── showcase_search_result_text.html │ │ └── tags.html │ ├── tests │ ├── __init__.py │ ├── action │ │ ├── __init__.py │ │ ├── test_create.py │ │ ├── test_delete.py │ │ └── test_get.py │ ├── fixtures.py │ ├── test_auth.py │ ├── test_converters.py │ ├── test_helpers.py │ ├── test_plugin.py │ └── test_utils.py │ ├── utils.py │ └── views.py ├── conftest.py ├── dev-requirements.txt ├── package-lock.json ├── package.json ├── requirements.txt ├── setup.cfg ├── setup.py ├── test.ini └── webpack.config.js /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | */site-packages/* 4 | */python?.?/* 5 | ckan/* -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [push, pull_request] 3 | jobs: 4 | lint: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: actions/setup-python@v5 9 | with: 10 | python-version: '3.10' 11 | - name: Install requirements 12 | run: pip install flake8 pycodestyle 13 | - name: Check syntax 14 | run: flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics --exclude ckan 15 | 16 | test: 17 | needs: lint 18 | strategy: 19 | matrix: 20 | ckan-version: ["2.11", "2.10"] 21 | fail-fast: false 22 | 23 | name: CKAN ${{ matrix.ckan-version }} 24 | runs-on: ubuntu-latest 25 | container: 26 | image: ckan/ckan-dev:${{ matrix.ckan-version }} 27 | options: --user root 28 | services: 29 | solr: 30 | image: ckan/ckan-solr:${{ matrix.ckan-version }}-solr9 31 | postgres: 32 | image: ckan/ckan-postgres-dev:${{ matrix.ckan-version }} 33 | env: 34 | POSTGRES_USER: postgres 35 | POSTGRES_PASSWORD: postgres 36 | POSTGRES_DB: postgres 37 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 38 | redis: 39 | image: redis:3 40 | env: 41 | CKAN_SQLALCHEMY_URL: postgresql://ckan_default:pass@postgres/ckan_test 42 | CKAN_DATASTORE_WRITE_URL: postgresql://datastore_write:pass@postgres/datastore_test 43 | CKAN_DATASTORE_READ_URL: postgresql://datastore_read:pass@postgres/datastore_test 44 | CKAN_SOLR_URL: http://solr:8983/solr/ckan 45 | CKAN_REDIS_URL: redis://redis:6379/1 46 | 47 | steps: 48 | - uses: actions/checkout@v4 49 | - name: Install requirements 50 | run: | 51 | pip install -r requirements.txt 52 | pip install -r dev-requirements.txt 53 | pip install -e . 54 | # Replace default path to CKAN core config file with the one on the container 55 | sed -i -e 's/use = config:.*/use = config:\/srv\/app\/src\/ckan\/test-core.ini/' test.ini 56 | 57 | - name: Setup extension (CKAN >= 2.9) 58 | run: | 59 | ckan -c test.ini db init 60 | 61 | - name: Run tests 62 | run: pytest --ckan-ini=test.ini --cov=ckanext.showcase --cov-report=term-missing --cov-append --disable-warnings ckanext/showcase/tests 63 | 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ropeproject 2 | node_modules 3 | bower_components 4 | .vscode 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | develop-eggs/ 17 | sdist/ 18 | *.egg-info/ 19 | .installed.cfg 20 | *.egg 21 | .eggs/ 22 | .venv 23 | 24 | # PyInstaller 25 | # Usually these files are written by a python script from a template 26 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 27 | *.manifest 28 | *.spec 29 | 30 | # Installer logs 31 | pip-log.txt 32 | pip-delete-this-directory.txt 33 | 34 | # Unit test / coverage reports 35 | htmlcov/ 36 | .tox/ 37 | .coverage 38 | .cache 39 | nosetests.xml 40 | coverage.xml 41 | 42 | # Sphinx documentation 43 | docs/_build/ 44 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Ckanext-showcase CHANGELOG 2 | 3 | ## v1.8.1 2025-01-29 4 | 5 | * Fixes delete and update showcase buttons (#173) 6 | * Update MANIFEST.in to include migration files and public assets (#174) 7 | 8 | ## v1.8.0 2025-01-28 9 | 10 | * Add CKAN 2.11 support (#170) 11 | 12 | ## v1.7.0 2024-04-17 13 | 14 | * Fix navigation menu headers (#169) 15 | * Add English Australia (en_AU) translation files (#168) 16 | 17 | ## v1.6.1 2023-06-15 18 | * Fixed BS5 class and add CSRF support to all forms (#167) 19 | * String prefix cleanup (#165) 20 | 21 | ## v1.6.0 2023-02-14 22 | 23 | * Dropped support for CKAN 2.7 and 2.8 24 | * Dropped support for Python 2 25 | * Add support for CSRF token 26 | * Sanitize blueprint names. All views should be called using `showcase_blueprint.` 27 | * Rename get_showcase_wysiwyg_editor to avoid name clashes with other extensions (like `ckanext-pages`) 28 | * Update CKEditor to it's latest version: 36.0.1 29 | 30 | ## v1.5.1 2022-08-10 31 | 32 | * Dependency update 33 | 34 | ## v1.5.0 2022-04-20 35 | 36 | * Support for CKAN 2.10 (#143) 37 | * Fix message in Add to showcase button (#139) 38 | 39 | ## v1.4.8 2022-01-17 40 | 41 | * Add Chinese (Traditional, Taiwan) translations (#136) 42 | * Dependency update 43 | 44 | ## v1.4.7 2022-01-04 45 | 46 | * Fix ReST in README (#133) 47 | * Move minimal requirements into setup.py (#134) 48 | 49 | ## v1.4.6 2022-01-04 50 | 51 | * Fix version in setup.py and add to changelog (#130) 52 | 53 | ## v1.4.5 2021-11.25 54 | 55 | * Add German and French translations (#124, #126) 56 | * Fix logic for API routes (#128) 57 | * Dependency updates 58 | 59 | ## v1.4.4 2021-08-17 60 | 61 | * Fix hardcoded route (#118) 62 | 63 | ## v1.4.3 2021-04-21 64 | 65 | * Fix typo on setup.py (#107) 66 | 67 | 68 | ## v1.4.2 2021-04-20 69 | 70 | * Fix ckeditor asset bundle (#105) 71 | 72 | 73 | ## v1.4.1 2021-04-08 74 | 75 | * Fix uploads on CKAN 2.9 (71215ca) 76 | * Upgrade CKEditor and additional libraries 77 | * Align search form design with core (#100) 78 | * Include webassets.yml in MANIFEST.in (#98) 79 | 80 | ## v1.4.0 2021-02-21 81 | 82 | Features: 83 | 84 | * Python 3 support #95 85 | * CKAN 2.9 support #95 86 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | recursive-include ckanext/showcase/templates * 3 | recursive-include ckanext/showcase/i18n * 4 | recursive-include ckanext/showcase/fanstatic * 5 | recursive-include ckanext/showcase/migration *.ini *.py *.mako 6 | recursive-include ckanext/showcase/public *.* 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. You should enable this project on travis-ci.org and coveralls.io to make 2 | these badges work. The necessary Travis and Coverage config files have been 3 | generated for you. 4 | 5 | .. image:: https://github.com/ckan/ckanext-showcase/workflows/Tests/badge.svg?branch=master 6 | :target: https://github.com/ckan/ckanext-showcase/actions 7 | 8 | .. image:: https://codecov.io/gh/ckan/ckanext-showcase/branch/master/graph/badge.svg 9 | :target: https://codecov.io/gh/ckan/ckanext-showcase 10 | 11 | ================ 12 | ckanext-showcase 13 | ================ 14 | 15 | Showcase and link to datasets in use. Datasets used in an app, website or 16 | visualization, or featured in an article, report or blog post can be showcased 17 | within the CKAN website. Showcases can include an image, description, tags and 18 | external link. Showcases may contain several datasets, helping users discover 19 | related datasets being used together. Showcases can be discovered by searching 20 | and filtered by tag. 21 | 22 | Site sysadmins can promote selected users to become 'Showcase Admins' to help 23 | create, populate and maintain showcases. 24 | 25 | ckanext-showcase is intended to be a more powerful replacement for the 26 | 'Related Item' feature. 27 | 28 | 29 | ------------ 30 | Requirements 31 | ------------ 32 | 33 | Tested on CKAN 2.9 to 2.11. 34 | 35 | Note: Use `1.5.2` for older CKAN versions (2.7 and 2.8). 36 | 37 | ------------ 38 | Installation 39 | ------------ 40 | 41 | .. Add any additional install steps to the list below. 42 | For example installing any non-Python dependencies or adding any required 43 | config settings. 44 | 45 | To install ckanext-showcase: 46 | 47 | 1. Activate your CKAN virtual environment, for example:: 48 | 49 | . /usr/lib/ckan/default/bin/activate 50 | 51 | 2. Install the ckanext-showcase Python package into your virtual environment:: 52 | 53 | pip install ckanext-showcase 54 | 55 | 3. Add ``showcase`` to the ``ckan.plugins`` setting in your CKAN 56 | config file (by default the config file is located at 57 | ``/etc/ckan/default/production.ini``). 58 | 59 | 4. Create the database tables:: 60 | 61 | ckan db upgrade -p showcase 62 | 63 | 64 | 5. Restart CKAN. 65 | 66 | ------------------------ 67 | Development Installation 68 | ------------------------ 69 | 70 | To install ckanext-showcase for development, activate your CKAN virtualenv and 71 | do:: 72 | 73 | git clone https://github.com/ckan/ckanext-showcase.git 74 | cd ckanext-showcase 75 | pip install -e . 76 | pip install -r dev-requirements.txt 77 | 78 | 79 | The extension contains a custom build of CKEditor to allow using a WYSIWYG editor 80 | to write the content of the showcase. It has been built using `webpack` and the 81 | repository contains all the files needed to edit and customize it if needed:: 82 | 83 | npm install 84 | npx webpack --config webpack.config.js 85 | 86 | Build anatomy 87 | * assets/build/ckeditor.js - The ready-to-use editor bundle, containing the editor and all plugins. 88 | * assets/js/showcase-editor - The CKAN module that will load and config the bundle when using it as data-module attribute. 89 | * assets/src/ckeditor.js - The source entry point of the build. Based on it the build/ckeditor.js file is created by webpack. It defines the editor creator, the list of plugins and the default configuration of a build. 90 | * webpack.config.js - The webpack configuration used to build the editor. 91 | 92 | More info on how to build CKEditor from source: 93 | https://ckeditor.com/docs/ckeditor5/latest/installation/getting-started/quick-start-other.html#building-the-editor-from-source 94 | 95 | 96 | --- 97 | API 98 | --- 99 | 100 | All actions in the Showcase extension are available in the CKAN Action API. 101 | 102 | Showcase actions:: 103 | 104 | - create a new showcase (sysadmins and showcase admins only) 105 | curl -X POST http://127.0.0.1:5000/api/3/action/ckanext_showcase_create -H "Authorization:{YOUR-API-KEY}" -d '{"name": "my-new-showcase"}' 106 | 107 | - delete a showcase (sysadmins and showcase admins only) 108 | curl -X POST http://127.0.0.1:5000/api/3/action/ckanext_showcase_delete -H "Authorization:{YOUR-API-KEY}" -d '{"name": "my-new-showcase"}' 109 | 110 | - show a showcase 111 | curl -X POST http://127.0.0.1:5000/api/3/action/ckanext_showcase_show -d '{"id": "my-new-showcase"}' 112 | 113 | - list showcases 114 | curl -X POST http://127.0.0.1:5000/api/3/action/ckanext_showcase_list -d '' 115 | 116 | 117 | Dataset actions:: 118 | 119 | - add a dataset to a showcase (sysadmins and showcase admins only) 120 | curl -X POST http://127.0.0.1:5000/api/3/action/ckanext_showcase_package_association_create -H "Authorization:{YOUR-API-KEY}" -d '{"showcase_id": "my-showcase", "package_id": "my-package"}' 121 | 122 | - remove a dataset from a showcase (sysadmins and showcase admins only) 123 | curl -X POST http://127.0.0.1:5000/api/3/action/ckanext_showcase_package_association_delete -H "Authorization:{YOUR-API-KEY}" -d '{"showcase_id": "my-showcase", "package_id": "my-package"}' 124 | 125 | - list datasets in a showcase 126 | curl -X POST http://127.0.0.1:5000/api/3/action/ckanext_showcase_package_list -d '{"showcase_id": "my-showcase"}' 127 | 128 | - list showcases featuring a given dataset 129 | curl -X POST http://127.0.0.1:5000/api/3/action/ckanext_package_showcase_list -d '{"package_id": "my-package"}' 130 | 131 | 132 | Showcase admin actions:: 133 | 134 | - add showcase admin (sysadmins only) 135 | curl -X POST http://127.0.0.1:5000/api/3/action/ckanext_showcase_admin_add -H "Authorization:{YOUR-API-KEY}" -d '{"username": "bert"}' 136 | 137 | - remove showcase admin (sysadmins only) 138 | curl -X POST http://127.0.0.1:5000/api/3/action/ckanext_showcase_admin_remove -H "Authorization:{YOUR-API-KEY}" -d '{"username": "bert"}' 139 | 140 | - list showcase admins (sysadmins only) 141 | curl -X POST http://127.0.0.1:5000/api/3/action/ckanext_showcase_admin_list -H "Authorization:{YOUR-API-KEY}" -d '' 142 | 143 | 144 | --- 145 | UI 146 | --- 147 | 148 | The Showcase extension adds the following pages to the user interface: 149 | 150 | 151 | * The main showcase index is available on: ``http://127.0.0.1:5000/showcase`` 152 | 153 | * To create a new showcase: ``http://127.0.0.1:5000/showcase/new`` 154 | 155 | * To edit or delete a showcase: ``http://127.0.0.1:5000/showcase/edit/{showcase-name}`` 156 | 157 | * To add a Showcase Admin : ``http://127.0.0.1:5000/ckan-admin/showcase_admins`` 158 | 159 | 160 | --------------------- 161 | Configuration 162 | --------------------- 163 | 164 | If you want to use the WYSIWYG editor instead of Markdown to write the content of the showcase:: 165 | 166 | ckanext.showcase.editor = ckeditor 167 | 168 | ----------------------------------------------- 169 | Migrating Showcases Notes from Markdown to HTML 170 | ----------------------------------------------- 171 | 172 | When using CKEditor as WYSIWYG editor showcases notes are stored in HTML 173 | instead of Markdown. To migrate all existing notes from markdown to 174 | HTML you can use the ```showcase markdown_to_html``` command. 175 | 176 | From the ``ckanext-showcase`` directory:: 177 | 178 | ckan -c {path to production.ini} showcase markdown-to-html 179 | 180 | ----------------- 181 | Running the Tests 182 | ----------------- 183 | 184 | To run the tests, do:: 185 | 186 | pytest --ckan-ini=test.ini ckanext/showcase/tests 187 | 188 | 189 | ------------------------------------ 190 | Registering ckanext-showcase on PyPI 191 | ------------------------------------ 192 | 193 | ckanext-showcase should be availabe on PyPI as 194 | https://pypi.python.org/pypi/ckanext-showcase. If that link doesn't work, then 195 | you can register the project on PyPI for the first time by following these 196 | steps: 197 | 198 | 1. Create a source distribution of the project:: 199 | 200 | python setup.py sdist 201 | 202 | 2. Register the project:: 203 | 204 | python setup.py register 205 | 206 | 3. Upload the source distribution to PyPI:: 207 | 208 | python setup.py sdist upload 209 | 210 | 4. Tag the first release of the project on GitHub with the version number from 211 | the ``setup.py`` file. For example if the version number in ``setup.py`` is 212 | 0.0.1 then do:: 213 | 214 | git tag 0.0.1 215 | git push --tags 216 | 217 | 218 | ------------------------------------------- 219 | Releasing a New Version of ckanext-showcase 220 | ------------------------------------------- 221 | 222 | ckanext-showcase is availabe on PyPI as https://pypi.python.org/pypi/ckanext-showcase. 223 | To publish a new version to PyPI follow these steps: 224 | 225 | 1. Update the version number in the ``setup.py`` file. 226 | See `PEP 440 `_ 227 | for how to choose version numbers. 228 | 229 | 2. Create a source distribution of the new version:: 230 | 231 | python setup.py sdist 232 | 233 | 3. Upload the source distribution to PyPI:: 234 | 235 | python setup.py sdist upload 236 | 237 | 4. Tag the new release of the project on GitHub with the version number from 238 | the ``setup.py`` file. For example if the version number in ``setup.py`` is 239 | 0.0.2 then do:: 240 | 241 | git tag 0.0.2 242 | git push --tags 243 | 244 | 245 | ------------------------------------------- 246 | i18n 247 | ------------------------------------------- 248 | 249 | See: "Internationalizing strings in extensions" : http://docs.ckan.org/en/latest/extensions/translating-extensions.html 250 | 251 | 1. Install babel 252 | 253 | pip install Babel 254 | 255 | 2. Init Catalog for your language 256 | 257 | python setup.py init_catalog -l es 258 | 259 | 3. Compile your language catalog ( You can force pybabel compile to compile messages marked as fuzzy with the -f) 260 | 261 | python setup.py compile_catalog -f -l es 262 | 263 | -------------------------------------------------------------------------------- /ckanext/__init__.py: -------------------------------------------------------------------------------- 1 | # this is a namespace package 2 | try: 3 | import pkg_resources 4 | pkg_resources.declare_namespace(__name__) 5 | except ImportError: 6 | import pkgutil 7 | __path__ = pkgutil.extend_path(__path__, __name__) 8 | -------------------------------------------------------------------------------- /ckanext/showcase/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-showcase/6a9b6e96c4482b9becbfb0bdd27083d51b4900ed/ckanext/showcase/__init__.py -------------------------------------------------------------------------------- /ckanext/showcase/assets/ckanext_showcase.css: -------------------------------------------------------------------------------- 1 | /* Custom style rules for ckanext-showcase */ 2 | 3 | .context-info .module-content .smallest { 4 | font-size: 13px; 5 | } 6 | 7 | .context-info .module-content .info .btn { 8 | margin-top: 18px; 9 | } 10 | 11 | .actions { 12 | top: 36px; 13 | right: 25px; 14 | } 15 | 16 | .ckanext-showcase-edit-wrapper { 17 | background-image: none; 18 | } 19 | 20 | .ckanext-showcase-image-container { 21 | text-align: center; 22 | } 23 | 24 | .ckanext-showcase-image { 25 | margin-bottom: 25px; 26 | } 27 | 28 | .ckanext-showcase-notes { 29 | margin-bottom: 25px; 30 | } 31 | 32 | .ckanext-showcase-launch { 33 | margin-bottom: 25px; 34 | } 35 | 36 | 37 | td.ckanext_showcase_pagination_footer { 38 | padding: 0; 39 | } 40 | .ckanext_showcase_pagination_footer .pagination { 41 | margin: 0; 42 | border-top: none 0; 43 | } 44 | 45 | .module-narrow .nav-item > a { 46 | word-break: break-word; 47 | } 48 | 49 | .ck-editor__editable_inline { 50 | min-height: 200px; 51 | } 52 | -------------------------------------------------------------------------------- /ckanext/showcase/assets/ckeditor-content-style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * CKEditor 5 (v17.0.0) content styles. 3 | * Generated on Fri, 13 Mar 2020 13:27:10 GMT. 4 | * For more information, check out https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/content-styles.html 5 | */ 6 | 7 | :root { 8 | --ck-highlight-marker-blue: #72cdfd; 9 | --ck-highlight-marker-green: #63f963; 10 | --ck-highlight-marker-pink: #fc7999; 11 | --ck-highlight-marker-yellow: #fdfd77; 12 | --ck-highlight-pen-green: #118800; 13 | --ck-highlight-pen-red: #e91313; 14 | --ck-image-style-spacing: 1.5em; 15 | --ck-todo-list-checkmark-size: 16px; 16 | } 17 | 18 | /* ckeditor5-image/theme/imageresize.css */ 19 | .ck-content .image.image_resized { 20 | max-width: 100%; 21 | display: block; 22 | box-sizing: border-box; 23 | } 24 | /* ckeditor5-image/theme/imageresize.css */ 25 | .ck-content .image.image_resized img { 26 | width: 100%; 27 | } 28 | /* ckeditor5-image/theme/imageresize.css */ 29 | .ck-content .image.image_resized > figcaption { 30 | display: block; 31 | } 32 | /* ckeditor5-basic-styles/theme/code.css */ 33 | .ck-content code { 34 | background-color: hsla(0, 0%, 78%, 0.3); 35 | padding: .15em; 36 | border-radius: 2px; 37 | } 38 | /* ckeditor5-image/theme/image.css */ 39 | .ck-content .image { 40 | display: table; 41 | clear: both; 42 | text-align: center; 43 | margin: 1em auto; 44 | } 45 | /* ckeditor5-image/theme/image.css */ 46 | .ck-content .image > img { 47 | display: block; 48 | margin: 0 auto; 49 | max-width: 100%; 50 | min-width: 50px; 51 | } 52 | /* ckeditor5-image/theme/imagestyle.css */ 53 | .ck-content .image-style-side, 54 | .ck-content .image-style-align-left, 55 | .ck-content .image-style-align-center, 56 | .ck-content .image-style-align-right { 57 | max-width: 50%; 58 | } 59 | /* ckeditor5-image/theme/imagestyle.css */ 60 | .ck-content .image-style-side { 61 | float: right; 62 | margin-left: var(--ck-image-style-spacing); 63 | } 64 | /* ckeditor5-image/theme/imagestyle.css */ 65 | .ck-content .image-style-align-left { 66 | float: left; 67 | margin-right: var(--ck-image-style-spacing); 68 | } 69 | /* ckeditor5-image/theme/imagestyle.css */ 70 | .ck-content .image-style-align-center { 71 | margin-left: auto; 72 | margin-right: auto; 73 | } 74 | /* ckeditor5-image/theme/imagestyle.css */ 75 | .ck-content .image-style-align-right { 76 | float: right; 77 | margin-left: var(--ck-image-style-spacing); 78 | } 79 | /* ckeditor5-page-break/theme/pagebreak.css */ 80 | .ck-content .page-break { 81 | position: relative; 82 | clear: both; 83 | padding: 5px 0; 84 | display: flex; 85 | align-items: center; 86 | justify-content: center; 87 | } 88 | /* ckeditor5-page-break/theme/pagebreak.css */ 89 | .ck-content .page-break::after { 90 | content: ''; 91 | position: absolute; 92 | border-bottom: 2px dashed hsl(0, 0%, 77%); 93 | width: 100%; 94 | } 95 | /* ckeditor5-page-break/theme/pagebreak.css */ 96 | .ck-content .page-break__label { 97 | position: relative; 98 | z-index: 1; 99 | padding: .3em .6em; 100 | display: block; 101 | text-transform: uppercase; 102 | border: 1px solid hsl(0, 0%, 77%); 103 | border-radius: 2px; 104 | font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif; 105 | font-size: 0.75em; 106 | font-weight: bold; 107 | color: hsl(0, 0%, 20%); 108 | background: #fff; 109 | box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15); 110 | -webkit-user-select: none; 111 | -moz-user-select: none; 112 | -ms-user-select: none; 113 | user-select: none; 114 | } 115 | /* ckeditor5-block-quote/theme/blockquote.css */ 116 | .ck-content blockquote { 117 | overflow: hidden; 118 | padding-right: 1.5em; 119 | padding-left: 1.5em; 120 | margin-left: 0; 121 | margin-right: 0; 122 | font-style: italic; 123 | border-left: solid 5px hsl(0, 0%, 80%); 124 | } 125 | /* ckeditor5-block-quote/theme/blockquote.css */ 126 | .ck-content[dir="rtl"] blockquote { 127 | border-left: 0; 128 | border-right: solid 5px hsl(0, 0%, 80%); 129 | } 130 | /* ckeditor5-media-embed/theme/mediaembed.css */ 131 | .ck-content .media { 132 | clear: both; 133 | margin: 1em 0; 134 | display: block; 135 | min-width: 15em; 136 | } 137 | /* ckeditor5-table/theme/table.css */ 138 | .ck-content .table { 139 | margin: 1em auto; 140 | display: table; 141 | } 142 | /* ckeditor5-table/theme/table.css */ 143 | .ck-content .table table { 144 | border-collapse: collapse; 145 | border-spacing: 0; 146 | width: 100%; 147 | height: 100%; 148 | border: 1px double hsl(0, 0%, 70%); 149 | } 150 | /* ckeditor5-table/theme/table.css */ 151 | .ck-content .table table td, 152 | .ck-content .table table th { 153 | min-width: 2em; 154 | padding: .4em; 155 | border-color: hsl(0, 0%, 75%); 156 | } 157 | /* ckeditor5-table/theme/table.css */ 158 | .ck-content .table table th { 159 | font-weight: bold; 160 | background: hsla(0, 0%, 0%, 5%); 161 | } 162 | /* ckeditor5-list/theme/todolist.css */ 163 | .ck-content .todo-list { 164 | list-style: none; 165 | } 166 | /* ckeditor5-list/theme/todolist.css */ 167 | .ck-content .todo-list li { 168 | margin-bottom: 5px; 169 | } 170 | /* ckeditor5-list/theme/todolist.css */ 171 | .ck-content .todo-list li .todo-list { 172 | margin-top: 5px; 173 | } 174 | /* ckeditor5-list/theme/todolist.css */ 175 | .ck-content .todo-list .todo-list__label > input { 176 | -webkit-appearance: none; 177 | display: inline-block; 178 | position: relative; 179 | width: var(--ck-todo-list-checkmark-size); 180 | height: var(--ck-todo-list-checkmark-size); 181 | vertical-align: middle; 182 | border: 0; 183 | left: -25px; 184 | margin-right: -15px; 185 | right: 0; 186 | margin-left: 0; 187 | } 188 | /* ckeditor5-list/theme/todolist.css */ 189 | .ck-content .todo-list .todo-list__label > input::before { 190 | display: block; 191 | position: absolute; 192 | box-sizing: border-box; 193 | content: ''; 194 | width: 100%; 195 | height: 100%; 196 | border: 1px solid hsl(0, 0%, 20%); 197 | border-radius: 2px; 198 | transition: 250ms ease-in-out box-shadow, 250ms ease-in-out background, 250ms ease-in-out border; 199 | } 200 | /* ckeditor5-list/theme/todolist.css */ 201 | .ck-content .todo-list .todo-list__label > input::after { 202 | display: block; 203 | position: absolute; 204 | box-sizing: content-box; 205 | pointer-events: none; 206 | content: ''; 207 | left: calc( var(--ck-todo-list-checkmark-size) / 3 ); 208 | top: calc( var(--ck-todo-list-checkmark-size) / 5.3 ); 209 | width: calc( var(--ck-todo-list-checkmark-size) / 5.3 ); 210 | height: calc( var(--ck-todo-list-checkmark-size) / 2.6 ); 211 | border-style: solid; 212 | border-color: transparent; 213 | border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0; 214 | transform: rotate(45deg); 215 | } 216 | /* ckeditor5-list/theme/todolist.css */ 217 | .ck-content .todo-list .todo-list__label > input[checked]::before { 218 | background: hsl(126, 64%, 41%); 219 | border-color: hsl(126, 64%, 41%); 220 | } 221 | /* ckeditor5-list/theme/todolist.css */ 222 | .ck-content .todo-list .todo-list__label > input[checked]::after { 223 | border-color: hsl(0, 0%, 100%); 224 | } 225 | /* ckeditor5-list/theme/todolist.css */ 226 | .ck-content .todo-list .todo-list__label .todo-list__label__description { 227 | vertical-align: middle; 228 | } 229 | /* ckeditor5-image/theme/imagecaption.css */ 230 | .ck-content .image > figcaption { 231 | display: table-caption; 232 | caption-side: bottom; 233 | word-break: break-word; 234 | color: hsl(0, 0%, 20%); 235 | background-color: hsl(0, 0%, 97%); 236 | padding: .6em; 237 | font-size: .75em; 238 | outline-offset: -1px; 239 | } 240 | /* ckeditor5-highlight/theme/highlight.css */ 241 | .ck-content .marker-yellow { 242 | background-color: var(--ck-highlight-marker-yellow); 243 | } 244 | /* ckeditor5-highlight/theme/highlight.css */ 245 | .ck-content .marker-green { 246 | background-color: var(--ck-highlight-marker-green); 247 | } 248 | /* ckeditor5-highlight/theme/highlight.css */ 249 | .ck-content .marker-pink { 250 | background-color: var(--ck-highlight-marker-pink); 251 | } 252 | /* ckeditor5-highlight/theme/highlight.css */ 253 | .ck-content .marker-blue { 254 | background-color: var(--ck-highlight-marker-blue); 255 | } 256 | /* ckeditor5-highlight/theme/highlight.css */ 257 | .ck-content .pen-red { 258 | color: var(--ck-highlight-pen-red); 259 | background-color: transparent; 260 | } 261 | /* ckeditor5-highlight/theme/highlight.css */ 262 | .ck-content .pen-green { 263 | color: var(--ck-highlight-pen-green); 264 | background-color: transparent; 265 | } 266 | /* ckeditor5-horizontal-line/theme/horizontalline.css */ 267 | .ck-content hr { 268 | border-width: 1px 0 0; 269 | border-style: solid; 270 | border-color: hsl(0, 0%, 37%); 271 | margin: 0; 272 | } 273 | /* ckeditor5-code-block/theme/codeblock.css */ 274 | .ck-content pre { 275 | padding: 1em; 276 | color: #353535; 277 | background: hsla(0, 0%, 78%, 0.3); 278 | border: 1px solid hsl(0, 0%, 77%); 279 | border-radius: 2px; 280 | text-align: left; 281 | direction: ltr; 282 | tab-size: 4; 283 | white-space: pre-wrap; 284 | font-style: normal; 285 | min-width: 200px; 286 | } 287 | /* ckeditor5-code-block/theme/codeblock.css */ 288 | .ck-content pre code { 289 | background: unset; 290 | padding: 0; 291 | border-radius: 0; 292 | } 293 | @media print { 294 | /* ckeditor5-page-break/theme/pagebreak.css */ 295 | .ck-content .page-break { 296 | padding: 0; 297 | } 298 | /* ckeditor5-page-break/theme/pagebreak.css */ 299 | .ck-content .page-break::after { 300 | display: none; 301 | } 302 | } -------------------------------------------------------------------------------- /ckanext/showcase/assets/js/showcase-ckeditor.js: -------------------------------------------------------------------------------- 1 | // Enable JavaScript's strict mode. Strict mode catches some common 2 | // programming errors and throws exceptions, prevents some unsafe actions from 3 | // being taken, and disables some confusing and bad JavaScript features. 4 | "use strict"; 5 | 6 | ckan.module('showcase-ckeditor', function ($) { 7 | return { 8 | options: { 9 | site_url: "" 10 | }, 11 | 12 | initialize: function () { 13 | jQuery.proxyAll(this, /_on/); 14 | this.el.ready(this._onReady); 15 | }, 16 | 17 | _onReady: function(){ 18 | var config = {}; 19 | config.toolbar = [ 20 | 'heading', 21 | '|', 22 | 'bold', 'italic','underline', 'code', 23 | '|', 24 | 'outdent', 'indent', 25 | '|', 26 | 'bulletedList', 'numberedList', 27 | '|', 28 | 'horizontalline', 'link', 'blockQuote', 'undo', 'redo', 29 | '|', 30 | 'imageUpload' 31 | ] 32 | 33 | config.image = { 34 | toolbar: [ 35 | 'imageTextAlternative', 36 | '|', 37 | 'imageStyle:alignLeft', 'imageStyle:full', 'imageStyle:alignRight' 38 | ], 39 | styles: ['alignLeft', 'full', 'alignRight'] 40 | } 41 | 42 | config.language = 'en' 43 | 44 | var csrf_field = $('meta[name=csrf_field_name]').attr('content'); 45 | var csrf_token = $('meta[name='+ csrf_field +']').attr('content'); 46 | config.simpleUpload = { 47 | uploadUrl: this.options.site_url + 'showcase_upload', 48 | headers: {'X-CSRFToken': csrf_token} 49 | } 50 | 51 | ClassicEditor 52 | .create( 53 | document.querySelector('#editor'), 54 | config 55 | ) 56 | .catch( error => { 57 | console.error( error.stack ); 58 | } ); 59 | } 60 | 61 | 62 | }; 63 | }); -------------------------------------------------------------------------------- /ckanext/showcase/assets/src/ckeditor.js: -------------------------------------------------------------------------------- 1 | import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; 2 | import EssentialsPlugin from '@ckeditor/ckeditor5-essentials/src/essentials'; 3 | import UploadAdapterPlugin from '@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter'; 4 | import AutoformatPlugin from '@ckeditor/ckeditor5-autoformat/src/autoformat'; 5 | 6 | // ckeditor5-basic-styles 7 | import BoldPlugin from '@ckeditor/ckeditor5-basic-styles/src/bold'; 8 | import ItalicPlugin from '@ckeditor/ckeditor5-basic-styles/src/italic'; 9 | import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline'; 10 | import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough'; 11 | import Code from '@ckeditor/ckeditor5-basic-styles/src/code'; 12 | import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript'; 13 | import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript'; 14 | 15 | import Indent from '@ckeditor/ckeditor5-indent/src/indent'; 16 | import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock'; 17 | 18 | import HorizontalLine from '@ckeditor/ckeditor5-horizontal-line/src/horizontalline'; 19 | 20 | import BlockQuotePlugin from '@ckeditor/ckeditor5-block-quote/src/blockquote'; 21 | import HeadingPlugin from '@ckeditor/ckeditor5-heading/src/heading'; 22 | import ImagePlugin from '@ckeditor/ckeditor5-image/src/image'; 23 | import ImageCaptionPlugin from '@ckeditor/ckeditor5-image/src/imagecaption'; 24 | import ImageStylePlugin from '@ckeditor/ckeditor5-image/src/imagestyle'; 25 | import ImageToolbarPlugin from '@ckeditor/ckeditor5-image/src/imagetoolbar'; 26 | import ImageUploadPlugin from '@ckeditor/ckeditor5-image/src/imageupload'; 27 | import LinkPlugin from '@ckeditor/ckeditor5-link/src/link'; 28 | import ListPlugin from '@ckeditor/ckeditor5-list/src/list'; 29 | import ParagraphPlugin from '@ckeditor/ckeditor5-paragraph/src/paragraph'; 30 | 31 | import SimpleUploadAdapter from '@ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter'; 32 | 33 | export default class ClassicEditor extends ClassicEditorBase {} 34 | 35 | ClassicEditor.builtinPlugins = [ 36 | EssentialsPlugin, 37 | UploadAdapterPlugin, 38 | AutoformatPlugin, 39 | BoldPlugin, 40 | ItalicPlugin, 41 | Underline, 42 | Strikethrough, 43 | Code, 44 | Subscript, 45 | Superscript, 46 | Indent, 47 | IndentBlock, 48 | HorizontalLine, 49 | BlockQuotePlugin, 50 | HeadingPlugin, 51 | ImagePlugin, 52 | ImageCaptionPlugin, 53 | ImageStylePlugin, 54 | ImageToolbarPlugin, 55 | ImageUploadPlugin, 56 | LinkPlugin, 57 | ListPlugin, 58 | ParagraphPlugin, 59 | SimpleUploadAdapter 60 | ]; 61 | 62 | window.ClassicEditor=ClassicEditor; 63 | -------------------------------------------------------------------------------- /ckanext/showcase/assets/webassets.yml: -------------------------------------------------------------------------------- 1 | ckeditor: 2 | filter: rjsmin 3 | output: showcase/%(version)s_ckeditor.js 4 | extra: 5 | preload: 6 | - base/main 7 | contents: 8 | - build/ckeditor.js 9 | - js/showcase-ckeditor.js 10 | 11 | ckanext-showcase-css: 12 | contents: 13 | - ckanext_showcase.css 14 | output: showcase/%(version)s_ckanext_showcase.css 15 | filter: cssrewrite 16 | 17 | ckeditor-content-css: 18 | contents: 19 | - ckeditor-content-style.css 20 | output: showcase/%(version)s_ckeditor-content-style.css 21 | filter: cssrewrite 22 | -------------------------------------------------------------------------------- /ckanext/showcase/cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import click 4 | 5 | from ckanext.showcase import utils 6 | 7 | # Click commands for CKAN 2.9 and above 8 | 9 | 10 | @click.group() 11 | def showcase(): 12 | '''showcase commands 13 | ''' 14 | pass 15 | 16 | 17 | @showcase.command() 18 | def markdown_to_html(): 19 | ''' 20 | showcase markdown-to-html 21 | ''' 22 | utils.markdown_to_html() 23 | 24 | 25 | def get_commands(): 26 | return [showcase] 27 | -------------------------------------------------------------------------------- /ckanext/showcase/i18n/__init__.py: -------------------------------------------------------------------------------- 1 | # this is a namespace package 2 | try: 3 | import pkg_resources 4 | pkg_resources.declare_namespace(__name__) 5 | except ImportError: 6 | import pkgutil 7 | __path__ = pkgutil.extend_path(__path__, __name__) 8 | -------------------------------------------------------------------------------- /ckanext/showcase/i18n/ckanext-showcase.pot: -------------------------------------------------------------------------------- 1 | # Translations template for ckanext-showcase. 2 | # Copyright (C) 2015 ORGANIZATION 3 | # This file is distributed under the same license as the ckanext-showcase 4 | # project. 5 | # FIRST AUTHOR , 2015. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: ckanext-showcase 1.0.0b1\n" 11 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 12 | "POT-Creation-Date: 2015-09-23 14:49+0100\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 0.9.6\n" 20 | 21 | #: ckanext/showcase/controller.py:56 22 | msgid "Unauthorized to create a package" 23 | msgstr "" 24 | 25 | #: ckanext/showcase/controller.py:71 ckanext/showcase/controller.py:267 26 | msgid "User not authorized to edit {showcase_id}" 27 | msgstr "" 28 | 29 | #: ckanext/showcase/controller.py:147 ckanext/showcase/controller.py:184 30 | #: ckanext/showcase/controller.py:224 ckanext/showcase/controller.py:238 31 | #: ckanext/showcase/controller.py:274 32 | msgid "Showcase not found" 33 | msgstr "" 34 | 35 | #: ckanext/showcase/controller.py:149 ckanext/showcase/controller.py:276 36 | msgid "Unauthorized to read showcase" 37 | msgstr "" 38 | 39 | #: ckanext/showcase/controller.py:171 ckanext/showcase/controller.py:182 40 | msgid "Unauthorized to delete showcase" 41 | msgstr "" 42 | 43 | #: ckanext/showcase/controller.py:176 44 | msgid "Showcase has been deleted." 45 | msgstr "" 46 | 47 | #: ckanext/showcase/controller.py:201 ckanext/showcase/controller.py:210 48 | msgid "Dataset not found" 49 | msgstr "" 50 | 51 | #: ckanext/showcase/controller.py:203 52 | msgid "Not authorized to see this page" 53 | msgstr "" 54 | 55 | #: ckanext/showcase/controller.py:212 56 | msgid "Unauthorized to read package" 57 | msgstr "" 58 | 59 | #: ckanext/showcase/controller.py:227 ckanext/showcase/controller.py:329 60 | msgid "The dataset has been added to the showcase." 61 | msgid_plural "The datasets have been added to the showcase." 62 | msgstr[0] "" 63 | msgstr[1] "" 64 | 65 | #: ckanext/showcase/controller.py:241 ckanext/showcase/controller.py:297 66 | msgid "The dataset has been removed from the showcase." 67 | msgid_plural "The datasets have been removed from the showcase." 68 | msgstr[0] "" 69 | msgstr[1] "" 70 | 71 | #: ckanext/showcase/controller.py:462 ckanext/showcase/templates/header.html:6 72 | msgid "Organizations" 73 | msgstr "" 74 | 75 | #: ckanext/showcase/controller.py:463 ckanext/showcase/templates/header.html:7 76 | #: ckanext/showcase/templates/package/read_base.html:5 77 | msgid "Groups" 78 | msgstr "" 79 | 80 | #: ckanext/showcase/controller.py:464 ckanext/showcase/plugin.py:103 81 | #: ckanext/showcase/templates/showcase/new_package_form.html:33 82 | msgid "Tags" 83 | msgstr "" 84 | 85 | #: ckanext/showcase/controller.py:465 86 | msgid "Formats" 87 | msgstr "" 88 | 89 | #: ckanext/showcase/controller.py:466 90 | msgid "Licenses" 91 | msgstr "" 92 | 93 | #: ckanext/showcase/controller.py:516 94 | msgid "Parameter '{parameter_name}' is not an integer" 95 | msgstr "" 96 | 97 | #: ckanext/showcase/controller.py:531 ckanext/showcase/controller.py:567 98 | msgid "User not authorized to view page" 99 | msgstr "" 100 | 101 | #: ckanext/showcase/controller.py:540 ckanext/showcase/controller.py:581 102 | msgid "Unauthorized to perform that action" 103 | msgstr "" 104 | 105 | #: ckanext/showcase/controller.py:542 106 | msgid "User '{user_name}' not found." 107 | msgstr "" 108 | 109 | #: ckanext/showcase/controller.py:547 110 | msgid "The user is now a Showcase Admin" 111 | msgstr "" 112 | 113 | #: ckanext/showcase/controller.py:583 114 | msgid "The user is not a Showcase Admin" 115 | msgstr "" 116 | 117 | #: ckanext/showcase/controller.py:585 118 | msgid "The user is no longer a Showcase Admin" 119 | msgstr "" 120 | 121 | #: ckanext/showcase/logic/converters.py:24 ckanext/showcase/logic/validators.py:29 122 | msgid "Not found" 123 | msgstr "" 124 | 125 | #: ckanext/showcase/logic/converters.py:24 ckanext/showcase/logic/validators.py:29 126 | #: ckanext/showcase/templates/package/read_base.html:4 127 | msgid "Dataset" 128 | msgstr "" 129 | 130 | #: ckanext/showcase/templates/header.html:5 131 | msgid "Datasets" 132 | msgstr "" 133 | 134 | #: ckanext/showcase/templates/header.html:8 135 | #: ckanext/showcase/templates/package/dataset_showcase_list.html:3 136 | #: ckanext/showcase/templates/package/read_base.html:7 137 | #: ckanext/showcase/templates/showcase/edit_base.html:5 138 | #: ckanext/showcase/templates/showcase/edit_base.html:17 139 | #: ckanext/showcase/templates/showcase/edit_base.html:21 140 | #: ckanext/showcase/templates/showcase/read.html:6 141 | #: ckanext/showcase/templates/showcase/read.html:33 142 | #: ckanext/showcase/templates/showcase/search.html:4 143 | #: ckanext/showcase/templates/showcase/search.html:7 144 | msgid "Showcases" 145 | msgstr "" 146 | 147 | #: ckanext/showcase/templates/header.html:9 148 | msgid "About" 149 | msgstr "" 150 | 151 | #: ckanext/showcase/templates/admin/confirm_remove_showcase_admin.html:3 152 | #: ckanext/showcase/templates/admin/confirm_remove_showcase_admin.html:16 153 | msgid "Confirm Remove" 154 | msgstr "" 155 | 156 | #: ckanext/showcase/templates/admin/confirm_remove_showcase_admin.html:11 157 | msgid "Are you sure you want to remove this user as a Showcase Admin - {name}?" 158 | msgstr "" 159 | 160 | #: ckanext/showcase/templates/admin/confirm_remove_showcase_admin.html:15 161 | #: ckanext/showcase/templates/showcase/confirm_delete.html:14 162 | msgid "Cancel" 163 | msgstr "" 164 | 165 | #: ckanext/showcase/templates/admin/manage_showcase_admins.html:9 166 | msgid "Manage Showcase Admins" 167 | msgstr "" 168 | 169 | #: ckanext/showcase/templates/admin/manage_showcase_admins.html:16 170 | msgid "Add an Existing User" 171 | msgstr "" 172 | 173 | #: ckanext/showcase/templates/admin/manage_showcase_admins.html:19 174 | msgid "To make an existing user a Showcase Admin, search for their username below." 175 | msgstr "" 176 | 177 | #: ckanext/showcase/templates/admin/manage_showcase_admins.html:23 178 | msgid "Username" 179 | msgstr "" 180 | 181 | #: ckanext/showcase/templates/admin/manage_showcase_admins.html:32 182 | msgid "Add User" 183 | msgstr "" 184 | 185 | #: ckanext/showcase/templates/admin/manage_showcase_admins.html:38 186 | msgid "Showcase Admins" 187 | msgstr "" 188 | 189 | #: ckanext/showcase/templates/admin/manage_showcase_admins.html:43 190 | msgid "User" 191 | msgstr "" 192 | 193 | #: ckanext/showcase/templates/admin/manage_showcase_admins.html:51 194 | msgid "Are you sure you want to remove this user from the Showcase Admin list?" 195 | msgstr "" 196 | 197 | #: ckanext/showcase/templates/admin/manage_showcase_admins.html:53 198 | #: ckanext/showcase/templates/showcase/snippets/showcase_item.html:46 199 | msgid "Remove" 200 | msgstr "" 201 | 202 | #: ckanext/showcase/templates/admin/manage_showcase_admins.html:61 203 | msgid "There are currently no Showcase Admins." 204 | msgstr "" 205 | 206 | #: ckanext/showcase/templates/admin/manage_showcase_admins.html:69 207 | msgid "" 208 | "

Showcase Admin: Can create and remove showcases. Can add" 209 | " and remove datasets from showcases.

" 210 | msgstr "" 211 | 212 | #: ckanext/showcase/templates/home/snippets/stats.html:5 213 | msgid "{site_title} statistics" 214 | msgstr "" 215 | 216 | #: ckanext/showcase/templates/home/snippets/stats.html:10 217 | msgid "dataset" 218 | msgid_plural "datasets" 219 | msgstr[0] "" 220 | msgstr[1] "" 221 | 222 | #: ckanext/showcase/templates/home/snippets/stats.html:16 223 | msgid "organization" 224 | msgid_plural "organizations" 225 | msgstr[0] "" 226 | msgstr[1] "" 227 | 228 | #: ckanext/showcase/templates/home/snippets/stats.html:22 229 | msgid "group" 230 | msgid_plural "groups" 231 | msgstr[0] "" 232 | msgstr[1] "" 233 | 234 | #: ckanext/showcase/templates/home/snippets/stats.html:28 235 | msgid "showcase" 236 | msgid_plural "showcases" 237 | msgstr[0] "" 238 | msgstr[1] "" 239 | 240 | #: ckanext/showcase/templates/package/dataset_showcase_list.html:13 241 | msgid "Associate this showcase with this dataset" 242 | msgstr "" 243 | 244 | #: ckanext/showcase/templates/package/dataset_showcase_list.html:13 245 | msgid "Add to showcase" 246 | msgstr "" 247 | 248 | #: ckanext/showcase/templates/package/dataset_showcase_list.html:17 249 | msgid "Showcases featuring {dataset_name}" 250 | msgstr "" 251 | 252 | #: ckanext/showcase/templates/package/dataset_showcase_list.html:22 253 | msgid "There are no showcases that feature this dataset" 254 | msgstr "" 255 | 256 | #: ckanext/showcase/templates/package/read_base.html:6 257 | msgid "Activity Stream" 258 | msgstr "" 259 | 260 | #: ckanext/showcase/templates/showcase/add_datasets.html:3 261 | msgid "Showcases - Add datasets" 262 | msgstr "" 263 | 264 | #: ckanext/showcase/templates/showcase/add_datasets.html:21 265 | #: ckanext/showcase/templates/showcase/manage_datasets.html:24 266 | #: ckanext/showcase/templates/showcase/search.html:27 267 | msgid "Relevance" 268 | msgstr "" 269 | 270 | #: ckanext/showcase/templates/showcase/add_datasets.html:22 271 | #: ckanext/showcase/templates/showcase/manage_datasets.html:25 272 | #: ckanext/showcase/templates/showcase/search.html:28 273 | msgid "Name Ascending" 274 | msgstr "" 275 | 276 | #: ckanext/showcase/templates/showcase/add_datasets.html:23 277 | #: ckanext/showcase/templates/showcase/manage_datasets.html:26 278 | #: ckanext/showcase/templates/showcase/search.html:29 279 | msgid "Name Descending" 280 | msgstr "" 281 | 282 | #: ckanext/showcase/templates/showcase/add_datasets.html:24 283 | #: ckanext/showcase/templates/showcase/manage_datasets.html:27 284 | #: ckanext/showcase/templates/showcase/search.html:30 285 | msgid "Last Modified" 286 | msgstr "" 287 | 288 | #: ckanext/showcase/templates/showcase/add_datasets.html:25 289 | #: ckanext/showcase/templates/showcase/manage_datasets.html:28 290 | #: ckanext/showcase/templates/showcase/search.html:31 291 | msgid "Popular" 292 | msgstr "" 293 | 294 | #: ckanext/showcase/templates/showcase/add_datasets.html:31 295 | #: ckanext/showcase/templates/showcase/manage_datasets.html:36 296 | msgid "Datasets available to add to this showcase" 297 | msgstr "" 298 | 299 | #: ckanext/showcase/templates/showcase/add_datasets.html:48 300 | #: ckanext/showcase/templates/showcase/manage_datasets.html:51 301 | msgid "Add to Showcase" 302 | msgstr "" 303 | 304 | #: ckanext/showcase/templates/showcase/add_datasets.html:78 305 | #: ckanext/showcase/templates/showcase/manage_datasets.html:88 306 | msgid "No datasets could be found" 307 | msgstr "" 308 | 309 | #: ckanext/showcase/templates/showcase/confirm_delete.html:3 310 | #: ckanext/showcase/templates/showcase/confirm_delete.html:15 311 | msgid "Confirm Delete" 312 | msgstr "" 313 | 314 | #: ckanext/showcase/templates/showcase/confirm_delete.html:11 315 | msgid "Are you sure you want to delete showcase - {showcase_name}?" 316 | msgstr "" 317 | 318 | #: ckanext/showcase/templates/showcase/edit_base.html:19 319 | msgid "Edit" 320 | msgstr "" 321 | 322 | #: ckanext/showcase/templates/showcase/edit_base.html:22 323 | #: ckanext/showcase/templates/showcase/new.html:3 324 | #: ckanext/showcase/templates/showcase/new_package_form.html:63 325 | msgid "Create Showcase" 326 | msgstr "" 327 | 328 | #: ckanext/showcase/templates/showcase/edit_base.html:35 329 | msgid "View showcase" 330 | msgstr "" 331 | 332 | #: ckanext/showcase/templates/showcase/edit_base.html:41 333 | msgid "Edit showcase" 334 | msgstr "" 335 | 336 | #: ckanext/showcase/templates/showcase/edit_base.html:42 337 | msgid "Manage datasets" 338 | msgstr "" 339 | 340 | #: ckanext/showcase/templates/showcase/manage_datasets.html:3 341 | msgid "Showcases - Manage datasets" 342 | msgstr "" 343 | 344 | #: ckanext/showcase/templates/showcase/manage_datasets.html:96 345 | msgid "Datasets in this showcase" 346 | msgstr "" 347 | 348 | #: ckanext/showcase/templates/showcase/manage_datasets.html:109 349 | msgid "Remove from Showcase" 350 | msgstr "" 351 | 352 | #: ckanext/showcase/templates/showcase/manage_datasets.html:140 353 | msgid "This showcase has no datasets associated to it" 354 | msgstr "" 355 | 356 | #: ckanext/showcase/templates/showcase/new.html:8 357 | msgid "Create a Showcase" 358 | msgstr "" 359 | 360 | #: ckanext/showcase/templates/showcase/new_package_form.html:15 361 | msgid "Title" 362 | msgstr "" 363 | 364 | #: ckanext/showcase/templates/showcase/new_package_form.html:15 365 | msgid "eg. A descriptive title" 366 | msgstr "" 367 | 368 | #: ckanext/showcase/templates/showcase/new_package_form.html:24 369 | msgid "URL" 370 | msgstr "" 371 | 372 | #: ckanext/showcase/templates/showcase/new_package_form.html:24 373 | msgid "eg. my-showcase" 374 | msgstr "" 375 | 376 | #: ckanext/showcase/templates/showcase/new_package_form.html:28 377 | msgid "Description" 378 | msgstr "" 379 | 380 | #: ckanext/showcase/templates/showcase/new_package_form.html:28 381 | msgid "eg. Some useful notes about the data" 382 | msgstr "" 383 | 384 | #: ckanext/showcase/templates/showcase/new_package_form.html:33 385 | msgid "eg. economy, mental health, government" 386 | msgstr "" 387 | 388 | #: ckanext/showcase/templates/showcase/new_package_form.html:44 389 | msgid "External link" 390 | msgstr "" 391 | 392 | #: ckanext/showcase/templates/showcase/new_package_form.html:44 393 | msgid "http://www.example.com" 394 | msgstr "" 395 | 396 | #: ckanext/showcase/templates/showcase/new_package_form.html:48 397 | msgid "Submitted By" 398 | msgstr "" 399 | 400 | #: ckanext/showcase/templates/showcase/new_package_form.html:48 401 | msgid "Joe Bloggs" 402 | msgstr "" 403 | 404 | #: ckanext/showcase/templates/showcase/new_package_form.html:50 405 | msgid "Submitter Email" 406 | msgstr "" 407 | 408 | #: ckanext/showcase/templates/showcase/new_package_form.html:50 409 | msgid "joe@example.com" 410 | msgstr "" 411 | 412 | #: ckanext/showcase/templates/showcase/new_package_form.html:58 413 | msgid "Are you sure you want to delete this showcase?" 414 | msgstr "" 415 | 416 | #: ckanext/showcase/templates/showcase/new_package_form.html:59 417 | msgid "Delete" 418 | msgstr "" 419 | 420 | #: ckanext/showcase/templates/showcase/new_package_form.html:63 421 | msgid "Update Showcase" 422 | msgstr "" 423 | 424 | #: ckanext/showcase/templates/showcase/read.html:46 425 | msgid "Manage" 426 | msgstr "" 427 | 428 | #: ckanext/showcase/templates/showcase/read.html:53 429 | msgid "Private" 430 | msgstr "" 431 | 432 | #: ckanext/showcase/templates/showcase/read.html:60 433 | msgid "Draft" 434 | msgstr "" 435 | 436 | #: ckanext/showcase/templates/showcase/read.html:78 437 | #: ckanext/showcase/templates/showcase/snippets/showcase_info.html:27 438 | msgid "Launch website" 439 | msgstr "" 440 | 441 | #: ckanext/showcase/templates/showcase/search.html:13 442 | msgid "Add Showcase" 443 | msgstr "" 444 | 445 | #: ckanext/showcase/templates/showcase/search.html:33 446 | msgid "Search showcases..." 447 | msgstr "" 448 | 449 | #: ckanext/showcase/templates/showcase/snippets/helper.html:4 450 | msgid "What are Showcases?" 451 | msgstr "" 452 | 453 | #: ckanext/showcase/templates/showcase/snippets/helper.html:8 454 | msgid "" 455 | "Datasets have been used to build apps, websites and visualizations. They've " 456 | "been featured in articles, and written about in news reports and blog posts. " 457 | "Showcases collect together the best examples of datasets in use, to provide " 458 | "further insight, ideas, and inspiration." 459 | msgstr "" 460 | 461 | #: ckanext/showcase/templates/showcase/snippets/helper.html:12 462 | #, python-format 463 | msgid "" 464 | "Sysadmins can manage Showcase Admins from the Showcase " 465 | "configuration page." 466 | msgstr "" 467 | 468 | #: ckanext/showcase/templates/showcase/snippets/showcase_info.html:21 469 | msgid "Submitted by" 470 | msgstr "" 471 | 472 | #: ckanext/showcase/templates/showcase/snippets/showcase_info.html:37 473 | msgid "Datasets in Showcase" 474 | msgstr "" 475 | 476 | #: ckanext/showcase/templates/showcase/snippets/showcase_info.html:47 477 | msgid "There are no Datasets in this Showcase" 478 | msgstr "" 479 | 480 | #: ckanext/showcase/templates/showcase/snippets/showcase_item.html:30 481 | msgid "This showcase has no description" 482 | msgstr "" 483 | 484 | #: ckanext/showcase/templates/showcase/snippets/showcase_item.html:35 485 | msgid "{num} Dataset" 486 | msgid_plural "{num} Datasets" 487 | msgstr[0] "" 488 | msgstr[1] "" 489 | 490 | #: ckanext/showcase/templates/showcase/snippets/showcase_item.html:37 491 | msgid "0 Datasets" 492 | msgstr "" 493 | 494 | #: ckanext/showcase/templates/showcase/snippets/showcase_item.html:41 495 | #: ckanext/showcase/templates/showcase/snippets/showcase_item.html:42 496 | msgid "View {showcase_title}" 497 | msgstr "" 498 | 499 | #: ckanext/showcase/templates/showcase/snippets/showcase_item.html:46 500 | msgid "Remove dataset from this showcase" 501 | msgstr "" 502 | 503 | #: ckanext/showcase/templates/showcase/snippets/showcase_search_result_text.html:7 504 | msgid "{number} showcase found for '{query}'" 505 | msgid_plural "{number} showcases found for '{query}'" 506 | msgstr[0] "" 507 | msgstr[1] "" 508 | 509 | #: ckanext/showcase/templates/showcase/snippets/showcase_search_result_text.html:8 510 | msgid "No showcases found for '{query}'" 511 | msgstr "" 512 | 513 | #: ckanext/showcase/templates/showcase/snippets/showcase_search_result_text.html:9 514 | msgid "{number} showcase found" 515 | msgid_plural "{number} showcases found" 516 | msgstr[0] "" 517 | msgstr[1] "" 518 | 519 | #: ckanext/showcase/templates/showcase/snippets/showcase_search_result_text.html:10 520 | msgid "No showcases found" 521 | msgstr "" 522 | 523 | -------------------------------------------------------------------------------- /ckanext/showcase/i18n/de/LC_MESSAGES/ckanext-showcase.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-showcase/6a9b6e96c4482b9becbfb0bdd27083d51b4900ed/ckanext/showcase/i18n/de/LC_MESSAGES/ckanext-showcase.mo -------------------------------------------------------------------------------- /ckanext/showcase/i18n/en_AU/LC_MESSAGES/ckanext-showcase.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-showcase/6a9b6e96c4482b9becbfb0bdd27083d51b4900ed/ckanext/showcase/i18n/en_AU/LC_MESSAGES/ckanext-showcase.mo -------------------------------------------------------------------------------- /ckanext/showcase/i18n/es/LC_MESSAGES/ckanext-showcase.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-showcase/6a9b6e96c4482b9becbfb0bdd27083d51b4900ed/ckanext/showcase/i18n/es/LC_MESSAGES/ckanext-showcase.mo -------------------------------------------------------------------------------- /ckanext/showcase/i18n/fr/LC_MESSAGES/ckanext-showcase.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-showcase/6a9b6e96c4482b9becbfb0bdd27083d51b4900ed/ckanext/showcase/i18n/fr/LC_MESSAGES/ckanext-showcase.mo -------------------------------------------------------------------------------- /ckanext/showcase/i18n/zh_Hant_TW/LC_MESSAGES/ckanext-showcase.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-showcase/6a9b6e96c4482b9becbfb0bdd27083d51b4900ed/ckanext/showcase/i18n/zh_Hant_TW/LC_MESSAGES/ckanext-showcase.mo -------------------------------------------------------------------------------- /ckanext/showcase/logic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-showcase/6a9b6e96c4482b9becbfb0bdd27083d51b4900ed/ckanext/showcase/logic/__init__.py -------------------------------------------------------------------------------- /ckanext/showcase/logic/action/__init__.py: -------------------------------------------------------------------------------- 1 | import ckanext.showcase.logic.action.create 2 | import ckanext.showcase.logic.action.delete 3 | import ckanext.showcase.logic.action.update 4 | import ckanext.showcase.logic.action.get 5 | 6 | 7 | def get_actions(): 8 | action_functions = { 9 | 'ckanext_showcase_create': 10 | ckanext.showcase.logic.action.create.showcase_create, 11 | 'ckanext_showcase_update': 12 | ckanext.showcase.logic.action.update.showcase_update, 13 | 'ckanext_showcase_delete': 14 | ckanext.showcase.logic.action.delete.showcase_delete, 15 | 'ckanext_showcase_show': 16 | ckanext.showcase.logic.action.get.showcase_show, 17 | 'ckanext_showcase_list': 18 | ckanext.showcase.logic.action.get.showcase_list, 19 | 'ckanext_showcase_package_association_create': 20 | ckanext.showcase.logic.action.create.showcase_package_association_create, 21 | 'ckanext_showcase_package_association_delete': 22 | ckanext.showcase.logic.action.delete.showcase_package_association_delete, 23 | 'ckanext_showcase_package_list': 24 | ckanext.showcase.logic.action.get.showcase_package_list, 25 | 'ckanext_package_showcase_list': 26 | ckanext.showcase.logic.action.get.package_showcase_list, 27 | 'ckanext_showcase_admin_add': 28 | ckanext.showcase.logic.action.create.showcase_admin_add, 29 | 'ckanext_showcase_admin_remove': 30 | ckanext.showcase.logic.action.delete.showcase_admin_remove, 31 | 'ckanext_showcase_admin_list': 32 | ckanext.showcase.logic.action.get.showcase_admin_list, 33 | 'ckanext_showcase_upload': 34 | ckanext.showcase.logic.action.create.showcase_upload, 35 | } 36 | return action_functions 37 | -------------------------------------------------------------------------------- /ckanext/showcase/logic/action/create.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import ckan.lib.uploader as uploader 4 | import ckan.lib.helpers as h 5 | import ckan.plugins.toolkit as toolkit 6 | from ckan.logic.converters import convert_user_name_or_id_to_id 7 | from ckan.lib.navl.dictization_functions import validate 8 | 9 | import ckanext.showcase.logic.converters as showcase_converters 10 | import ckanext.showcase.logic.schema as showcase_schema 11 | from ckanext.showcase.model import ShowcasePackageAssociation, ShowcaseAdmin 12 | 13 | convert_package_name_or_id_to_title_or_name = \ 14 | showcase_converters.convert_package_name_or_id_to_title_or_name 15 | showcase_package_association_create_schema = \ 16 | showcase_schema.showcase_package_association_create_schema 17 | showcase_admin_add_schema = showcase_schema.showcase_admin_add_schema 18 | 19 | log = logging.getLogger(__name__) 20 | 21 | 22 | def showcase_create(context, data_dict): 23 | '''Upload the image and continue with package creation.''' 24 | 25 | # force type to 'showcase' 26 | data_dict['type'] = 'showcase' 27 | upload = uploader.get_uploader('showcase') 28 | 29 | upload.update_data_dict(data_dict, 'image_url', 30 | 'image_upload', 'clear_upload') 31 | 32 | upload.upload(uploader.get_max_image_size()) 33 | 34 | pkg = toolkit.get_action('package_create')(context, data_dict) 35 | 36 | return pkg 37 | 38 | 39 | def showcase_package_association_create(context, data_dict): 40 | '''Create an association between a showcase and a package. 41 | 42 | :param showcase_id: id or name of the showcase to associate 43 | :type showcase_id: string 44 | 45 | :param package_id: id or name of the package to associate 46 | :type package_id: string 47 | ''' 48 | 49 | toolkit.check_access('ckanext_showcase_package_association_create', 50 | context, data_dict) 51 | 52 | # validate the incoming data_dict 53 | validated_data_dict, errors = validate( 54 | data_dict, showcase_package_association_create_schema(), context) 55 | 56 | if errors: 57 | raise toolkit.ValidationError(errors) 58 | 59 | package_id, showcase_id = toolkit.get_or_bust(validated_data_dict, 60 | ['package_id', 61 | 'showcase_id']) 62 | 63 | if ShowcasePackageAssociation.exists(package_id=package_id, 64 | showcase_id=showcase_id): 65 | raise toolkit.ValidationError("ShowcasePackageAssociation with package_id '{0}' and showcase_id '{1}' already exists.".format(package_id, showcase_id), 66 | error_summary=u"The dataset, {0}, is already in the showcase".format(convert_package_name_or_id_to_title_or_name(package_id, context))) 67 | 68 | # create the association 69 | return ShowcasePackageAssociation.create(package_id=package_id, 70 | showcase_id=showcase_id) 71 | 72 | 73 | def showcase_admin_add(context, data_dict): 74 | '''Add a user to the list of showcase admins. 75 | 76 | :param username: name of the user to add to showcase user admin list 77 | :type username: string 78 | ''' 79 | 80 | toolkit.check_access('ckanext_showcase_admin_add', context, data_dict) 81 | 82 | # validate the incoming data_dict 83 | validated_data_dict, errors = validate( 84 | data_dict, showcase_admin_add_schema(), context) 85 | 86 | username = toolkit.get_or_bust(validated_data_dict, 'username') 87 | try: 88 | user_id = convert_user_name_or_id_to_id(username, context) 89 | except toolkit.Invalid: 90 | raise toolkit.ObjectNotFound 91 | 92 | if errors: 93 | raise toolkit.ValidationError(errors) 94 | 95 | if ShowcaseAdmin.exists(user_id=user_id): 96 | raise toolkit.ValidationError("ShowcaseAdmin with user_id '{0}' already exists.".format(user_id), 97 | error_summary=u"User '{0}' is already a Showcase Admin.".format(username)) 98 | 99 | # create showcase admin entry 100 | return ShowcaseAdmin.create(user_id=user_id) 101 | 102 | 103 | def showcase_upload(context, data_dict): 104 | ''' Uploads images to be used in showcase content. 105 | 106 | ''' 107 | toolkit.check_access('ckanext_showcase_upload', context, data_dict) 108 | 109 | upload = uploader.get_uploader('showcase_image') 110 | 111 | upload.update_data_dict(data_dict, 'image_url', 'upload', 'clear_upload') 112 | upload.upload(uploader.get_max_image_size()) 113 | 114 | image_url = data_dict.get('image_url') 115 | if image_url and image_url[0:6] not in {'http:/', 'https:'}: 116 | image_url = h.url_for_static( 117 | 'uploads/showcase_image/{}'.format(image_url), 118 | qualified=True 119 | ) 120 | return {'url': image_url} 121 | -------------------------------------------------------------------------------- /ckanext/showcase/logic/action/delete.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import ckan.plugins.toolkit as toolkit 4 | from ckan.logic.converters import convert_user_name_or_id_to_id 5 | import ckan.lib.navl.dictization_functions 6 | 7 | from ckanext.showcase.logic.schema import ( 8 | showcase_package_association_delete_schema, 9 | showcase_admin_remove_schema) 10 | 11 | from ckanext.showcase.model import ShowcasePackageAssociation, ShowcaseAdmin 12 | 13 | validate = ckan.lib.navl.dictization_functions.validate 14 | 15 | log = logging.getLogger(__name__) 16 | 17 | 18 | def showcase_delete(context, data_dict): 19 | '''Delete a showcase. Showcase delete cascades to 20 | ShowcasePackageAssociation objects. 21 | 22 | :param id: the id or name of the showcase to delete 23 | :type id: string 24 | ''' 25 | 26 | model = context['model'] 27 | id = toolkit.get_or_bust(data_dict, 'id') 28 | 29 | entity = model.Package.get(id) 30 | 31 | if entity is None: 32 | raise toolkit.ObjectNotFound 33 | 34 | toolkit.check_access('ckanext_showcase_delete', context, data_dict) 35 | 36 | entity.purge() 37 | model.repo.commit() 38 | 39 | 40 | def showcase_package_association_delete(context, data_dict): 41 | '''Delete an association between a showcase and a package. 42 | 43 | :param showcase_id: id or name of the showcase in the association 44 | :type showcase_id: string 45 | 46 | :param package_id: id or name of the package in the association 47 | :type package_id: string 48 | ''' 49 | 50 | model = context['model'] 51 | 52 | toolkit.check_access('ckanext_showcase_package_association_delete', 53 | context, data_dict) 54 | 55 | # validate the incoming data_dict 56 | validated_data_dict, errors = validate( 57 | data_dict, showcase_package_association_delete_schema(), context) 58 | 59 | if errors: 60 | raise toolkit.ValidationError(errors) 61 | 62 | package_id, showcase_id = toolkit.get_or_bust(validated_data_dict, 63 | ['package_id', 64 | 'showcase_id']) 65 | 66 | showcase_package_association = ShowcasePackageAssociation.get( 67 | package_id=package_id, showcase_id=showcase_id) 68 | 69 | if showcase_package_association is None: 70 | raise toolkit.ObjectNotFound("ShowcasePackageAssociation with package_id '{0}' and showcase_id '{1}' doesn't exist.".format(package_id, showcase_id)) 71 | 72 | # delete the association 73 | showcase_package_association.delete() 74 | model.repo.commit() 75 | 76 | 77 | def showcase_admin_remove(context, data_dict): 78 | '''Remove a user to the list of showcase admins. 79 | 80 | :param username: name of the user to remove from showcase user admin list 81 | :type username: string 82 | ''' 83 | 84 | model = context['model'] 85 | 86 | toolkit.check_access('ckanext_showcase_admin_remove', context, data_dict) 87 | 88 | # validate the incoming data_dict 89 | validated_data_dict, errors = validate(data_dict, 90 | showcase_admin_remove_schema(), 91 | context) 92 | 93 | if errors: 94 | raise toolkit.ValidationError(errors) 95 | 96 | username = toolkit.get_or_bust(validated_data_dict, 'username') 97 | user_id = convert_user_name_or_id_to_id(username, context) 98 | 99 | showcase_admin_to_remove = ShowcaseAdmin.get(user_id=user_id) 100 | 101 | if showcase_admin_to_remove is None: 102 | raise toolkit.ObjectNotFound("ShowcaseAdmin with user_id '{0}' doesn't exist.".format(user_id)) 103 | 104 | showcase_admin_to_remove.delete() 105 | model.repo.commit() 106 | -------------------------------------------------------------------------------- /ckanext/showcase/logic/action/get.py: -------------------------------------------------------------------------------- 1 | import ckan.plugins.toolkit as toolkit 2 | import ckan.lib.dictization.model_dictize as model_dictize 3 | from ckan.lib.navl.dictization_functions import validate 4 | 5 | from ckanext.showcase.logic.schema import (showcase_package_list_schema, 6 | package_showcase_list_schema) 7 | from ckanext.showcase.model import ShowcasePackageAssociation, ShowcaseAdmin 8 | 9 | import logging 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | @toolkit.side_effect_free 14 | def showcase_show(context, data_dict): 15 | '''Return the pkg_dict for a showcase (package). 16 | 17 | :param id: the id or name of the showcase 18 | :type id: string 19 | ''' 20 | 21 | toolkit.check_access('ckanext_showcase_show', context, data_dict) 22 | 23 | pkg_dict = toolkit.get_action('package_show')(context, data_dict) 24 | 25 | return pkg_dict 26 | 27 | 28 | @toolkit.side_effect_free 29 | def showcase_list(context, data_dict): 30 | '''Return a list of all showcases in the site.''' 31 | 32 | toolkit.check_access('ckanext_showcase_list', context, data_dict) 33 | 34 | model = context["model"] 35 | 36 | q = model.Session.query(model.Package) \ 37 | .filter(model.Package.type == 'showcase') \ 38 | .filter(model.Package.state == 'active') 39 | 40 | showcase_list = [] 41 | for pkg in q.all(): 42 | showcase_list.append(model_dictize.package_dictize(pkg, context)) 43 | 44 | return showcase_list 45 | 46 | 47 | @toolkit.side_effect_free 48 | def showcase_package_list(context, data_dict): 49 | '''List packages associated with a showcase. 50 | 51 | :param showcase_id: id or name of the showcase 52 | :type showcase_id: string 53 | 54 | :rtype: list of dictionaries 55 | ''' 56 | 57 | toolkit.check_access('ckanext_showcase_package_list', context, data_dict) 58 | 59 | # validate the incoming data_dict 60 | validated_data_dict, errors = validate(data_dict, 61 | showcase_package_list_schema(), 62 | context) 63 | 64 | if errors: 65 | raise toolkit.ValidationError(errors) 66 | 67 | # get a list of package ids associated with showcase id 68 | pkg_id_list = ShowcasePackageAssociation.get_package_ids_for_showcase( 69 | validated_data_dict['showcase_id']) 70 | 71 | pkg_list = [] 72 | if pkg_id_list: 73 | # for each package id, get the package dict and append to list if 74 | # active 75 | id_list = [] 76 | for pkg_id in pkg_id_list: 77 | id_list.append(pkg_id[0]) 78 | q = 'id:(' + ' OR '.join(['{0}'.format(x) for x in id_list]) + ')' 79 | _pkg_list = toolkit.get_action('package_search')( 80 | context, 81 | {'q': q, 'rows': 100}) 82 | pkg_list = _pkg_list['results'] 83 | return pkg_list 84 | 85 | 86 | @toolkit.side_effect_free 87 | def package_showcase_list(context, data_dict): 88 | '''List showcases associated with a package. 89 | 90 | :param package_id: id or name of the package 91 | :type package_id: string 92 | 93 | :rtype: list of dictionaries 94 | ''' 95 | 96 | toolkit.check_access('ckanext_package_showcase_list', context, data_dict) 97 | 98 | # validate the incoming data_dict 99 | validated_data_dict, errors = validate(data_dict, 100 | package_showcase_list_schema(), 101 | context) 102 | 103 | if errors: 104 | raise toolkit.ValidationError(errors) 105 | 106 | # get a list of showcase ids associated with the package id 107 | showcase_id_list = ShowcasePackageAssociation.get_showcase_ids_for_package( 108 | validated_data_dict['package_id']) 109 | showcase_list = [] 110 | 111 | q = '' 112 | fq = '' 113 | if showcase_id_list: 114 | id_list = [] 115 | for showcase_id in showcase_id_list: 116 | id_list.append(showcase_id[0]) 117 | fq = 'dataset_type:showcase' 118 | q = 'id:(' + ' OR '.join(['{0}'.format(x) for x in id_list]) + ')' 119 | _showcase_list = toolkit.get_action('package_search')( 120 | context, 121 | {'q': q, 'fq': fq, 'rows': 100}) 122 | showcase_list = _showcase_list['results'] 123 | 124 | return showcase_list 125 | 126 | 127 | @toolkit.side_effect_free 128 | def showcase_admin_list(context, data_dict): 129 | ''' 130 | Return a list of dicts containing the id and name of all active showcase 131 | admin users. 132 | 133 | :rtype: list of dictionaries 134 | ''' 135 | 136 | toolkit.check_access('ckanext_showcase_admin_list', context, data_dict) 137 | 138 | model = context["model"] 139 | 140 | user_ids = ShowcaseAdmin.get_showcase_admin_ids() 141 | 142 | if user_ids: 143 | q = model.Session.query(model.User) \ 144 | .filter(model.User.state == 'active') \ 145 | .filter(model.User.id.in_(user_ids)) 146 | 147 | showcase_admin_list = [] 148 | for user in q.all(): 149 | showcase_admin_list.append({'name': user.name, 'id': user.id}) 150 | return showcase_admin_list 151 | 152 | return [] 153 | -------------------------------------------------------------------------------- /ckanext/showcase/logic/action/update.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import ckan.lib.uploader as uploader 4 | import ckan.plugins.toolkit as toolkit 5 | 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | def showcase_update(context, data_dict): 11 | 12 | upload = uploader.get_uploader('showcase', data_dict['image_url']) 13 | 14 | upload.update_data_dict(data_dict, 'image_url', 15 | 'image_upload', 'clear_upload') 16 | 17 | upload.upload(uploader.get_max_image_size()) 18 | 19 | pkg = toolkit.get_action('package_update')(context, data_dict) 20 | 21 | return pkg 22 | -------------------------------------------------------------------------------- /ckanext/showcase/logic/auth.py: -------------------------------------------------------------------------------- 1 | import ckan.plugins.toolkit as toolkit 2 | import ckan.model as model 3 | 4 | from ckanext.showcase.model import ShowcaseAdmin 5 | 6 | import logging 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | def get_auth_functions(): 11 | return { 12 | 'ckanext_showcase_create': create, 13 | 'ckanext_showcase_update': update, 14 | 'ckanext_showcase_delete': delete, 15 | 'ckanext_showcase_show': show, 16 | 'ckanext_showcase_list': showcase_list, 17 | 'ckanext_showcase_package_association_create': package_association_create, 18 | 'ckanext_showcase_package_association_delete': package_association_delete, 19 | 'ckanext_showcase_package_list': showcase_package_list, 20 | 'ckanext_package_showcase_list': package_showcase_list, 21 | 'ckanext_showcase_admin_add': add_showcase_admin, 22 | 'ckanext_showcase_admin_remove': remove_showcase_admin, 23 | 'ckanext_showcase_admin_list': showcase_admin_list, 24 | 'ckanext_showcase_upload': showcase_upload, 25 | } 26 | 27 | 28 | def _is_showcase_admin(context): 29 | ''' 30 | Determines whether user in context is in the showcase admin list. 31 | ''' 32 | user = context.get('user', '') 33 | userobj = model.User.get(user) 34 | return ShowcaseAdmin.is_user_showcase_admin(userobj) 35 | 36 | 37 | def create(context, data_dict): 38 | '''Create a Showcase. 39 | 40 | Only sysadmin or users listed as Showcase Admins can create a Showcase. 41 | ''' 42 | return {'success': _is_showcase_admin(context)} 43 | 44 | 45 | def delete(context, data_dict): 46 | '''Delete a Showcase. 47 | 48 | Only sysadmin or users listed as Showcase Admins can delete a Showcase. 49 | ''' 50 | return {'success': _is_showcase_admin(context)} 51 | 52 | 53 | def update(context, data_dict): 54 | '''Update a Showcase. 55 | 56 | Only sysadmin or users listed as Showcase Admins can update a Showcase. 57 | ''' 58 | return {'success': _is_showcase_admin(context)} 59 | 60 | 61 | @toolkit.auth_allow_anonymous_access 62 | def show(context, data_dict): 63 | '''All users can access a showcase show''' 64 | return {'success': True} 65 | 66 | 67 | @toolkit.auth_allow_anonymous_access 68 | def showcase_list(context, data_dict): 69 | '''All users can access a showcase list''' 70 | return {'success': True} 71 | 72 | 73 | def package_association_create(context, data_dict): 74 | '''Create a package showcase association. 75 | 76 | Only sysadmins or user listed as Showcase Admins can create a 77 | package/showcase association. 78 | ''' 79 | return {'success': _is_showcase_admin(context)} 80 | 81 | 82 | def package_association_delete(context, data_dict): 83 | '''Delete a package showcase association. 84 | 85 | Only sysadmins or user listed as Showcase Admins can delete a 86 | package/showcase association. 87 | ''' 88 | return {'success': _is_showcase_admin(context)} 89 | 90 | 91 | @toolkit.auth_allow_anonymous_access 92 | def showcase_package_list(context, data_dict): 93 | '''All users can access a showcase's package list''' 94 | return {'success': True} 95 | 96 | 97 | @toolkit.auth_allow_anonymous_access 98 | def package_showcase_list(context, data_dict): 99 | '''All users can access a packages's showcase list''' 100 | return {'success': True} 101 | 102 | 103 | def add_showcase_admin(context, data_dict): 104 | '''Only sysadmins can add users to showcase admin list.''' 105 | return {'success': False} 106 | 107 | 108 | def remove_showcase_admin(context, data_dict): 109 | '''Only sysadmins can remove users from showcase admin list.''' 110 | return {'success': False} 111 | 112 | 113 | def showcase_admin_list(context, data_dict): 114 | '''Only sysadmins can list showcase admin users.''' 115 | return {'success': False} 116 | 117 | 118 | def showcase_upload(context, data_dict): 119 | '''Only sysadmins can upload images.''' 120 | return {'success': _is_showcase_admin(context)} 121 | -------------------------------------------------------------------------------- /ckanext/showcase/logic/converters.py: -------------------------------------------------------------------------------- 1 | import ckan.model as model 2 | import ckan.lib.navl.dictization_functions as df 3 | from ckan.common import _ 4 | 5 | 6 | def convert_package_name_or_id_to_title_or_name(package_name_or_id, context): 7 | ''' 8 | Return the package title, or name if no title, for the given package name 9 | or id. 10 | 11 | :returns: the name of the package with the given name or id 12 | :rtype: string 13 | :raises: ckan.lib.navl.dictization_functions.Invalid if there is no 14 | package with the given name or id 15 | 16 | ''' 17 | session = context['session'] 18 | result = session.query(model.Package).filter_by( 19 | id=package_name_or_id).first() 20 | if not result: 21 | result = session.query(model.Package).filter_by( 22 | name=package_name_or_id).first() 23 | if not result: 24 | raise df.Invalid('%s: %s' % (_('Not found'), _('Dataset'))) 25 | return result.title or result.name 26 | -------------------------------------------------------------------------------- /ckanext/showcase/logic/helpers.py: -------------------------------------------------------------------------------- 1 | import ckan.lib.helpers as h 2 | from ckan.plugins import toolkit as tk 3 | 4 | 5 | def facet_remove_field(key, value=None, replace=None): 6 | ''' 7 | A custom remove field function to be used by the Showcase search page to 8 | render the remove link for the tag pills. 9 | ''' 10 | index_route = 'showcase_blueprint.index' 11 | 12 | return h.remove_url_param( 13 | key, value=value, replace=replace, 14 | alternative_url=h.url_for(index_route)) 15 | 16 | 17 | def get_site_statistics(): 18 | ''' 19 | Custom stats helper, so we can get the correct number of packages, and a 20 | count of showcases. 21 | ''' 22 | 23 | stats = {} 24 | stats['showcase_count'] = tk.get_action('package_search')( 25 | {}, {"rows": 1, 'fq': '+dataset_type:showcase'})['count'] 26 | stats['dataset_count'] = tk.get_action('package_search')( 27 | {}, {"rows": 1, 'fq': '!dataset_type:showcase'})['count'] 28 | stats['group_count'] = len(tk.get_action('group_list')({}, {})) 29 | stats['organization_count'] = len( 30 | tk.get_action('organization_list')({}, {})) 31 | 32 | return stats 33 | 34 | 35 | def showcase_get_wysiwyg_editor(): 36 | return tk.config.get('ckanext.showcase.editor', '') 37 | -------------------------------------------------------------------------------- /ckanext/showcase/logic/schema.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import ckan.plugins.toolkit as toolkit 3 | 4 | from ckan.logic.schema import (default_tags_schema, 5 | default_extras_schema, 6 | default_resource_schema) 7 | 8 | from ckanext.showcase.logic.validators import ( 9 | convert_package_name_or_id_to_id_for_type_dataset, 10 | convert_package_name_or_id_to_id_for_type_showcase) 11 | 12 | if toolkit.check_ckan_version("2.10"): 13 | unicode_safe = toolkit.get_validator("unicode_safe") 14 | else: 15 | unicode_safe = str 16 | 17 | not_empty = toolkit.get_validator("not_empty") 18 | empty = toolkit.get_validator("empty") 19 | if_empty_same_as = toolkit.get_validator("if_empty_same_as") 20 | ignore_missing = toolkit.get_validator("ignore_missing") 21 | ignore = toolkit.get_validator("ignore") 22 | keep_extras = toolkit.get_validator("keep_extras") 23 | 24 | package_id_not_changed = toolkit.get_validator("package_id_not_changed") 25 | name_validator = toolkit.get_validator("name_validator") 26 | user_id_or_name_exists = toolkit.get_validator("user_id_or_name_exists") 27 | package_name_validator = toolkit.get_validator("package_name_validator") 28 | tag_string_convert = toolkit.get_validator("tag_string_convert") 29 | ignore_not_package_admin = toolkit.get_validator("ignore_not_package_admin") 30 | url_validator = toolkit.get_validator("url_validator") 31 | 32 | 33 | def showcase_base_schema(): 34 | schema = { 35 | 'id': [empty], 36 | 'revision_id': [ignore], 37 | 'name': [not_empty, name_validator, package_name_validator], 38 | 'title': [if_empty_same_as("name"), unicode_safe], 39 | 'author': [ignore_missing, unicode_safe], 40 | 'author_email': [ignore_missing, unicode_safe], 41 | 'notes': [ignore_missing, unicode_safe], 42 | 'url': [ignore_missing, url_validator], 43 | 'state': [ignore_not_package_admin, ignore_missing], 44 | 'type': [ignore_missing, unicode_safe], 45 | '__extras': [ignore], 46 | '__junk': [empty], 47 | 'resources': default_resource_schema(), 48 | 'tags': default_tags_schema(), 49 | 'tag_string': [ignore_missing, tag_string_convert], 50 | 'extras': default_extras_schema(), 51 | 'save': [ignore], 52 | 'return_to': [ignore], 53 | 'image_url': [toolkit.get_validator('ignore_missing'), 54 | toolkit.get_converter('convert_to_extras')] 55 | } 56 | return schema 57 | 58 | 59 | def showcase_create_schema(): 60 | return showcase_base_schema() 61 | 62 | 63 | def showcase_update_schema(): 64 | schema = showcase_base_schema() 65 | 66 | # Users can (optionally) supply the package id when updating a package, but 67 | # only to identify the package to be updated, they cannot change the id. 68 | schema['id'] = [ignore_missing, package_id_not_changed] 69 | 70 | # Supplying the package name when updating a package is optional (you can 71 | # supply the id to identify the package instead). 72 | schema['name'] = [ignore_missing, name_validator, 73 | package_name_validator, unicode_safe] 74 | 75 | # Supplying the package title when updating a package is optional, if it's 76 | # not supplied the title will not be changed. 77 | schema['title'] = [ignore_missing, unicode_safe] 78 | 79 | return schema 80 | 81 | 82 | def showcase_show_schema(): 83 | schema = showcase_base_schema() 84 | # Don't strip ids from package dicts when validating them. 85 | schema['id'] = [] 86 | 87 | schema.update({ 88 | 'tags': {'__extras': [keep_extras]}}) 89 | 90 | schema.update({ 91 | 'state': [ignore_missing], 92 | }) 93 | 94 | # Remove validators for several keys from the schema so validation doesn't 95 | # strip the keys from the package dicts if the values are 'missing' (i.e. 96 | # None). 97 | schema['author'] = [] 98 | schema['author_email'] = [] 99 | schema['notes'] = [] 100 | schema['url'] = [] 101 | 102 | # Add several keys that are missing from default_create_package_schema(), 103 | # so validation doesn't strip the keys from the package dicts. 104 | schema['metadata_created'] = [] 105 | schema['metadata_modified'] = [] 106 | schema['creator_user_id'] = [] 107 | schema['num_tags'] = [] 108 | schema['revision_id'] = [ignore_missing] 109 | schema['tracking_summary'] = [ignore_missing] 110 | 111 | schema.update({ 112 | 'image_url': [toolkit.get_converter('convert_from_extras'), 113 | toolkit.get_validator('ignore_missing')], 114 | 'original_related_item_id': [ 115 | toolkit.get_converter('convert_from_extras'), 116 | toolkit.get_validator('ignore_missing')] 117 | }) 118 | 119 | return schema 120 | 121 | 122 | def showcase_package_association_create_schema(): 123 | schema = { 124 | 'package_id': [not_empty, unicode_safe, 125 | convert_package_name_or_id_to_id_for_type_dataset], 126 | 'showcase_id': [not_empty, unicode_safe, 127 | convert_package_name_or_id_to_id_for_type_showcase] 128 | } 129 | return schema 130 | 131 | 132 | def showcase_package_association_delete_schema(): 133 | return showcase_package_association_create_schema() 134 | 135 | 136 | def showcase_package_list_schema(): 137 | schema = { 138 | 'showcase_id': [not_empty, unicode_safe, 139 | convert_package_name_or_id_to_id_for_type_showcase] 140 | } 141 | return schema 142 | 143 | 144 | def package_showcase_list_schema(): 145 | schema = { 146 | 'package_id': [not_empty, unicode_safe, 147 | convert_package_name_or_id_to_id_for_type_dataset] 148 | } 149 | return schema 150 | 151 | 152 | def showcase_admin_add_schema(): 153 | schema = { 154 | 'username': [not_empty, user_id_or_name_exists, unicode_safe], 155 | } 156 | return schema 157 | 158 | 159 | def showcase_admin_remove_schema(): 160 | return showcase_admin_add_schema() 161 | -------------------------------------------------------------------------------- /ckanext/showcase/logic/validators.py: -------------------------------------------------------------------------------- 1 | from ckan.plugins import toolkit as tk 2 | 3 | _ = tk._ 4 | Invalid = tk.Invalid 5 | 6 | 7 | def convert_package_name_or_id_to_id_for_type(package_name_or_id, 8 | context, package_type='dataset'): 9 | ''' 10 | Return the id for the given package name or id. Only works with packages 11 | of type package_type. 12 | 13 | Also validates that a package with the given name or id exists. 14 | 15 | :returns: the id of the package with the given name or id 16 | :rtype: string 17 | :raises: ckan.lib.navl.dictization_functions.Invalid if there is no 18 | package with the given name or id 19 | 20 | ''' 21 | session = context['session'] 22 | model = context['model'] 23 | result = session.query(model.Package) \ 24 | .filter_by(id=package_name_or_id, type=package_type).first() 25 | if not result: 26 | result = session.query(model.Package) \ 27 | .filter_by(name=package_name_or_id, type=package_type).first() 28 | if not result: 29 | raise Invalid('%s: %s' % (_('Not found'), _('Dataset'))) 30 | return result.id 31 | 32 | 33 | def convert_package_name_or_id_to_id_for_type_dataset(package_name_or_id, 34 | context): 35 | return convert_package_name_or_id_to_id_for_type(package_name_or_id, 36 | context, 37 | package_type='dataset') 38 | 39 | 40 | def convert_package_name_or_id_to_id_for_type_showcase(package_name_or_id, 41 | context): 42 | return convert_package_name_or_id_to_id_for_type(package_name_or_id, 43 | context, 44 | package_type='showcase') 45 | -------------------------------------------------------------------------------- /ckanext/showcase/migration/showcase/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /ckanext/showcase/migration/showcase/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | script_location = %(here)s 6 | 7 | # template used to generate migration files 8 | # file_template = %%(rev)s_%%(slug)s 9 | 10 | # timezone to use when rendering the date 11 | # within the migration file as well as the filename. 12 | # string value is passed to dateutil.tz.gettz() 13 | # leave blank for localtime 14 | # timezone = 15 | 16 | # max length of characters to apply to the 17 | # "slug" field 18 | #truncate_slug_length = 40 19 | 20 | # set to 'true' to run the environment during 21 | # the 'revision' command, regardless of autogenerate 22 | # revision_environment = false 23 | 24 | # set to 'true' to allow .pyc and .pyo files without 25 | # a source .py file to be detected as revisions in the 26 | # versions/ directory 27 | # sourceless = false 28 | 29 | # version location specification; this defaults 30 | # to /home/adria/dev/pyenvs/ckan-211/ckanext-showcase/ckanext/showcase/migration/showcase/versions. When using multiple version 31 | # directories, initial revisions must be specified with --version-path 32 | # version_locations = %(here)s/bar %(here)s/bat /home/adria/dev/pyenvs/ckan-211/ckanext-showcase/ckanext/showcase/migration/showcase/versions 33 | 34 | # the output encoding used when revision files 35 | # are written from script.py.mako 36 | # output_encoding = utf-8 37 | 38 | sqlalchemy.url = driver://user:pass@localhost/dbname 39 | 40 | 41 | # Logging configuration 42 | [loggers] 43 | keys = root,sqlalchemy,alembic 44 | 45 | [handlers] 46 | keys = console 47 | 48 | [formatters] 49 | keys = generic 50 | 51 | [logger_root] 52 | level = WARN 53 | handlers = console 54 | qualname = 55 | 56 | [logger_sqlalchemy] 57 | level = WARN 58 | handlers = 59 | qualname = sqlalchemy.engine 60 | 61 | [logger_alembic] 62 | level = INFO 63 | handlers = 64 | qualname = alembic 65 | 66 | [handler_console] 67 | class = StreamHandler 68 | args = (sys.stderr,) 69 | level = NOTSET 70 | formatter = generic 71 | 72 | [formatter_generic] 73 | format = %(levelname)-5.5s [%(name)s] %(message)s 74 | datefmt = %H:%M:%S 75 | -------------------------------------------------------------------------------- /ckanext/showcase/migration/showcase/env.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import with_statement 4 | from alembic import context 5 | from sqlalchemy import engine_from_config, pool 6 | from logging.config import fileConfig 7 | from ckan.model.meta import metadata 8 | 9 | import os 10 | 11 | # this is the Alembic Config object, which provides 12 | # access to the values within the .ini file in use. 13 | config = context.config 14 | 15 | # Interpret the config file for Python logging. 16 | # This line sets up loggers basically. 17 | fileConfig(config.config_file_name) 18 | 19 | # add your model's MetaData object here 20 | # for 'autogenerate' support 21 | # from myapp import mymodel 22 | # target_metadata = mymodel.Base.metadata 23 | target_metadata = metadata 24 | 25 | # other values from the config, defined by the needs of env.py, 26 | # can be acquired: 27 | # my_important_option = config.get_main_option("my_important_option") 28 | # ... etc. 29 | 30 | name = os.path.basename(os.path.dirname(__file__)) 31 | 32 | 33 | def include_object(object, object_name, type_, reflected, compare_to): 34 | if type_ == "table": 35 | return object_name.startswith(name) 36 | return True 37 | 38 | 39 | def run_migrations_offline(): 40 | """Run migrations in 'offline' mode. 41 | 42 | This configures the context with just a URL 43 | and not an Engine, though an Engine is acceptable 44 | here as well. By skipping the Engine creation 45 | we don't even need a DBAPI to be available. 46 | 47 | Calls to context.execute() here emit the given string to the 48 | script output. 49 | 50 | """ 51 | 52 | url = config.get_main_option(u"sqlalchemy.url") 53 | context.configure( 54 | url=url, target_metadata=target_metadata, literal_binds=True, 55 | version_table=u'{}_alembic_version'.format(name), 56 | include_object=include_object, 57 | ) 58 | 59 | with context.begin_transaction(): 60 | context.run_migrations() 61 | 62 | 63 | def run_migrations_online(): 64 | """Run migrations in 'online' mode. 65 | 66 | In this scenario we need to create an Engine 67 | and associate a connection with the context. 68 | 69 | """ 70 | connectable = engine_from_config( 71 | config.get_section(config.config_ini_section), 72 | prefix=u'sqlalchemy.', 73 | poolclass=pool.NullPool) 74 | 75 | with connectable.connect() as connection: 76 | context.configure( 77 | connection=connection, 78 | target_metadata=target_metadata, 79 | version_table=u'{}_alembic_version'.format(name), 80 | include_object=include_object, 81 | ) 82 | 83 | with context.begin_transaction(): 84 | context.run_migrations() 85 | 86 | 87 | if context.is_offline_mode(): 88 | run_migrations_offline() 89 | else: 90 | run_migrations_online() 91 | -------------------------------------------------------------------------------- /ckanext/showcase/migration/showcase/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /ckanext/showcase/migration/showcase/versions/02b006cb222c_add_ckanext_showcase_tables.py: -------------------------------------------------------------------------------- 1 | """Add ckanext-showcase tables 2 | 3 | Revision ID: 02b006cb222c 4 | Revises: 5 | Create Date: 2024-07-12 12:04:18.803072 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = "02b006cb222c" 14 | down_revision = None 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | engine = op.get_bind() 21 | inspector = sa.inspect(engine) 22 | tables = inspector.get_table_names() 23 | if "showcase_package_association" not in tables: 24 | op.create_table( 25 | "showcase_package_association", 26 | sa.Column( 27 | "package_id", 28 | sa.UnicodeText, 29 | sa.ForeignKey("package.id", ondelete="CASCADE", onupdate="CASCADE"), 30 | primary_key=True, 31 | nullable=False, 32 | ), 33 | sa.Column( 34 | "showcase_id", 35 | sa.UnicodeText, 36 | sa.ForeignKey("package.id", ondelete="CASCADE", onupdate="CASCADE"), 37 | primary_key=True, 38 | nullable=False, 39 | ), 40 | ) 41 | if "showcase_package_association" not in tables: 42 | op.create_table( 43 | "showcase_admin", 44 | sa.Column( 45 | "user_id", 46 | sa.UnicodeText, 47 | sa.ForeignKey("user.id", ondelete="CASCADE", onupdate="CASCADE"), 48 | primary_key=True, 49 | nullable=False, 50 | ), 51 | ) 52 | 53 | 54 | def downgrade(): 55 | op.drop_table("showcase_package_association") 56 | op.drop_table("showcase_admin") 57 | -------------------------------------------------------------------------------- /ckanext/showcase/model/__init__.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, ForeignKey, types 2 | 3 | from ckan.model.domain_object import DomainObject 4 | from ckan.model.meta import Session 5 | 6 | import logging 7 | 8 | try: 9 | from ckan.plugins.toolkit import BaseModel 10 | except ImportError: 11 | # CKAN <= 2.9 12 | from ckan.model.meta import metadata 13 | from sqlalchemy.ext.declarative import declarative_base 14 | 15 | BaseModel = declarative_base(metadata=metadata) 16 | 17 | log = logging.getLogger(__name__) 18 | 19 | 20 | class ShowcaseBaseModel(DomainObject): 21 | @classmethod 22 | def filter(cls, **kwargs): 23 | return Session.query(cls).filter_by(**kwargs) 24 | 25 | @classmethod 26 | def exists(cls, **kwargs): 27 | if cls.filter(**kwargs).first(): 28 | return True 29 | else: 30 | return False 31 | 32 | @classmethod 33 | def get(cls, **kwargs): 34 | instance = cls.filter(**kwargs).first() 35 | return instance 36 | 37 | @classmethod 38 | def create(cls, **kwargs): 39 | instance = cls(**kwargs) 40 | Session.add(instance) 41 | Session.commit() 42 | return instance.as_dict() 43 | 44 | 45 | class ShowcasePackageAssociation(ShowcaseBaseModel, BaseModel): 46 | 47 | __tablename__ = "showcase_package_association" 48 | 49 | package_id = Column( 50 | types.UnicodeText, 51 | ForeignKey("package.id", ondelete="CASCADE", onupdate="CASCADE"), 52 | primary_key=True, 53 | nullable=False, 54 | ) 55 | showcase_id = Column( 56 | types.UnicodeText, 57 | ForeignKey("package.id", ondelete="CASCADE", onupdate="CASCADE"), 58 | primary_key=True, 59 | nullable=False, 60 | ) 61 | 62 | @classmethod 63 | def get_package_ids_for_showcase(cls, showcase_id): 64 | """ 65 | Return a list of package ids associated with the passed showcase_id. 66 | """ 67 | showcase_package_association_list = ( 68 | Session.query(cls.package_id).filter_by(showcase_id=showcase_id).all() 69 | ) 70 | return showcase_package_association_list 71 | 72 | @classmethod 73 | def get_showcase_ids_for_package(cls, package_id): 74 | """ 75 | Return a list of showcase ids associated with the passed package_id. 76 | """ 77 | showcase_package_association_list = ( 78 | Session.query(cls.showcase_id).filter_by(package_id=package_id).all() 79 | ) 80 | return showcase_package_association_list 81 | 82 | 83 | class ShowcaseAdmin(ShowcaseBaseModel, BaseModel): 84 | __tablename__ = "showcase_admin" 85 | 86 | user_id = Column( 87 | types.UnicodeText, 88 | ForeignKey("user.id", ondelete="CASCADE", onupdate="CASCADE"), 89 | primary_key=True, 90 | nullable=False, 91 | ) 92 | 93 | @classmethod 94 | def get_showcase_admin_ids(cls): 95 | """ 96 | Return a list of showcase admin user ids. 97 | """ 98 | id_list = [i for (i,) in Session.query(cls.user_id).all()] 99 | return id_list 100 | 101 | @classmethod 102 | def is_user_showcase_admin(cls, user): 103 | """ 104 | Determine whether passed user is in the showcase admin list. 105 | """ 106 | return user.id in cls.get_showcase_admin_ids() 107 | -------------------------------------------------------------------------------- /ckanext/showcase/plugin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | import logging 6 | from collections import OrderedDict 7 | 8 | import ckan.plugins as plugins 9 | import ckan.plugins.toolkit as tk 10 | import ckan.lib.plugins as lib_plugins 11 | import ckan.lib.helpers as h 12 | 13 | 14 | from ckanext.showcase import cli 15 | from ckanext.showcase import utils 16 | from ckanext.showcase import views 17 | from ckanext.showcase.logic import auth, action 18 | 19 | import ckanext.showcase.logic.schema as showcase_schema 20 | import ckanext.showcase.logic.helpers as showcase_helpers 21 | 22 | _ = tk._ 23 | 24 | log = logging.getLogger(__name__) 25 | 26 | DATASET_TYPE_NAME = utils.DATASET_TYPE_NAME 27 | 28 | 29 | class ShowcasePlugin(plugins.SingletonPlugin, lib_plugins.DefaultDatasetForm): 30 | plugins.implements(plugins.IConfigurer) 31 | plugins.implements(plugins.IDatasetForm) 32 | plugins.implements(plugins.IFacets, inherit=True) 33 | plugins.implements(plugins.IAuthFunctions) 34 | plugins.implements(plugins.IActions) 35 | plugins.implements(plugins.IPackageController, inherit=True) 36 | plugins.implements(plugins.ITemplateHelpers) 37 | plugins.implements(plugins.ITranslation) 38 | plugins.implements(plugins.IBlueprint) 39 | plugins.implements(plugins.IClick) 40 | 41 | # IBlueprint 42 | 43 | def get_blueprint(self): 44 | return views.get_blueprints() 45 | 46 | # IClick 47 | 48 | def get_commands(self): 49 | return cli.get_commands() 50 | 51 | # IConfigurer 52 | 53 | def update_config(self, config): 54 | tk.add_template_directory(config, 'templates') 55 | tk.add_public_directory(config, 'public') 56 | tk.add_resource('assets', 'showcase') 57 | 58 | # IDatasetForm 59 | 60 | def package_types(self): 61 | return [DATASET_TYPE_NAME] 62 | 63 | def is_fallback(self): 64 | return False 65 | 66 | def search_template(self): 67 | return 'showcase/search.html' 68 | 69 | def new_template(self): 70 | return 'showcase/new.html' 71 | 72 | def read_template(self): 73 | return 'showcase/read.html' 74 | 75 | def edit_template(self): 76 | return 'showcase/edit.html' 77 | 78 | def package_form(self): 79 | return 'showcase/new_package_form.html' 80 | 81 | def create_package_schema(self): 82 | return showcase_schema.showcase_create_schema() 83 | 84 | def update_package_schema(self): 85 | return showcase_schema.showcase_update_schema() 86 | 87 | def show_package_schema(self): 88 | return showcase_schema.showcase_show_schema() 89 | 90 | # ITemplateHelpers 91 | 92 | def get_helpers(self): 93 | return { 94 | 'facet_remove_field': showcase_helpers.facet_remove_field, 95 | 'get_site_statistics': showcase_helpers.get_site_statistics, 96 | 'showcase_get_wysiwyg_editor': showcase_helpers.showcase_get_wysiwyg_editor, 97 | } 98 | 99 | # IFacets 100 | 101 | def dataset_facets(self, facets_dict, package_type): 102 | '''Only show tags for Showcase search list.''' 103 | if package_type != DATASET_TYPE_NAME: 104 | return facets_dict 105 | return OrderedDict({'tags': _('Tags')}) 106 | 107 | # IAuthFunctions 108 | 109 | def get_auth_functions(self): 110 | return auth.get_auth_functions() 111 | 112 | # IActions 113 | 114 | def get_actions(self): 115 | return action.get_actions() 116 | 117 | # IPackageController 118 | 119 | def _add_to_pkg_dict(self, context, pkg_dict): 120 | '''Add key/values to pkg_dict and return it.''' 121 | 122 | if pkg_dict['type'] != 'showcase': 123 | return pkg_dict 124 | 125 | # Add a display url for the Showcase image to the pkg dict so template 126 | # has access to it. 127 | image_url = pkg_dict.get('image_url') 128 | pkg_dict['image_display_url'] = image_url 129 | if image_url and not image_url.startswith('http'): 130 | pkg_dict['image_url'] = image_url 131 | pkg_dict['image_display_url'] = \ 132 | h.url_for_static('uploads/{0}/{1}' 133 | .format(DATASET_TYPE_NAME, 134 | pkg_dict.get('image_url')), 135 | qualified=True) 136 | 137 | # Add dataset count 138 | pkg_dict['num_datasets'] = len( 139 | tk.get_action('ckanext_showcase_package_list')( 140 | context, {'showcase_id': pkg_dict['id']})) 141 | 142 | # Rendered notes 143 | if showcase_helpers.showcase_get_wysiwyg_editor() == 'ckeditor': 144 | pkg_dict['showcase_notes_formatted'] = pkg_dict['notes'] 145 | else: 146 | pkg_dict['showcase_notes_formatted'] = \ 147 | h.render_markdown(pkg_dict['notes']) 148 | 149 | return pkg_dict 150 | 151 | # CKAN >= 2.10 152 | def after_dataset_show(self, context, pkg_dict): 153 | '''Modify package_show pkg_dict.''' 154 | pkg_dict = self._add_to_pkg_dict(context, pkg_dict) 155 | 156 | def before_dataset_view(self, pkg_dict): 157 | '''Modify pkg_dict that is sent to templates.''' 158 | context = {'user': tk.g.user or tk.g.author} 159 | 160 | return self._add_to_pkg_dict(context, pkg_dict) 161 | 162 | def before_dataset_search(self, search_params): 163 | ''' 164 | Unless the query is already being filtered by this dataset_type 165 | (either positively, or negatively), exclude datasets of type 166 | `showcase`. 167 | ''' 168 | fq = search_params.get('fq', '') 169 | filter = 'dataset_type:{0}'.format(DATASET_TYPE_NAME) 170 | if filter not in fq: 171 | search_params.update({'fq': fq + " -" + filter}) 172 | return search_params 173 | 174 | # CKAN < 2.10 (Remove when dropping support for 2.9) 175 | def after_show(self, context, pkg_dict): 176 | '''Modify package_show pkg_dict.''' 177 | pkg_dict = self.after_dataset_show(context, pkg_dict) 178 | 179 | def before_view(self, pkg_dict): 180 | '''Modify pkg_dict that is sent to templates.''' 181 | return self.before_dataset_view(pkg_dict) 182 | 183 | def before_search(self, search_params): 184 | ''' 185 | Unless the query is already being filtered by this dataset_type 186 | (either positively, or negatively), exclude datasets of type 187 | `showcase`. 188 | ''' 189 | return self.before_dataset_search(search_params) 190 | 191 | # ITranslation 192 | def i18n_directory(self): 193 | '''Change the directory of the *.mo translation files 194 | 195 | The default implementation assumes the plugin is 196 | ckanext/myplugin/plugin.py and the translations are stored in 197 | i18n/ 198 | ''' 199 | # assume plugin is called ckanext..<...>.PluginClass 200 | extension_module_name = '.'.join(self.__module__.split('.')[0:2]) 201 | module = sys.modules[extension_module_name] 202 | return os.path.join(os.path.dirname(module.__file__), 'i18n') 203 | 204 | def i18n_locales(self): 205 | '''Change the list of locales that this plugin handles 206 | 207 | By default the will assume any directory in subdirectory in the 208 | directory defined by self.directory() is a locale handled by this 209 | plugin 210 | ''' 211 | directory = self.i18n_directory() 212 | return [d for 213 | d in os.listdir(directory) 214 | if os.path.isdir(os.path.join(directory, d))] 215 | 216 | def i18n_domain(self): 217 | '''Change the gettext domain handled by this plugin 218 | 219 | This implementation assumes the gettext domain is 220 | ckanext-{extension name}, hence your pot, po and mo files should be 221 | named ckanext-{extension name}.mo''' 222 | return 'ckanext-{name}'.format(name=self.name) 223 | -------------------------------------------------------------------------------- /ckanext/showcase/public/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-showcase/6a9b6e96c4482b9becbfb0bdd27083d51b4900ed/ckanext/showcase/public/.gitignore -------------------------------------------------------------------------------- /ckanext/showcase/templates/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-showcase/6a9b6e96c4482b9becbfb0bdd27083d51b4900ed/ckanext/showcase/templates/.gitignore -------------------------------------------------------------------------------- /ckanext/showcase/templates/admin/base.html: -------------------------------------------------------------------------------- 1 | {% ckan_extends %} 2 | 3 | {% block content_primary_nav %} 4 | {{ super() }} 5 | {{ h.build_nav_icon('showcase_blueprint.admins', _('Showcase Config'), icon='trophy') }} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/admin/confirm_remove_showcase_admin.html: -------------------------------------------------------------------------------- 1 | {% extends "page.html" %} 2 | {% set showcase_admin_remove_route = 'showcase_blueprint.admin_remove' %} 3 | 4 | {% block subtitle %}{{ _("Confirm Remove") }}{% endblock %} 5 | 6 | {% block maintag %}
{% endblock %} 7 | 8 | {% block main_content %} 9 |
10 |
11 | {% block form %} 12 |

{{ _('Are you sure you want to remove this user as a Showcase Admin - {name}?').format(name=user_dict.name) }}

13 |

14 |

15 | {{ h.csrf_input() if 'csrf_input' in h }} 16 | 17 | 18 | 19 |
20 |

21 | {% endblock %} 22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/admin/manage_showcase_admins.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | 3 | {% import 'macros/form.html' as form %} 4 | 5 | {% set user = user_dict %} 6 | {% set showcase_admin_remove_route = 'showcase_blueprint.admin_remove' %} 7 | 8 | 9 | {% block primary_content_inner %} 10 |

11 | {% block page_heading %}{{ _('Manage Showcase Admins') }}{% endblock %} 12 |

13 | {% block form %} 14 |
15 | {{ h.csrf_input() if 'csrf_input' in h }} 16 |
17 |
18 | 21 | 22 | {{ _('To make an existing user a Showcase Admin, search for their username below.') }} 23 | 24 | 25 |
26 | 29 |
30 |
31 |
32 | 33 |
34 | 37 |
38 |
39 | {% endblock %} 40 | 41 |

{{ _('Showcase Admins') }}

42 | {% if showcase_admins %} 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {% for user_dict in showcase_admins %} 51 | 52 | 59 | 60 | {% endfor %} 61 | 62 |
{{ _('User') }}
53 | {{ h.linked_user(user_dict['id'], maxlength=20) }} 54 | {% set locale = h.dump_json({'content': _('Are you sure you want to remove this user from the Showcase Admin list?')}) %} 55 | 58 |
63 | {% else %} 64 |

{{ _('There are currently no Showcase Admins.') }}

65 | {% endif %} 66 | {% endblock %} 67 | 68 | {% block secondary_content %} 69 | {{ super() }} 70 |
71 |
72 | {% trans %} 73 |

Showcase Admin: Can create and remove showcases. Can add and remove datasets from showcases.

74 | {% endtrans %} 75 |
76 |
77 | {% endblock %} 78 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/header.html: -------------------------------------------------------------------------------- 1 | {% ckan_extends %} 2 | 3 | {% set showcase_route = 'showcase_blueprint.index' %} 4 | 5 | {% block header_site_navigation_tabs %} 6 | {{ super() }} 7 | {{ h.build_nav_main((showcase_route, _('Showcases'))) }} 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/home/snippets/stats.html: -------------------------------------------------------------------------------- 1 | {% set showcase_index_route = 'showcase_blueprint.index' %} 2 | {% set search_route = 'dataset.search' %} 3 | {% set organization_route = 'organization.index' %} 4 | {% set group_route = 'group.index' %} 5 | 6 | {% set stats = h.get_site_statistics() %} 7 | 8 | 39 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/package/dataset_showcase_list.html: -------------------------------------------------------------------------------- 1 | {% extends "package/read_base.html" %} 2 | 3 | {% block subtitle %}{{ _('Showcases') }} - {{ super() }}{% endblock %} 4 | 5 | {% block primary_content_inner %} 6 | {% if h.check_access('ckanext_showcase_update') and showcase_dropdown %} 7 |
8 | {{ h.csrf_input() if 'csrf_input' in h }} 9 | 14 | 15 |
16 | {% endif %} 17 | 18 |

{% block page_heading %}{{ _('Showcases featuring {dataset_name}').format(dataset_name=h.dataset_display_name(pkg_dict)) }}{% endblock %}

19 | {% block showcase_list %} 20 | {% if showcase_list %} 21 | {% snippet "showcase/snippets/showcase_list.html", packages=showcase_list, pkg_id=pkg_dict.name, show_remove=h.check_access('ckanext_showcase_update') %} 22 | {% else %} 23 |

{{ _('There are no showcases that feature this dataset') }}

24 | {% endif %} 25 | {% endblock %} 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/package/read_base.html: -------------------------------------------------------------------------------- 1 | {% ckan_extends %} 2 | {% set showcase_dataset_showcase_list_route = 'showcase_blueprint.dataset_showcase_list' %} 3 | 4 | {% block content_primary_nav %} 5 | {{ super() }} 6 | {{ h.build_nav_icon(showcase_dataset_showcase_list_route, _('Showcases'), id=pkg.name, icon='trophy') }} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/add_datasets.html: -------------------------------------------------------------------------------- 1 | {% extends 'showcase/edit_base.html' %} 2 | 3 | {% block subtitle %}{{ _('Showcases - Add datasets') }}{% endblock %} 4 | 5 | {% block wrapper_class %} ckanext-showcase-edit-wrapper{% endblock %} 6 | 7 | {% block ckanext_showcase_edit_span %}span12{% endblock %} 8 | 9 | {% block primary_content_inner %} 10 |
11 |
12 | {% block form %} 13 | {% set facets = { 14 | 'fields': fields_grouped, 15 | 'search': search_facets, 16 | 'titles': facet_titles, 17 | 'translated_fields': translated_fields, 18 | 'remove_field': remove_field } 19 | %} 20 | {% set sorting = [ 21 | (_('Relevance'), 'score desc, metadata_modified desc'), 22 | (_('Name Ascending'), 'title_string asc'), 23 | (_('Name Descending'), 'title_string desc'), 24 | (_('Last Modified'), 'metadata_modified desc'), 25 | (_('Popular'), 'views_recent desc') if g.tracking_enabled else (false, false) ] 26 | %} 27 | {% snippet 'snippets/search_form.html', type='dataset', query=q, sorting=sorting, sorting_selected=sort_by_selected, count=page.item_count, facets=facets, show_empty=request.args, error=query_error, fields=fields %} 28 | {% endblock %} 29 |

30 | {% block page_heading %} 31 | {{ _('Datasets available to add to this showcase') }} 32 | {% endblock %} 33 |

34 | {% block package_search_results_list %} 35 | {% if page.items %} 36 |
37 | {{ h.csrf_input() if 'csrf_input' in h }} 38 | {#{% block errors %}{{ form.errors(error_summary) }}{% endblock %}#} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 53 | 54 | 55 | 56 | {% for package in page.items %} 57 | {% set truncate = truncate or 180 %} 58 | {% set truncate_title = truncate_title or 80 %} 59 | {% set title = package.title or package.name %} 60 | {% set notes = h.markdown_extract(package.notes, extract_length=truncate) %} 61 | 62 | 65 | 73 | 74 | {% endfor %} 75 | 76 |
46 |
47 | 51 |
52 |
63 | 64 | 66 |

67 | {{ h.link_to(title|truncate(truncate_title), h.url_for(controller='package', action='read', id=package.name)) }} 68 |

69 | {% if notes %} 70 |

{{ notes|urlize }}

71 | {% endif %} 72 |
77 |
78 | {% else %} 79 |

{{ _('No datasets could be found') }}

80 | {% endif %} 81 | {% endblock %} 82 |
83 | 84 | {% block page_pagination %} 85 | {{ page.pager(q=q) }} 86 | {% endblock %} 87 |
88 | 89 | {% endblock %} 90 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "page.html" %} 2 | 3 | {% set pkg = pkg_dict %} 4 | {% set showcase_delete_route = 'showcase_blueprint.delete' %} 5 | 6 | {% block subtitle %}{{ _("Confirm Delete") }}{% endblock %} 7 | 8 | {% block maintag %} 9 |
10 | {% endblock %} 11 | 12 | {% block main_content %} 13 |
14 |
15 | {% block form %} 16 |

{{ _('Are you sure you want to delete showcase - {showcase_name}?').format(showcase_name=pkg.name) }}

17 |

18 |

19 | {{ h.csrf_input() if 'csrf_input' in h }} 20 | 21 | 22 |
23 |

24 | {% endblock %} 25 |
26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'showcase/edit_base.html' %} 2 | 3 | {% block wrapper_class %} ckanext-showcase-edit-wrapper{% endblock %} 4 | 5 | {% block ckanext_showcase_edit_span %}span12{% endblock %} 6 | 7 | {% block primary_content_inner %} 8 | {% block form %} 9 | {#- passing c to a snippet is bad but is required here 10 | for backwards compatibility with old templates and 11 | plugins using setup_template_variables() -#} 12 | {{- h.snippet(form_snippet, c=c, **form_vars) -}} 13 | {% endblock %} 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/edit_base.html: -------------------------------------------------------------------------------- 1 | {% extends 'page.html' %} 2 | 3 | {% set pkg = pkg_dict %} 4 | 5 | {% set showcase_index_route = 'showcase_blueprint.index' %} 6 | {% set showcase_read_route = 'showcase_blueprint.read' %} 7 | {% set showcase_edit_route = 'showcase_blueprint.edit' %} 8 | {% set showcase_manage_route = 'showcase_blueprint.manage_datasets' %} 9 | 10 | 11 | 12 | {% block subtitle %}{{ _('Showcases') }}{% endblock %} 13 | 14 | {% block styles %} 15 | {{ super() }} 16 | {% asset "showcase/ckanext-showcase-css" %} 17 | {% endblock %} 18 | 19 | {% block breadcrumb_content_selected %}{% endblock %} 20 | 21 | {% block breadcrumb_content %} 22 | {% if pkg %} 23 | {% set showcase = pkg.title or pkg.name %} 24 |
  • {% link_for _('Showcases'), named_route=showcase_index_route %}
  • 25 | {% link_for showcase|truncate(30), named_route=showcase_read_route, id=pkg.name %} 26 |
  • {% link_for _('Edit'), named_route=showcase_edit_route, id=pkg.name %}
  • 27 | {% else %} 28 |
  • {% link_for _('Showcases'), named_route=showcase_index_route %}
  • 29 |
  • {{ _('Create Showcase') }}
  • 30 | {% endif %} 31 | {% endblock %} 32 | 33 | {% block primary %} 34 |
    35 | {% block primary_content %} 36 |
    37 | {% block page_header %} 38 | 53 | {% endblock %} 54 |
    55 | {% if self.page_primary_action() | trim %} 56 |
    57 | {% block page_primary_action %}{% endblock %} 58 |
    59 | {% endif %} 60 | {% block primary_content_inner %} 61 | {% endblock %} 62 |
    63 |
    64 | {% endblock %} 65 |
    66 | {% endblock %} 67 | 68 | {% block secondary %} 69 | 70 | {% endblock %} 71 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/manage_datasets.html: -------------------------------------------------------------------------------- 1 | {% extends 'showcase/edit_base.html' %} 2 | 3 | {% block subtitle %}{{ _('Showcases - Manage datasets') }}{% endblock %} 4 | 5 | {% block wrapper_class %} ckanext-showcase-edit-wrapper{% endblock %} 6 | 7 | {% block ckanext_showcase_edit_span %}span12{% endblock %} 8 | 9 | {% block ckanext_showcase_edit_module_content_class %}{% endblock %} 10 | 11 | {% block primary_content_inner %} 12 | 13 |
    14 | 15 |
    16 | {% set facets = { 17 | 'fields': fields_grouped, 18 | 'search': search_facets, 19 | 'titles': facet_titles, 20 | 'translated_fields': translated_fields, 21 | 'remove_field': remove_field } 22 | %} 23 | {% set sorting = [ 24 | (_('Relevance'), 'score desc, metadata_modified desc'), 25 | (_('Name Ascending'), 'title_string asc'), 26 | (_('Name Descending'), 'title_string desc'), 27 | (_('Last Modified'), 'metadata_modified desc'), 28 | (_('Popular'), 'views_recent desc') if g.tracking_enabled else (false, false) ] 29 | %} 30 | {% snippet 'snippets/search_form.html', type='dataset', query=q, sorting=sorting, sorting_selected=sort_by_selected, count=page.item_count, facets=facets, show_empty=request.args, error=query_error, fields=fields %} 31 |
    32 | 33 |
    34 |
    35 |
    36 |

    {{ _('Datasets available to add to this showcase') }}

    37 | {% block package_search_results_list %} 38 | {% if page.items %} 39 |
    40 | {{ h.csrf_input() if 'csrf_input' in h }} 41 | {#{% block errors %}{{ form.errors(error_summary) }}{% endblock %}#} 42 | 43 | 44 | 45 | 46 | 47 | 48 | 56 | 57 | 58 | 59 | {% for package in page.items %} 60 | {% set truncate = truncate or 180 %} 61 | {% set truncate_title = truncate_title or 80 %} 62 | {% set title = package.title or package.name %} 63 | {% set notes = h.markdown_extract(package.notes, extract_length=truncate) %} 64 | 65 | 68 | 76 | 77 | {% endfor %} 78 | 79 | {% if page.pager() %} 80 | 81 | 82 | 83 | 84 | 85 | {% endif %} 86 |
    49 |
    50 | 54 |
    55 |
    66 | 67 | 69 |

    70 | {{ h.link_to(title|truncate(truncate_title), h.url_for('dataset.read', id=package.name)) }} 71 |

    72 | {% if notes %} 73 |

    {{ notes|urlize }}

    74 | {% endif %} 75 |
    87 |
    88 | {% else %} 89 |

    {{ _('No datasets could be found') }}

    90 | {% endif %} 91 | {% endblock %} 92 |
    93 |
    94 | 95 |
    96 |
    97 |

    {{ _('Datasets in this showcase') }}

    98 | {% if showcase_pkgs %} 99 |
    100 | {{ h.csrf_input() if 'csrf_input' in h }} 101 | 102 | 103 | 104 | 105 | 106 | 107 | 115 | 116 | 117 | 118 | {% for package in showcase_pkgs %} 119 | {% set truncate = truncate or 180 %} 120 | {% set truncate_title = truncate_title or 80 %} 121 | {% set title = package.title or package.name %} 122 | {% set notes = h.markdown_extract(package.notes, extract_length=truncate) %} 123 | 124 | 127 | 135 | 136 | {% endfor %} 137 | 138 |
    108 |
    109 | 113 |
    114 |
    125 | 126 | 128 |

    129 | {{ h.link_to(title|truncate(truncate_title), h.url_for('dataset.read', id=package.name)) }} 130 |

    131 | {% if notes %} 132 |

    {{ notes|urlize }}

    133 | {% endif %} 134 |
    139 |
    140 | {% else %} 141 |

    142 | {{ _('This showcase has no datasets associated to it') }}. 143 |

    144 | {% endif %} 145 |
    146 |
    147 |
    148 |
    149 | {% endblock %} 150 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/new.html: -------------------------------------------------------------------------------- 1 | {% extends "showcase/edit_base.html" %} 2 | 3 | {% block subtitle %}{{ _('Create Showcase') }}{% endblock %} 4 | 5 | {% block primary_content %} 6 |
    7 |
    8 |

    {% block page_heading %}{{ _('Create a Showcase') }}{% endblock %}

    9 | {% block primary_content_inner %} 10 | {% block form %} 11 | {#- passing c to a snippet is bad but is required here 12 | for backwards compatibility with old templates and 13 | plugins using setup_template_variables() -#} 14 | {{- h.snippet(form_snippet, c=c, **form_vars) -}} 15 | {% endblock %} 16 | {% endblock %} 17 |
    18 |
    19 | {% endblock %} 20 | 21 | {% block secondary_content %} 22 | {{ h.snippet('showcase/snippets/helper.html') }} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/new_package_form.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/form.html' as form %} 2 | {% set action = form_action or '' %} 3 | {% set form_style = form_style or action %} 4 | {% set showcase_read_route = 'showcase_blueprint.read' %} 5 | {% set showcase_delete_route = 'showcase_blueprint.delete' %} 6 | 7 | 8 |
    9 | {{ h.csrf_input() if 'csrf_input' in h }} 10 | 11 | {# pkg_name used in 3 stage edit #} 12 | 13 | {% block errors %}{{ form.errors(error_summary) }}{% endblock %} 14 | 15 | {% block basic_fields %} 16 | 17 | {% block package_basic_fields_title %} 18 | {{ form.input('title', id='field-title', label=_('Title'), placeholder=_('eg. A descriptive title'), value=data.title, error=errors.title, classes=['control-full', 'control-large'], attrs={'data-module': 'slug-preview-target'}) }} 19 | {% endblock %} 20 | 21 | {% block package_basic_fields_url %} 22 | {% set prefix = h.url_for(showcase_read_route, id='') %} 23 | {% set domain = h.url_for(showcase_read_route, id='', qualified=true) %} 24 | {% set domain = domain|replace("http://", "")|replace("https://", "") %} 25 | {% set attrs = {'data-module': 'slug-preview-slug', 'data-module-prefix': domain, 'data-module-placeholder': ''} %} 26 | 27 | {{ form.prepend('name', id='field-name', label=_('URL'), prepend=prefix, placeholder=_('eg. my-showcase'), value=data.name, error=errors.name, attrs=attrs, is_required=true) }} 28 | {% endblock %} 29 | 30 | {% block package_basic_fields_description %} 31 | {% set editor = h.showcase_get_wysiwyg_editor() %} 32 | {% if editor == 'ckeditor' %} 33 | {% asset 'showcase/ckeditor' %} 34 | {% 35 | set ckeditor_attrs = { 36 | 'data-module': 'showcase-ckeditor', 37 | 'data-module-site_url': h.url_for('/', qualified=true)} 38 | %} 39 | {{ form.textarea('notes', id='editor', label=_('Description'), placeholder=_('eg. Some useful notes about the data'), value=data.notes, error=errors.notes, attrs=ckeditor_attrs)}} 40 | {% else %} 41 | {{ form.markdown('notes', id='field-notes', label=_('Description'), placeholder=_('eg. Some useful notes about the data'), value=data.notes, error=errors.notes) }} 42 | {% endif %} 43 | {% endblock %} 44 | 45 | {% block package_basic_fields_tags %} 46 | {% set tag_attrs = {'data-module': 'autocomplete', 'data-module-tags': '', 'data-module-source': '/api/2/util/tag/autocomplete?incomplete=?'} %} 47 | {{ form.input('tag_string', id='field-tags', label=_('Tags'), placeholder=_('eg. economy, mental health, government'), value=data.tag_string, error=errors.tags, classes=['control-full'], attrs=tag_attrs) }} 48 | {% endblock %} 49 | 50 | {% set is_upload = data.image_url and not data.image_url.startswith('http') %} 51 | {% set is_url = data.image_url and data.image_url.startswith('http') %} 52 | 53 | {{ form.image_upload(data, errors, is_upload_enabled=h.uploads_enabled(), is_url=is_url, is_upload=is_upload) }} 54 | {% endblock %} 55 | 56 | {% block metadata_fields %} 57 | {% block package_metadata_fields_url %} 58 | {{ form.input('url', label=_('External link'), id='field-url', placeholder=_('http://www.example.com'), value=data.url, error=errors.url, classes=['control-medium']) }} 59 | {% endblock %} 60 | 61 | {% block package_metadata_author %} 62 | {{ form.input('author', label=_('Submitted By'), id='field-author', placeholder=_('Joe Bloggs'), value=data.author, error=errors.author, classes=['control-medium']) }} 63 | 64 | {{ form.input('author_email', label=_('Submitter Email'), id='field-author-email', placeholder=_('joe@example.com'), value=data.author_email, error=errors.author_email, classes=['control-medium']) }} 65 | {% endblock %} 66 | {% endblock %} 67 | 68 | {% block form_actions %} 69 |
    70 | {% block delete_button %} 71 | {% if form_style == 'edit' and h.check_access('ckanext_showcase_delete', {'id': data.id}) and not data.state == 'deleted' %} 72 | {% set locale = h.dump_json({'content': _('Are you sure you want to delete this showcase?')}) %} 73 | {% block delete_button_text %}{{ _('Delete') }}{% endblock %} 74 | {% endif %} 75 | {% endblock %} 76 | {% block save_button %} 77 | 78 | {% endblock %} 79 | {{ form.required_message() }} 80 |
    81 | {% endblock %} 82 | 83 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/read.html: -------------------------------------------------------------------------------- 1 | {% extends "page.html" %} 2 | 3 | {% set pkg = pkg_dict %} 4 | {% set name = pkg.title or pkg.name %} 5 | {% set editor = h.showcase_get_wysiwyg_editor() %} 6 | 7 | {% set showcase_read_route = 'showcase_blueprint.read' %} 8 | {% set showcase_index_route = 'showcase_blueprint.index' %} 9 | {% set showcase_edit_route = 'showcase_blueprint.edit' %} 10 | 11 | {% block subtitle %}{{ pkg.title or pkg.name }} - {{ _('Showcases') }}{% endblock %} 12 | 13 | {% block styles %} 14 | {{ super() }} 15 | {% asset "showcase/ckanext-showcase-css" %} 16 | {% if editor == 'ckeditor' %} 17 | {% asset 'showcase/ckeditor' %} 18 | {% endif %} 19 | {% endblock %} 20 | 21 | {% block links -%} 22 | {{ super() }} 23 | 24 | {% endblock -%} 25 | 26 | {% block head_extras -%} 27 | {{ super() }} 28 | {% set description = h.markdown_extract(pkg.notes, extract_length=200)|forceescape %} 29 | 30 | 31 | 32 | {% if pkg.image_display_url %} 33 | 34 | {% endif %} 35 | {% endblock -%} 36 | 37 | {% block breadcrumb_content_selected %} class="active"{% endblock %} 38 | 39 | {% block breadcrumb_content %} 40 | {% set showcase = pkg.title or pkg.name %} 41 |
  • {{ h.nav_link(_('Showcases'), named_route=showcase_index_route) }}
  • 42 | {% link_for showcase|truncate(30), named_route=showcase_read_route, id=pkg.name %} 43 | {% endblock %} 44 | 45 | {% block page_header %} 46 | {% endblock %} 47 | 48 | {% block pre_primary %} 49 | {% endblock %} 50 | 51 | {% block primary_content_inner %} 52 | {% if h.check_access('ckanext_showcase_update', {'id':pkg.id }) %} 53 |
    54 | {% link_for _('Manage'), named_route=showcase_edit_route, id=pkg.name, class_='btn btn-default', icon='wrench' %} 55 |
    56 | {% endif %} 57 | {% block package_description %} 58 | {% if pkg.private %} 59 | 60 | 61 | {{ _('Private') }} 62 | 63 | {% endif %} 64 |

    65 | {% block page_heading %} 66 | {{ name }} 67 | {% if pkg.state.startswith('draft') %} 68 | [{{ _('Draft') }}] 69 | {% endif %} 70 | {% endblock %} 71 |

    72 | 73 | {% if pkg.image_display_url %} 74 |

    {{ name }}

    75 | {% endif %} 76 | 77 | {% block package_notes %} 78 | {% if pkg.showcase_notes_formatted and editor == 'ckeditor' %} 79 |
    80 | {{ pkg.showcase_notes_formatted|safe }} 81 |
    82 | {% elif pkg.showcase_notes_formatted %} 83 |
    84 | {{ pkg.showcase_notes_formatted }} 85 |
    86 | {% endif %} 87 | {% endblock %} 88 | 89 | {% if pkg.url %} 90 |

    {{ _('Launch website') }}

    91 | {% endif %} 92 | 93 | {% endblock %} 94 | 95 | {% block package_tags %} 96 | {% snippet "showcase/snippets/tags.html", tags=pkg.tags %} 97 | {% endblock %} 98 | 99 | {% block package_search_results_list %} 100 | {% endblock %} 101 | 102 | {% endblock %} 103 | 104 | {% block secondary_content %} 105 | {% block secondary_help_content %}{% endblock %} 106 | 107 | {% block package_info %} 108 | {% snippet 'showcase/snippets/showcase_info.html', pkg=pkg, showcase_pkgs=showcase_pkgs %} 109 | {% endblock %} 110 | 111 | {% block package_social %} 112 | {% snippet "snippets/social.html" %} 113 | {% endblock %} 114 | {% endblock %} 115 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/search.html: -------------------------------------------------------------------------------- 1 | {% extends "package/search.html" %} 2 | {% import 'macros/form.html' as form %} 3 | 4 | {% set showcase_index_route = 'showcase_blueprint.index' %} 5 | {% set showcase_new_route = 'showcase_blueprint.new' %} 6 | 7 | 8 | {% block subtitle %}{{ _("Showcases") }}{% endblock %} 9 | 10 | {% block breadcrumb_content %} 11 |
  • {{ h.nav_link(_('Showcases'), named_route=showcase_index_route) }}
  • 12 | {% endblock %} 13 | 14 | {% block page_primary_action %} 15 | {% if h.check_access('ckanext_showcase_create') %} 16 |
    17 | {% link_for _('Add Showcase'), named_route=showcase_new_route, class_='btn btn-primary', icon='plus-square' %} 18 |
    19 | {% endif %} 20 | {% endblock %} 21 | 22 | {% block form %} 23 | {% set facets = { 24 | 'fields': fields_grouped, 25 | 'search': search_facets, 26 | 'titles': facet_titles, 27 | 'translated_fields': translated_fields, 28 | 'remove_field': h.facet_remove_field } 29 | %} 30 | {% set sorting = [ 31 | (_('Relevance'), 'score desc, metadata_modified desc'), 32 | (_('Name Ascending'), 'title_string asc'), 33 | (_('Name Descending'), 'title_string desc'), 34 | (_('Last Modified'), 'metadata_modified desc'), 35 | (_('Popular'), 'views_recent desc') if g.tracking_enabled else (false, false) ] 36 | %} 37 | {% snippet 'showcase/snippets/showcase_search_form.html', type='showcase', placeholder=_('Search showcases...'), query=q, sorting=sorting, sorting_selected=sort_by_selected, count=page.item_count, facets=facets, show_empty=request.args, error=query_error, fields=fields, no_bottom_border=true %} 38 | {% endblock %} 39 | 40 | {% block package_search_results_list %} 41 | {{ h.snippet('showcase/snippets/showcase_list.html', packages=page.items) }} 42 | {% endblock %} 43 | 44 | {% block package_search_results_api %} 45 | {% endblock %} 46 | 47 | {% block secondary_content %} 48 | {{ h.snippet('showcase/snippets/helper.html') }} 49 |
    50 |
    51 | {% for facet in facet_titles %} 52 | {{ h.snippet('snippets/facet_list.html', title=facet_titles[facet], name=facet, search_facets=search_facets) }} 53 | {% endfor %} 54 |
    55 | close 56 |
    57 | {% endblock %} 58 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/snippets/helper.html: -------------------------------------------------------------------------------- 1 | {% set showcase_admins_route = 'showcase_blueprint.admins' %} 2 | 3 |
    4 |

    5 | 6 | {{ _('What are Showcases?') }} 7 |

    8 |
    9 |

    10 | {% trans %}Datasets have been used to build apps, websites and visualizations. They've been featured in articles, and written about in news reports and blog posts. Showcases collect together the best examples of datasets in use, to provide further insight, ideas, and inspiration.{% endtrans %} 11 |

    12 | {% if h.check_access('ckanext_showcase_admin_add') %} 13 |

    14 | {% trans url=h.url_for(showcase_admins_route) %}Sysadmins can manage Showcase Admins from the Showcase configuration page.{% endtrans %} 15 |

    16 | {% endif %} 17 |
    18 |
    19 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/snippets/showcase_info.html: -------------------------------------------------------------------------------- 1 | {# 2 | Displays a sidebard module with information for given package 3 | 4 | pkg - The showcase package dict that owns the resources. 5 | 6 | Example: 7 | 8 | {% snippet "package/snippets/info.html", pkg=pkg %} 9 | 10 | #} 11 | {% block package_info %} 12 | {% if pkg %} 13 |
    14 |
    15 |
    16 | {% block package_info_inner %} 17 | {% block heading %} 18 |

    {{ pkg.title or pkg.name }}

    19 | {% endblock %} 20 | {% if pkg.author %} 21 | {{_('Submitted by')}} 22 |

    {{ pkg.author }}

    23 | {% endif %} 24 | {% if pkg.url %} 25 | 30 | {% endif %} 31 | {% endblock %} 32 |
    33 |
    34 |
    35 | 36 |
    37 |

    {{ _('Datasets in Showcase') }}

    38 | {% if showcase_pkgs %} 39 | 46 | {% else %} 47 |

    {{_('There are no Datasets in this Showcase')}}

    48 | {% endif %} 49 |
    50 | {% endif %} 51 | {% endblock %} 52 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/snippets/showcase_item.html: -------------------------------------------------------------------------------- 1 | {# 2 | Displays a single dataset of type 'showcase'. 3 | 4 | package - A package to display. 5 | item_class - The class name to use on the list item. 6 | truncate - The length to trucate the description to (default: 180) 7 | truncate_title - The length to truncate the title to (default: 80). 8 | show_remove - If True, show the remove button to remove showcase/dataset association. 9 | 10 | #} 11 | {% set truncate = truncate or 180 %} 12 | {% set truncate_title = truncate_title or 80 %} 13 | {% set title = package.title or package.name %} 14 | {% set notes = h.markdown_extract(package.notes, extract_length=truncate) %} 15 | 16 | {% set showcase_read_route = 'showcase_blueprint.read' %} 17 | 18 | {% block package_item %} 19 | 20 |
  • 21 | {% block item_inner %} 22 | {% block image %} 23 | {{ package.name }} 24 | {% endblock %} 25 | {% block title %} 26 |

    {{ h.link_to(title|truncate(truncate_title), h.url_for(showcase_read_route, id=package.name)) }}

    27 | {% endblock %} 28 | {% block notes %} 29 | {% if notes %} 30 |
    {{ notes|urlize }}
    31 | {% else %} 32 |

    {{ _("This showcase has no description") }}

    33 | {% endif %} 34 | {% endblock %} 35 | {% block datasets %} 36 | {% if package.num_datasets %} 37 | {{ ungettext('{num} Dataset', '{num} Datasets', package.num_datasets).format(num=package.num_datasets) }} 38 | {% elif package.num_datasets == 0 %} 39 | {{ _('0 Datasets') }} 40 | {% endif %} 41 | {% endblock %} 42 | {% block link %} 43 | 44 | {{ _('View {showcase_title}').format(showcase_title=package.title) }} 45 | 46 | {% endblock %} 47 | {% if show_remove %} 48 |
    49 | {{ h.csrf_input() if 'csrf_input' in h }} 50 | 51 | 52 |
    53 | {% endif %} 54 | {% endblock %} 55 |
  • 56 | {% endblock %} 57 | {# {% if position is divisibleby 3 %} 58 |
  • 59 | {% endif %} #} 60 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/snippets/showcase_list.html: -------------------------------------------------------------------------------- 1 | {# 2 | Displays a list of showcases. 3 | 4 | packages - A list of packages to display. 5 | list_class - The class name for the list item. 6 | item_class - The class name to use on each item. 7 | truncate - The length to trucate the description to (default: 180) 8 | truncate_title - The length to truncate the title to (default: 80). 9 | show_remove - If True, show the remove button to remove the showcase/dataset association. 10 | 11 | #} 12 | {% block package_list %} 13 | {% if packages %} 14 |
      15 | {% block package_list_inner %} 16 | {% for package in packages %} 17 | {% snippet 'showcase/snippets/showcase_item.html', package=package, item_class=item_class, truncate=truncate, truncate_title=truncate_title, show_remove=show_remove %} 18 | {% endfor %} 19 | {% endblock %} 20 |
    21 | {% endif %} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/snippets/showcase_search_form.html: -------------------------------------------------------------------------------- 1 | {% extends "snippets/search_form.html" %} 2 | 3 | {% block search_title %} 4 | {% if not no_title %} 5 |

    {% snippet 'showcase/snippets/showcase_search_result_text.html', query=query, count=count, type=type %}

    6 | {% endif %} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/snippets/showcase_search_result_text.html: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | Based on snippets/search_result_text.html 4 | 5 | #} 6 | {% if type == 'showcase' %} 7 | {% set text_query = ungettext("{number} showcase found for '{query}'", "{number} showcases found for '{query}'", count) %} 8 | {% set text_query_none = _("No showcases found for '{query}'") %} 9 | {% set text_no_query = ungettext("{number} showcase found", "{number} showcases found", count) %} 10 | {% set text_no_query_none = _("No showcases found") %} 11 | {%- endif -%} 12 | 13 | {% if query %} 14 | {%- if count -%} 15 | {{ text_query.format(number=h.localised_number(count), query=query) }} 16 | {%- else -%} 17 | {{ text_query_none.format(query=query) }} 18 | {%- endif -%} 19 | {%- else -%} 20 | {%- if count -%} 21 | {{ text_no_query.format(number=h.localised_number(count)) }} 22 | {%- else -%} 23 | {{ text_no_query_none }} 24 | {%- endif -%} 25 | {%- endif -%} 26 | -------------------------------------------------------------------------------- /ckanext/showcase/templates/showcase/snippets/tags.html: -------------------------------------------------------------------------------- 1 | {% if tags %} 2 | {% set showcase_index_route = 'showcase_blueprint.index' %} 3 | 4 |
    5 | {% set _class = 'tag-list well' %} 6 | {% block tag_list %} 7 | 14 | {% endblock %} 15 |
    16 | {% endif %} 17 | -------------------------------------------------------------------------------- /ckanext/showcase/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-showcase/6a9b6e96c4482b9becbfb0bdd27083d51b4900ed/ckanext/showcase/tests/__init__.py -------------------------------------------------------------------------------- /ckanext/showcase/tests/action/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-showcase/6a9b6e96c4482b9becbfb0bdd27083d51b4900ed/ckanext/showcase/tests/action/__init__.py -------------------------------------------------------------------------------- /ckanext/showcase/tests/action/test_create.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ckan.model.package import Package 4 | import ckan.model as model 5 | import ckan.plugins.toolkit as toolkit 6 | 7 | from ckan.tests import factories, helpers 8 | 9 | 10 | from ckanext.showcase.model import ShowcasePackageAssociation, ShowcaseAdmin 11 | 12 | 13 | @pytest.mark.usefixtures("with_plugins", "clean_db", "clean_session") 14 | class TestCreateShowcase(object): 15 | def test_showcase_create_no_args(self): 16 | """ 17 | Calling showcase create without args raises ValidationError. 18 | """ 19 | sysadmin = factories.Sysadmin() 20 | context = {"user": sysadmin["name"]} 21 | 22 | # no showcases exist. 23 | assert ( 24 | model.Session.query(Package) 25 | .filter(Package.type == "showcase") 26 | .count() 27 | == 0 28 | ) 29 | 30 | with pytest.raises(toolkit.ValidationError): 31 | helpers.call_action( 32 | "ckanext_showcase_create", context=context, 33 | ) 34 | 35 | # no showcases (dataset of type 'showcase') created. 36 | assert ( 37 | model.Session.query(Package) 38 | .filter(Package.type == "showcase") 39 | .count() 40 | == 0 41 | ) 42 | 43 | def test_showcase_create_with_name_arg(self): 44 | """ 45 | Calling showcase create with a name arg creates a showcase package. 46 | """ 47 | sysadmin = factories.Sysadmin() 48 | context = {"user": sysadmin["name"]} 49 | 50 | # no showcases exist. 51 | assert ( 52 | model.Session.query(Package) 53 | .filter(Package.type == "showcase") 54 | .count() 55 | == 0 56 | ) 57 | 58 | helpers.call_action( 59 | "ckanext_showcase_create", context=context, name="my-showcase" 60 | ) 61 | 62 | # a showcases (dataset of type 'showcase') created. 63 | assert ( 64 | model.Session.query(Package) 65 | .filter(Package.type == "showcase") 66 | .count() 67 | == 1 68 | ) 69 | 70 | def test_showcase_create_with_existing_name(self): 71 | """ 72 | Calling showcase create with an existing name raises ValidationError. 73 | """ 74 | sysadmin = factories.Sysadmin() 75 | context = {"user": sysadmin["name"]} 76 | factories.Dataset(type="showcase", name="my-showcase") 77 | 78 | # a single showcases exist. 79 | assert ( 80 | model.Session.query(Package) 81 | .filter(Package.type == "showcase") 82 | .count() 83 | == 1 84 | ) 85 | 86 | with pytest.raises(toolkit.ValidationError): 87 | helpers.call_action( 88 | "ckanext_showcase_create", context=context, name="my-showcase", 89 | ) 90 | 91 | # still only one showcase exists. 92 | assert ( 93 | model.Session.query(Package) 94 | .filter(Package.type == "showcase") 95 | .count() 96 | == 1 97 | ) 98 | 99 | 100 | @pytest.mark.usefixtures("with_plugins", "clean_db", "clean_session") 101 | class TestCreateShowcasePackageAssociation(object): 102 | def test_association_create_no_args(self): 103 | """ 104 | Calling sc/pkg association create with no args raises 105 | ValidationError. 106 | """ 107 | sysadmin = factories.User(sysadmin=True) 108 | context = {"user": sysadmin["name"]} 109 | with pytest.raises(toolkit.ValidationError): 110 | helpers.call_action( 111 | "ckanext_showcase_package_association_create", context=context, 112 | ) 113 | 114 | assert model.Session.query(ShowcasePackageAssociation).count() == 0 115 | 116 | def test_association_create_missing_arg(self): 117 | """ 118 | Calling sc/pkg association create with a missing arg raises 119 | ValidationError. 120 | """ 121 | sysadmin = factories.User(sysadmin=True) 122 | package_id = factories.Dataset()["id"] 123 | 124 | context = {"user": sysadmin["name"]} 125 | with pytest.raises(toolkit.ValidationError): 126 | helpers.call_action( 127 | "ckanext_showcase_package_association_create", 128 | context=context, 129 | package_id=package_id, 130 | ) 131 | 132 | assert model.Session.query(ShowcasePackageAssociation).count() == 0 133 | 134 | def test_association_create_by_id(self): 135 | """ 136 | Calling sc/pkg association create with correct args (package ids) 137 | creates an association. 138 | """ 139 | sysadmin = factories.User(sysadmin=True) 140 | package_id = factories.Dataset()["id"] 141 | showcase_id = factories.Dataset(type="showcase")["id"] 142 | 143 | context = {"user": sysadmin["name"]} 144 | association_dict = helpers.call_action( 145 | "ckanext_showcase_package_association_create", 146 | context=context, 147 | package_id=package_id, 148 | showcase_id=showcase_id, 149 | ) 150 | 151 | # One association object created 152 | assert model.Session.query(ShowcasePackageAssociation).count() == 1 153 | # Association properties are correct 154 | assert association_dict.get("showcase_id") == showcase_id 155 | assert association_dict.get("package_id") == package_id 156 | 157 | def test_association_create_by_name(self): 158 | """ 159 | Calling sc/pkg association create with correct args (package names) 160 | creates an association. 161 | """ 162 | sysadmin = factories.User(sysadmin=True) 163 | package = factories.Dataset() 164 | package_name = package["name"] 165 | showcase = factories.Dataset(type="showcase") 166 | showcase_name = showcase["name"] 167 | 168 | context = {"user": sysadmin["name"]} 169 | association_dict = helpers.call_action( 170 | "ckanext_showcase_package_association_create", 171 | context=context, 172 | package_id=package_name, 173 | showcase_id=showcase_name, 174 | ) 175 | 176 | assert model.Session.query(ShowcasePackageAssociation).count() == 1 177 | assert association_dict.get("showcase_id") == showcase["id"] 178 | assert association_dict.get("package_id") == package["id"] 179 | 180 | def test_association_create_existing(self): 181 | """ 182 | Attempt to create association with existing details returns Validation 183 | Error. 184 | """ 185 | sysadmin = factories.User(sysadmin=True) 186 | package_id = factories.Dataset()["id"] 187 | showcase_id = factories.Dataset(type="showcase")["id"] 188 | 189 | context = {"user": sysadmin["name"]} 190 | # Create association 191 | helpers.call_action( 192 | "ckanext_showcase_package_association_create", 193 | context=context, 194 | package_id=package_id, 195 | showcase_id=showcase_id, 196 | ) 197 | # Attempted duplicate creation results in ValidationError 198 | with pytest.raises(toolkit.ValidationError): 199 | helpers.call_action( 200 | "ckanext_showcase_package_association_create", 201 | context=context, 202 | package_id=package_id, 203 | showcase_id=showcase_id, 204 | ) 205 | 206 | 207 | @pytest.mark.usefixtures("with_plugins", "clean_db", "clean_session") 208 | class TestCreateShowcaseAdmin(object): 209 | def test_showcase_admin_add_creates_showcase_admin_user(self): 210 | """ 211 | Calling ckanext_showcase_admin_add adds user to showcase admin list. 212 | """ 213 | user_to_add = factories.User() 214 | 215 | assert model.Session.query(ShowcaseAdmin).count() == 0 216 | 217 | helpers.call_action( 218 | "ckanext_showcase_admin_add", 219 | context={}, 220 | username=user_to_add["name"], 221 | ) 222 | 223 | assert model.Session.query(ShowcaseAdmin).count() == 1 224 | assert user_to_add["id"] in ShowcaseAdmin.get_showcase_admin_ids() 225 | 226 | def test_showcase_admin_add_multiple_users(self): 227 | """ 228 | Calling ckanext_showcase_admin_add for multiple users correctly adds 229 | them to showcase admin list. 230 | """ 231 | user_to_add = factories.User() 232 | second_user_to_add = factories.User() 233 | 234 | assert model.Session.query(ShowcaseAdmin).count() == 0 235 | 236 | helpers.call_action( 237 | "ckanext_showcase_admin_add", 238 | context={}, 239 | username=user_to_add["name"], 240 | ) 241 | 242 | helpers.call_action( 243 | "ckanext_showcase_admin_add", 244 | context={}, 245 | username=second_user_to_add["name"], 246 | ) 247 | 248 | assert model.Session.query(ShowcaseAdmin).count() == 2 249 | assert user_to_add["id"] in ShowcaseAdmin.get_showcase_admin_ids() 250 | assert ( 251 | second_user_to_add["id"] in ShowcaseAdmin.get_showcase_admin_ids() 252 | ) 253 | 254 | def test_showcase_admin_add_existing_user(self): 255 | """ 256 | Calling ckanext_showcase_admin_add twice for same user raises a 257 | ValidationError. 258 | """ 259 | user_to_add = factories.User() 260 | 261 | # Add once 262 | helpers.call_action( 263 | "ckanext_showcase_admin_add", 264 | context={}, 265 | username=user_to_add["name"], 266 | ) 267 | 268 | assert model.Session.query(ShowcaseAdmin).count() == 1 269 | 270 | # Attempt second add 271 | with pytest.raises(toolkit.ValidationError): 272 | helpers.call_action( 273 | "ckanext_showcase_admin_add", 274 | context={}, 275 | username=user_to_add["name"], 276 | ) 277 | 278 | # Still only one ShowcaseAdmin object. 279 | assert model.Session.query(ShowcaseAdmin).count() == 1 280 | 281 | def test_showcase_admin_add_username_doesnot_exist(self): 282 | """ 283 | Calling ckanext_showcase_admin_add with non-existent username raises 284 | ValidationError and no ShowcaseAdmin object is created. 285 | """ 286 | with pytest.raises(toolkit.ObjectNotFound): 287 | helpers.call_action( 288 | "ckanext_showcase_admin_add", context={}, username="missing", 289 | ) 290 | 291 | assert model.Session.query(ShowcaseAdmin).count() == 0 292 | assert ShowcaseAdmin.get_showcase_admin_ids() == [] 293 | 294 | def test_showcase_admin_add_no_args(self): 295 | """ 296 | Calling ckanext_showcase_admin_add with no args raises ValidationError 297 | and no ShowcaseAdmin object is created. 298 | """ 299 | with pytest.raises(toolkit.ValidationError): 300 | helpers.call_action( 301 | "ckanext_showcase_admin_add", context={}, 302 | ) 303 | 304 | assert model.Session.query(ShowcaseAdmin).count() == 0 305 | assert ShowcaseAdmin.get_showcase_admin_ids() == [] 306 | -------------------------------------------------------------------------------- /ckanext/showcase/tests/action/test_delete.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import ckan.model as model 4 | import ckan.plugins.toolkit as toolkit 5 | 6 | from ckan.tests import factories, helpers 7 | 8 | from ckanext.showcase.model import ShowcasePackageAssociation, ShowcaseAdmin 9 | from ckan.model.package import Package 10 | 11 | 12 | @pytest.mark.usefixtures("with_plugins", "clean_db") 13 | class TestDeleteShowcase(object): 14 | def test_showcase_delete_no_args(self): 15 | """ 16 | Calling showcase delete with no args raises a ValidationError. 17 | """ 18 | sysadmin = factories.Sysadmin() 19 | context = {"user": sysadmin["name"]} 20 | with pytest.raises(toolkit.ValidationError): 21 | helpers.call_action( 22 | "ckanext_showcase_delete", context=context, 23 | ) 24 | 25 | def test_showcase_delete_incorrect_args(self): 26 | """ 27 | Calling showcase delete with incorrect args raises ObjectNotFound. 28 | """ 29 | sysadmin = factories.Sysadmin() 30 | context = {"user": sysadmin["name"]} 31 | factories.Dataset(type="showcase") 32 | with pytest.raises(toolkit.ObjectNotFound): 33 | helpers.call_action( 34 | "ckanext_showcase_delete", context=context, id="blah-blah", 35 | ) 36 | 37 | def test_showcase_delete_by_id(self): 38 | """ 39 | Calling showcase delete with showcase id. 40 | """ 41 | sysadmin = factories.Sysadmin() 42 | context = {"user": sysadmin["name"]} 43 | showcase = factories.Dataset(type="showcase") 44 | 45 | # One showcase object created 46 | assert ( 47 | model.Session.query(Package) 48 | .filter(Package.type == "showcase") 49 | .count() 50 | == 1 51 | ) 52 | 53 | helpers.call_action( 54 | "ckanext_showcase_delete", context=context, id=showcase["id"] 55 | ) 56 | 57 | assert ( 58 | model.Session.query(Package) 59 | .filter(Package.type == "showcase") 60 | .count() 61 | == 0 62 | ) 63 | 64 | def test_showcase_delete_by_name(self): 65 | """ 66 | Calling showcase delete with showcase name. 67 | """ 68 | sysadmin = factories.Sysadmin() 69 | context = {"user": sysadmin["name"]} 70 | showcase = factories.Dataset(type="showcase") 71 | 72 | # One showcase object created 73 | assert ( 74 | model.Session.query(Package) 75 | .filter(Package.type == "showcase") 76 | .count() 77 | == 1 78 | ) 79 | 80 | helpers.call_action( 81 | "ckanext_showcase_delete", context=context, id=showcase["name"] 82 | ) 83 | 84 | assert ( 85 | model.Session.query(Package) 86 | .filter(Package.type == "showcase") 87 | .count() 88 | == 0 89 | ) 90 | 91 | def test_showcase_delete_removes_associations(self): 92 | """ 93 | Deleting a showcase also deletes associated ShowcasePackageAssociation 94 | objects. 95 | """ 96 | sysadmin = factories.Sysadmin() 97 | context = {"user": sysadmin["name"]} 98 | showcase = factories.Dataset(type="showcase", name="my-showcase") 99 | dataset_one = factories.Dataset(name="dataset-one") 100 | dataset_two = factories.Dataset(name="dataset-two") 101 | 102 | helpers.call_action( 103 | "ckanext_showcase_package_association_create", 104 | context=context, 105 | package_id=dataset_one["id"], 106 | showcase_id=showcase["id"], 107 | ) 108 | helpers.call_action( 109 | "ckanext_showcase_package_association_create", 110 | context=context, 111 | package_id=dataset_two["id"], 112 | showcase_id=showcase["id"], 113 | ) 114 | 115 | assert model.Session.query(ShowcasePackageAssociation).count() == 2 116 | 117 | helpers.call_action( 118 | "ckanext_showcase_delete", context=context, id=showcase["id"] 119 | ) 120 | 121 | assert model.Session.query(ShowcasePackageAssociation).count() == 0 122 | 123 | 124 | @pytest.mark.usefixtures("with_plugins", "clean_db") 125 | class TestDeletePackage(object): 126 | def test_package_delete_retains_associations(self): 127 | """ 128 | Deleting a package (setting its status to 'delete') retains associated 129 | ShowcasePackageAssociation objects. 130 | """ 131 | sysadmin = factories.Sysadmin() 132 | context = {"user": sysadmin["name"]} 133 | showcase = factories.Dataset(type="showcase", name="my-showcase") 134 | dataset_one = factories.Dataset(name="dataset-one") 135 | dataset_two = factories.Dataset(name="dataset-two") 136 | 137 | helpers.call_action( 138 | "ckanext_showcase_package_association_create", 139 | context=context, 140 | package_id=dataset_one["id"], 141 | showcase_id=showcase["id"], 142 | ) 143 | helpers.call_action( 144 | "ckanext_showcase_package_association_create", 145 | context=context, 146 | package_id=dataset_two["id"], 147 | showcase_id=showcase["id"], 148 | ) 149 | 150 | assert model.Session.query(ShowcasePackageAssociation).count() == 2 151 | 152 | # delete the first package, should also delete the 153 | # ShowcasePackageAssociation associated with it. 154 | helpers.call_action( 155 | "package_delete", context=context, id=dataset_one["id"] 156 | ) 157 | 158 | assert model.Session.query(ShowcasePackageAssociation).count() == 2 159 | 160 | def test_package_purge_deletes_associations(self): 161 | """ 162 | Purging a package (actually deleting it from the database) deletes 163 | associated ShowcasePackageAssociation objects. 164 | """ 165 | sysadmin = factories.Sysadmin() 166 | context = {"user": sysadmin["name"]} 167 | showcase = factories.Dataset(type="showcase", name="my-showcase") 168 | dataset_one = factories.Dataset(name="dataset-one") 169 | dataset_two = factories.Dataset(name="dataset-two") 170 | 171 | helpers.call_action( 172 | "ckanext_showcase_package_association_create", 173 | context=context, 174 | package_id=dataset_one["id"], 175 | showcase_id=showcase["id"], 176 | ) 177 | helpers.call_action( 178 | "ckanext_showcase_package_association_create", 179 | context=context, 180 | package_id=dataset_two["id"], 181 | showcase_id=showcase["id"], 182 | ) 183 | 184 | assert model.Session.query(ShowcasePackageAssociation).count() == 2 185 | 186 | # purge the first package, should also delete the 187 | # ShowcasePackageAssociation associated with it. 188 | pkg = model.Session.query(model.Package).get(dataset_one["id"]) 189 | pkg.purge() 190 | model.repo.commit_and_remove() 191 | 192 | assert model.Session.query(ShowcasePackageAssociation).count() == 1 193 | 194 | 195 | @pytest.mark.usefixtures("with_plugins", "clean_db") 196 | class TestDeleteShowcasePackageAssociation(object): 197 | def test_association_delete_no_args(self): 198 | """ 199 | Calling sc/pkg association delete with no args raises ValidationError. 200 | """ 201 | sysadmin = factories.User(sysadmin=True) 202 | context = {"user": sysadmin["name"]} 203 | with pytest.raises(toolkit.ValidationError): 204 | helpers.call_action( 205 | "ckanext_showcase_package_association_delete", context=context, 206 | ) 207 | 208 | def test_association_delete_missing_arg(self): 209 | """ 210 | Calling sc/pkg association delete with a missing arg raises 211 | ValidationError. 212 | """ 213 | sysadmin = factories.User(sysadmin=True) 214 | package_id = factories.Dataset()["id"] 215 | 216 | context = {"user": sysadmin["name"]} 217 | with pytest.raises(toolkit.ValidationError): 218 | helpers.call_action( 219 | "ckanext_showcase_package_association_delete", 220 | context=context, 221 | package_id=package_id, 222 | ) 223 | 224 | def test_association_delete_by_id(self): 225 | """ 226 | Calling sc/pkg association delete with correct args (package ids) 227 | correctly deletes an association. 228 | """ 229 | sysadmin = factories.User(sysadmin=True) 230 | package_id = factories.Dataset()["id"] 231 | showcase_id = factories.Dataset(type="showcase")["id"] 232 | 233 | context = {"user": sysadmin["name"]} 234 | helpers.call_action( 235 | "ckanext_showcase_package_association_create", 236 | context=context, 237 | package_id=package_id, 238 | showcase_id=showcase_id, 239 | ) 240 | 241 | # One association object created 242 | assert model.Session.query(ShowcasePackageAssociation).count() == 1 243 | 244 | helpers.call_action( 245 | "ckanext_showcase_package_association_delete", 246 | context=context, 247 | package_id=package_id, 248 | showcase_id=showcase_id, 249 | ) 250 | 251 | def test_association_delete_attempt_with_non_existent_association(self): 252 | """ 253 | Attempting to delete a non-existent association (package ids exist, 254 | but aren't associated with each other), will cause a NotFound error. 255 | """ 256 | sysadmin = factories.User(sysadmin=True) 257 | package_id = factories.Dataset()["id"] 258 | showcase_id = factories.Dataset(type="showcase")["id"] 259 | 260 | # No existing associations 261 | assert model.Session.query(ShowcasePackageAssociation).count() == 0 262 | 263 | context = {"user": sysadmin["name"]} 264 | with pytest.raises(toolkit.ObjectNotFound): 265 | helpers.call_action( 266 | "ckanext_showcase_package_association_delete", 267 | context=context, 268 | package_id=package_id, 269 | showcase_id=showcase_id, 270 | ) 271 | 272 | def test_association_delete_attempt_with_bad_package_ids(self): 273 | """ 274 | Attempting to delete an association by passing non-existent package 275 | ids will cause a ValidationError. 276 | """ 277 | sysadmin = factories.User(sysadmin=True) 278 | 279 | # No existing associations 280 | assert model.Session.query(ShowcasePackageAssociation).count() == 0 281 | 282 | context = {"user": sysadmin["name"]} 283 | with pytest.raises(toolkit.ValidationError): 284 | helpers.call_action( 285 | "ckanext_showcase_package_association_delete", 286 | context=context, 287 | package_id="my-bad-package-id", 288 | showcase_id="my-bad-showcase-id", 289 | ) 290 | 291 | def test_association_delete_retains_packages(self): 292 | """ 293 | Deleting a sc/pkg association doesn't delete the associated packages. 294 | """ 295 | sysadmin = factories.User(sysadmin=True) 296 | package_id = factories.Dataset()["id"] 297 | showcase_id = factories.Dataset(type="showcase")["id"] 298 | 299 | context = {"user": sysadmin["name"]} 300 | helpers.call_action( 301 | "ckanext_showcase_package_association_create", 302 | context=context, 303 | package_id=package_id, 304 | showcase_id=showcase_id, 305 | ) 306 | 307 | helpers.call_action( 308 | "ckanext_showcase_package_association_delete", 309 | context=context, 310 | package_id=package_id, 311 | showcase_id=showcase_id, 312 | ) 313 | 314 | # package still exist 315 | assert ( 316 | model.Session.query(Package) 317 | .filter(Package.type == "dataset") 318 | .count() 319 | == 1 320 | ) 321 | 322 | # showcase still exist 323 | assert ( 324 | model.Session.query(Package) 325 | .filter(Package.type == "showcase") 326 | .count() 327 | == 1 328 | ) 329 | 330 | 331 | @pytest.mark.usefixtures("with_plugins", "clean_db") 332 | class TestRemoveShowcaseAdmin(object): 333 | def test_showcase_admin_remove_deletes_showcase_admin_user(self): 334 | """ 335 | Calling ckanext_showcase_admin_remove deletes ShowcaseAdmin object. 336 | """ 337 | user = factories.User() 338 | 339 | helpers.call_action( 340 | "ckanext_showcase_admin_add", context={}, username=user["name"] 341 | ) 342 | 343 | # There's a ShowcaseAdmin obj 344 | assert model.Session.query(ShowcaseAdmin).count() == 1 345 | 346 | helpers.call_action( 347 | "ckanext_showcase_admin_remove", context={}, username=user["name"] 348 | ) 349 | 350 | # There's no ShowcaseAdmin obj 351 | assert model.Session.query(ShowcaseAdmin).count() == 0 352 | assert ShowcaseAdmin.get_showcase_admin_ids() == [] 353 | 354 | def test_showcase_admin_delete_user_removes_showcase_admin_object(self): 355 | """ 356 | Deleting a user also deletes the corresponding ShowcaseAdmin object. 357 | """ 358 | user = factories.User() 359 | 360 | helpers.call_action( 361 | "ckanext_showcase_admin_add", context={}, username=user["name"] 362 | ) 363 | 364 | # There's a ShowcaseAdmin object 365 | assert model.Session.query(ShowcaseAdmin).count() == 1 366 | assert user["id"] in ShowcaseAdmin.get_showcase_admin_ids() 367 | 368 | # purge the user, should also delete the ShowcaseAdmin object 369 | # associated with it. 370 | user_obj = model.Session.query(model.User).get(user["id"]) 371 | user_obj.purge() 372 | model.repo.commit_and_remove() 373 | 374 | # The ShowcaseAdmin has also been removed 375 | assert model.Session.query(ShowcaseAdmin).count() == 0 376 | assert ShowcaseAdmin.get_showcase_admin_ids() == [] 377 | 378 | def test_showcase_admin_remove_retains_user(self): 379 | """ 380 | Deleting a ShowcaseAdmin object doesn't delete the corresponding user. 381 | """ 382 | 383 | user = factories.User() 384 | 385 | helpers.call_action( 386 | "ckanext_showcase_admin_add", context={}, username=user["name"] 387 | ) 388 | 389 | # We have a user 390 | user_obj = model.Session.query(model.User).get(user["id"]) 391 | assert user_obj is not None 392 | 393 | helpers.call_action( 394 | "ckanext_showcase_admin_remove", context={}, username=user["name"] 395 | ) 396 | 397 | # We still have a user 398 | user_obj = model.Session.query(model.User).get(user["id"]) 399 | assert user_obj is not None 400 | 401 | def test_showcase_admin_remove_with_bad_username(self): 402 | """ 403 | Calling showcase admin remove with a non-existent user raises 404 | ValidationError. 405 | """ 406 | 407 | with pytest.raises(toolkit.ValidationError): 408 | helpers.call_action( 409 | "ckanext_showcase_admin_remove", 410 | context={}, 411 | username="no-one-here", 412 | ) 413 | 414 | def test_showcase_admin_remove_with_no_args(self): 415 | """ 416 | Calling showcase admin remove with no arg raises ValidationError. 417 | """ 418 | 419 | with pytest.raises(toolkit.ValidationError): 420 | helpers.call_action( 421 | "ckanext_showcase_admin_remove", context={}, 422 | ) 423 | -------------------------------------------------------------------------------- /ckanext/showcase/tests/fixtures.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import ckan.model as model 4 | from ckan.plugins import toolkit 5 | 6 | 7 | @pytest.fixture 8 | def clean_db(reset_db, migrate_db_for): 9 | reset_db() 10 | migrate_db_for("showcase") 11 | 12 | 13 | @pytest.fixture 14 | def clean_session(): 15 | if toolkit.check_ckan_version(max_version="2.9.0"): 16 | if hasattr(model.Session, "revision"): 17 | model.Session.revision = None 18 | -------------------------------------------------------------------------------- /ckanext/showcase/tests/test_converters.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import pytest 4 | 5 | import ckan.model as model 6 | import ckan.plugins.toolkit as toolkit 7 | 8 | from ckan.tests import factories 9 | 10 | from ckanext.showcase.logic.converters import ( 11 | convert_package_name_or_id_to_title_or_name, 12 | ) 13 | 14 | 15 | @pytest.mark.usefixtures("with_plugins", "clean_db", "clean_index") 16 | class TestNameOrIdToTitleConverter(object): 17 | def test_name_to_title(self): 18 | """ 19 | Package correctly returns title. 20 | """ 21 | my_id = str(uuid.uuid4()) 22 | context = {"session": model.Session} 23 | factories.Dataset(id=my_id, title="My Title", name="my-name") 24 | 25 | result = convert_package_name_or_id_to_title_or_name( 26 | "my-name", context 27 | ) 28 | assert "My Title" == result 29 | 30 | def test_name_to_name(self): 31 | """ 32 | Package with no title correctly returns name. 33 | """ 34 | 35 | my_id = str(uuid.uuid4()) 36 | context = {"session": model.Session} 37 | factories.Dataset(id=my_id, title="", name="my-name") 38 | 39 | result = convert_package_name_or_id_to_title_or_name( 40 | "my-name", context 41 | ) 42 | assert "my-name" == result 43 | 44 | def test_id_to_title(self): 45 | """ 46 | Package correctly returns title. 47 | """ 48 | 49 | my_id = str(uuid.uuid4()) 50 | context = {"session": model.Session} 51 | factories.Dataset(id=my_id, title="My Title", name="my-name") 52 | 53 | result = convert_package_name_or_id_to_title_or_name(my_id, context) 54 | assert "My Title" == result 55 | 56 | def test_id_to_name(self): 57 | """ 58 | Package with no title correctly returns name. 59 | """ 60 | 61 | my_id = str(uuid.uuid4()) 62 | context = {"session": model.Session} 63 | factories.Dataset(id=my_id, title="", name="my-name") 64 | 65 | result = convert_package_name_or_id_to_title_or_name(my_id, context) 66 | assert "my-name" == result 67 | 68 | def test_with_no_package_id_exists(self): 69 | """ 70 | No package with id exists. Raises error. 71 | """ 72 | context = {"session": model.Session} 73 | 74 | with pytest.raises(toolkit.Invalid): 75 | convert_package_name_or_id_to_title_or_name( 76 | "my-non-existent-id", context=context, 77 | ) 78 | -------------------------------------------------------------------------------- /ckanext/showcase/tests/test_helpers.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ckan.plugins import toolkit as tk 4 | 5 | from ckan.tests import factories 6 | 7 | import ckanext.showcase.logic.helpers as showcase_helpers 8 | 9 | 10 | @pytest.mark.usefixtures("with_plugins", "clean_db", "clean_index") 11 | class TestGetSiteStatistics(object): 12 | def test_dataset_count_no_datasets(self): 13 | """ 14 | Dataset and showcase count is 0 when no datasets, and no showcases. 15 | """ 16 | if not tk.check_ckan_version(min_version="2.5"): 17 | pytest.skip( 18 | reason="get_site_statistics without user broken in CKAN 2.4" 19 | ) 20 | stats = showcase_helpers.get_site_statistics() 21 | assert stats["dataset_count"] == 0 22 | assert stats["showcase_count"] == 0 23 | 24 | def test_dataset_count_no_datasets_some_showcases(self): 25 | """ 26 | Dataset and showcase count is 0 when no datasets, but some showcases. 27 | """ 28 | if not tk.check_ckan_version(min_version="2.5"): 29 | pytest.skip( 30 | reason="get_site_statistics without user broken in CKAN 2.4" 31 | ) 32 | for i in range(0, 10): 33 | factories.Dataset(type="showcase") 34 | 35 | stats = showcase_helpers.get_site_statistics() 36 | assert stats["dataset_count"] == 0 37 | assert stats["showcase_count"] == 10 38 | 39 | def test_dataset_count_some_datasets_no_showcases(self): 40 | """ 41 | Dataset and showcase count is correct when there are datasets, but no 42 | showcases. 43 | """ 44 | if not tk.check_ckan_version(min_version="2.5"): 45 | pytest.skip( 46 | reason="get_site_statistics without user broken in CKAN 2.4" 47 | ) 48 | for i in range(0, 10): 49 | factories.Dataset() 50 | 51 | stats = showcase_helpers.get_site_statistics() 52 | assert stats["dataset_count"] == 10 53 | assert stats["showcase_count"] == 0 54 | 55 | def test_dataset_count_some_datasets_some_showcases(self): 56 | """ 57 | Dataset and showcase count is correct when there are datasets and some 58 | showcases. 59 | """ 60 | if not tk.check_ckan_version(min_version="2.5"): 61 | pytest.skip( 62 | reason="get_site_statistics without user broken in CKAN 2.4" 63 | ) 64 | for i in range(0, 10): 65 | factories.Dataset() 66 | 67 | for i in range(0, 5): 68 | factories.Dataset(type="showcase") 69 | 70 | stats = showcase_helpers.get_site_statistics() 71 | assert stats["dataset_count"] == 10 72 | assert stats["showcase_count"] == 5 73 | -------------------------------------------------------------------------------- /ckanext/showcase/tests/test_plugin.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from bs4 import BeautifulSoup 3 | 4 | from ckan.lib.helpers import url_for 5 | 6 | 7 | from ckan.plugins import toolkit as tk 8 | import ckan.model as model 9 | 10 | from ckan.tests import factories, helpers 11 | 12 | from ckanext.showcase.model import ShowcasePackageAssociation 13 | 14 | import logging 15 | 16 | log = logging.getLogger(__name__) 17 | 18 | 19 | @pytest.mark.usefixtures("with_plugins", "clean_db", "clean_index") 20 | class TestShowcaseIndex(object): 21 | def test_showcase_listed_on_index(self, app): 22 | """ 23 | An added Showcase will appear on the Showcase index page. 24 | """ 25 | 26 | factories.Dataset(type="showcase", name="my-showcase") 27 | 28 | response = app.get("/showcase", status=200) 29 | assert "1 showcase found" in response.body 30 | assert "my-showcase" in response.body 31 | 32 | 33 | @pytest.mark.usefixtures("with_plugins", "clean_db", "clean_index") 34 | class TestShowcaseNewView(object): 35 | def test_showcase_create_form_renders(self, app): 36 | 37 | sysadmin = factories.Sysadmin() 38 | 39 | env = {"REMOTE_USER": sysadmin["name"].encode("ascii")} 40 | response = app.get(url=url_for("showcase_new"), extra_environ=env,) 41 | assert "dataset-edit" in response 42 | 43 | def test_showcase_new_redirects_to_manage_datasets(self, app): 44 | """Creating a new showcase redirects to the manage datasets form.""" 45 | sysadmin = factories.Sysadmin() 46 | # need a dataset for the 'bulk_action.showcase_add' button to show 47 | factories.Dataset() 48 | 49 | env = {"REMOTE_USER": sysadmin["name"].encode("ascii")} 50 | response = app.post( 51 | url=url_for("showcase_blueprint.new"), 52 | extra_environ=env, 53 | data={"name": "my-showcase"}, 54 | follow_redirects=False 55 | ) 56 | 57 | # Requested page is the manage_datasets url. 58 | assert ( 59 | url_for("showcase_blueprint.manage_datasets", id="my-showcase") 60 | in response.location 61 | ) 62 | 63 | def test_create_showcase(self, app): 64 | sysadmin = factories.Sysadmin() 65 | 66 | env = {"REMOTE_USER": sysadmin["name"].encode("ascii")} 67 | app.post( 68 | url=url_for("showcase_blueprint.new"), 69 | extra_environ=env, 70 | data={ 71 | "name": "my-test-showcase", 72 | "image_url": "", 73 | "notes": "My new description!" 74 | } 75 | ) 76 | 77 | res = app.get( 78 | url=url_for("showcase_blueprint.read", id="my-test-showcase"), 79 | extra_environ=env, 80 | ) 81 | assert "my-test-showcase" in res.body 82 | assert "My new description!" in res.body 83 | 84 | 85 | 86 | @pytest.mark.usefixtures("with_plugins", "clean_db", "clean_index") 87 | class TestShowcaseEditView(object): 88 | def test_showcase_edit_form_renders(self, app): 89 | """ 90 | Edit form renders in response for ShowcaseController edit action. 91 | """ 92 | 93 | sysadmin = factories.Sysadmin() 94 | factories.Dataset(name="my-showcase", type="showcase") 95 | 96 | env = {"REMOTE_USER": sysadmin["name"].encode("ascii")} 97 | response = app.get( 98 | url=url_for("showcase_edit", id="my-showcase"), extra_environ=env, 99 | ) 100 | assert "dataset-edit" in response 101 | 102 | def test_showcase_edit_redirects_to_showcase_details(self, app): 103 | """Editing a showcase redirects to the showcase details page.""" 104 | if tk.check_ckan_version("2.9"): 105 | pytest.skip("submit_and_follow not supported") 106 | 107 | sysadmin = factories.Sysadmin() 108 | factories.Dataset(name="my-showcase", type="showcase") 109 | 110 | env = {"REMOTE_USER": sysadmin["name"].encode("ascii")} 111 | response = app.get( 112 | url=url_for("showcase_edit", id="my-showcase"), extra_environ=env, 113 | ) 114 | 115 | # edit showcase 116 | form = response.forms["dataset-edit"] 117 | form["name"] = u"my-changed-showcase" 118 | edit_response = helpers.submit_and_follow(app, form, env, "save") 119 | 120 | # Requested page is the showcase read url. 121 | assert ( 122 | url_for("showcase_read", id="my-changed-showcase") 123 | == edit_response.request.path 124 | ) 125 | 126 | def test_edit_showcase(self, app): 127 | sysadmin = factories.Sysadmin() 128 | factories.Dataset(name="my-showcase", type="showcase") 129 | env = {"REMOTE_USER": sysadmin["name"]} 130 | 131 | app.post( 132 | url=url_for("showcase_blueprint.edit", id="my-showcase"), 133 | extra_environ=env, 134 | data={ 135 | "name": "my-edited-showcase", 136 | "notes": "My new description!", 137 | "image_url": "" 138 | } 139 | ) 140 | res = app.get( 141 | url=url_for("showcase_blueprint.edit", id="my-edited-showcase"), 142 | extra_environ=env, 143 | ) 144 | assert "my-edited-showcase" in res.body 145 | assert "My new description!" in res.body 146 | 147 | 148 | @pytest.mark.usefixtures("with_plugins", "clean_db", "clean_index") 149 | class TestDatasetView(object): 150 | 151 | """Plugin adds a new showcases view for datasets.""" 152 | 153 | def test_dataset_read_has_showcases_tab(self, app): 154 | """ 155 | Dataset view page has a new Showcases tab linked to the correct place. 156 | """ 157 | 158 | dataset = factories.Dataset(name="my-dataset") 159 | 160 | url = url = url_for("dataset.read", id=dataset["id"]) 161 | 162 | response = app.get(url) 163 | # response contains link to dataset's showcase list 164 | assert "/dataset/showcases/{0}".format(dataset["name"]) in response 165 | 166 | def test_dataset_showcase_page_lists_showcases_no_associations(self, app): 167 | """ 168 | No showcases are listed if dataset has no showcase associations. 169 | """ 170 | 171 | dataset = factories.Dataset(name="my-dataset") 172 | 173 | response = app.get( 174 | url=url_for("showcase_blueprint.dataset_showcase_list", id=dataset["id"]) 175 | ) 176 | 177 | assert ( 178 | len( 179 | BeautifulSoup(response.body).select( 180 | "ul.media-grid li.media-item" 181 | ) 182 | ) 183 | == 0 184 | ) 185 | 186 | def test_dataset_showcase_page_lists_showcases_two_associations(self, app): 187 | """ 188 | Two showcases are listed for dataset with two showcase associations. 189 | """ 190 | 191 | sysadmin = factories.Sysadmin() 192 | dataset = factories.Dataset(name="my-dataset") 193 | showcase_one = factories.Dataset( 194 | name="my-first-showcase", type="showcase" 195 | ) 196 | showcase_two = factories.Dataset( 197 | name="my-second-showcase", type="showcase" 198 | ) 199 | factories.Dataset(name="my-third-showcase", type="showcase") 200 | 201 | context = {"user": sysadmin["name"]} 202 | helpers.call_action( 203 | "ckanext_showcase_package_association_create", 204 | context=context, 205 | package_id=dataset["id"], 206 | showcase_id=showcase_one["id"], 207 | ) 208 | helpers.call_action( 209 | "ckanext_showcase_package_association_create", 210 | context=context, 211 | package_id=dataset["id"], 212 | showcase_id=showcase_two["id"], 213 | ) 214 | 215 | response = app.get( 216 | url=url_for("showcase_blueprint.dataset_showcase_list", id=dataset["id"]) 217 | ) 218 | 219 | assert len(BeautifulSoup(response.body).select("li.media-item")) == 2 220 | assert "my-first-showcase" in response 221 | assert "my-second-showcase" in response 222 | assert "my-third-showcase" not in response 223 | 224 | def test_dataset_showcase_page_add_to_showcase_dropdown_list(self, app): 225 | """ 226 | Add to showcase dropdown only lists showcases that aren't already 227 | associated with dataset. 228 | """ 229 | sysadmin = factories.Sysadmin() 230 | dataset = factories.Dataset(name="my-dataset") 231 | showcase_one = factories.Dataset( 232 | name="my-first-showcase", type="showcase" 233 | ) 234 | showcase_two = factories.Dataset( 235 | name="my-second-showcase", type="showcase" 236 | ) 237 | showcase_three = factories.Dataset( 238 | name="my-third-showcase", type="showcase" 239 | ) 240 | 241 | context = {"user": sysadmin["name"]} 242 | helpers.call_action( 243 | "ckanext_showcase_package_association_create", 244 | context=context, 245 | package_id=dataset["id"], 246 | showcase_id=showcase_one["id"], 247 | ) 248 | 249 | response = app.get( 250 | url=url_for("showcase_blueprint.dataset_showcase_list", id=dataset["id"]), 251 | extra_environ={"REMOTE_USER": str(sysadmin["name"])}, 252 | ) 253 | 254 | assert f'