From f2a2d6bb01f302a016fc7a1c7bfdc1864525e598 Mon Sep 17 00:00:00 2001 From: Will Barton Date: Thu, 3 May 2018 10:28:38 -0400 Subject: [PATCH 1/6] Add initial version of treemodeladmin --- .gitattributes | 1 + .travis.yml | 26 ++++ MANIFEST.in | 3 + README.md | 140 ++++++++++-------- setup.py | 53 +++++++ tox.ini | 60 ++++++++ treemodeladmin/__init__.py | 1 + treemodeladmin/apps.py | 8 + treemodeladmin/helpers.py | 9 ++ treemodeladmin/options.py | 55 +++++++ .../treemodeladmin/includes/breadcrumb.html | 8 + .../treemodeladmin/includes/header.html | 24 +++ .../includes/tree_result_list.html | 28 ++++ .../includes/tree_result_row.html | 15 ++ .../templates/treemodeladmin/index.html | 77 ++++++++++ treemodeladmin/templatetags/__init__.py | 0 .../templatetags/treemodeladmin_tags.py | 36 +++++ treemodeladmin/tests/__init__.py | 0 treemodeladmin/tests/settings.py | 109 ++++++++++++++ treemodeladmin/tests/test_options.py | 107 +++++++++++++ treemodeladmin/tests/test_views.py | 69 +++++++++ .../tests/treemodeladmintest/__init__.py | 0 .../tests/treemodeladmintest/apps.py | 8 + .../fixtures/treemodeladmin_test.json | 62 ++++++++ .../migrations/0001_initial.py | 29 ++++ .../treemodeladmintest/migrations/__init__.py | 0 .../tests/treemodeladmintest/models.py | 16 ++ .../tests/treemodeladmintest/wagtail_hooks.py | 25 ++++ treemodeladmin/tests/urls.py | 8 + treemodeladmin/views.py | 123 +++++++++++++++ 30 files changed, 1037 insertions(+), 63 deletions(-) create mode 100644 .travis.yml create mode 100644 MANIFEST.in create mode 100644 setup.py create mode 100644 tox.ini create mode 100644 treemodeladmin/__init__.py create mode 100644 treemodeladmin/apps.py create mode 100644 treemodeladmin/helpers.py create mode 100644 treemodeladmin/options.py create mode 100644 treemodeladmin/templates/treemodeladmin/includes/breadcrumb.html create mode 100644 treemodeladmin/templates/treemodeladmin/includes/header.html create mode 100644 treemodeladmin/templates/treemodeladmin/includes/tree_result_list.html create mode 100644 treemodeladmin/templates/treemodeladmin/includes/tree_result_row.html create mode 100644 treemodeladmin/templates/treemodeladmin/index.html create mode 100644 treemodeladmin/templatetags/__init__.py create mode 100644 treemodeladmin/templatetags/treemodeladmin_tags.py create mode 100644 treemodeladmin/tests/__init__.py create mode 100644 treemodeladmin/tests/settings.py create mode 100644 treemodeladmin/tests/test_options.py create mode 100644 treemodeladmin/tests/test_views.py create mode 100644 treemodeladmin/tests/treemodeladmintest/__init__.py create mode 100644 treemodeladmin/tests/treemodeladmintest/apps.py create mode 100644 treemodeladmin/tests/treemodeladmintest/fixtures/treemodeladmin_test.json create mode 100644 treemodeladmin/tests/treemodeladmintest/migrations/0001_initial.py create mode 100644 treemodeladmin/tests/treemodeladmintest/migrations/__init__.py create mode 100644 treemodeladmin/tests/treemodeladmintest/models.py create mode 100644 treemodeladmin/tests/treemodeladmintest/wagtail_hooks.py create mode 100644 treemodeladmin/tests/urls.py create mode 100644 treemodeladmin/views.py diff --git a/.gitattributes b/.gitattributes index 3d432e0..6ed19dc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ +# Auto detect text files and perform LF normalization /CHANGELOG.md merge=union diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b18f6ca --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +language: python +cache: pip + +matrix: + include: + - env: TOXENV=lint + python: 3.6 + - env: TOXENV=py27-dj18-wag113 + python: 2.7 + - env: TOXENV=py27-dj111-wag113 + python: 2.7 + - env: TOXENV=py36-dj18-wag113 + python: 3.6 + - env: TOXENV=py36-dj111-wag113 + python: 3.6 + - env: TOXENV=py36-dj20-wag20 + python: 3.6 + +install: + pip install tox coveralls + +script: + tox + +after_success: + coveralls diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..fc1fa37 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE +include README.md +global-include *.html diff --git a/README.md b/README.md index f6dc35a..d06a9a4 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,120 @@ -#### CFPB Open Source Project Template Instructions +# Wagtail-TreeModelAdmin -1. Create a new project. -2. [Copy these files into the new project](#installation) -3. Update the README, replacing the contents below as prescribed. -4. Add any libraries, assets, or hard dependencies whose source code will be included - in the project's repository to the _Exceptions_ section in the [TERMS](TERMS.md). - - If no exceptions are needed, remove that section from TERMS. -5. If working with an existing code base, answer the questions on the [open source checklist](opensource-checklist.md) -6. Delete these instructions and everything up to the _Project Title_ from the README. -7. Write some great software and tell people about it. +[![Build Status](https://travis-ci.org/cfpb/wagtail-treemodeladmin.svg?branch=master)](https://travis-ci.org/cfpb/wagtail-treemodeladmin) +[![Coverage Status](https://coveralls.io/repos/github/cfpb/wagtail-treemodeladmin/badge.svg?branch=master)](https://coveralls.io/github/cfpb/wagtail-treemodeladmin?branch=master) -> Keep the README fresh! It's the first thing people see and will make the initial impression. +Wagtail-TreeModelAdmin is an extension for Wagtail's ModelAdmin that allows for a page explorer-like navigation of Django model relationships within the Wagtail admin. + +- [Dependencies](#dependencies) +- [Installation](#installation) +- [Concepts](#concepts) +- [Usage](#usage) + - [Quickstart](#quickstart) +- [API](#api) +- [Getting help](#getting-help) +- [Getting involved](#getting-involved) +- [Licensing](#licensing) +- [Credits and references](#credits-and-references) + +## Dependencies + +- Django 1.8+ (including Django 2.0) +- Wagtail 1.13+ (including Wagtail 2.0) +- Python 2.7+, 3.6+ ## Installation -To install all of the template files, run the following script from the root of your project's directory: +1. Install wagtail-treemodeladmin: -``` -bash -c "$(curl -s https://raw.githubusercontent.com/CFPB/development/master/open-source-template.sh)" +```shell +pip install wagtail-treemodeladmin ``` ----- +2. Add `treemodeladmin` as an installed app in your Django `settings.py`: -# Project Title - -**Description**: Put a meaningful, short, plain-language description of what -this project is trying to accomplish and why it matters. -Describe the problem(s) this project solves. -Describe how this software can improve the lives of its audience. + ```python + INSTALLED_APPS = ( + ... + 'treemodeladmin', + ... + ) +``` -Other things to include: +## Concepts - - **Technology stack**: Indicate the technological nature of the software, including primary programming language(s) and whether the software is intended as standalone or as a module in a framework or other ecosystem. - - **Status**: Alpha, Beta, 1.1, etc. It's OK to write a sentence, too. The goal is to let interested people know where this project is at. This is also a good place to link to the [CHANGELOG](CHANGELOG.md). - - **Links to production or demo instances** - - Describe what sets this apart from related-projects. Linking to another doc or page is OK if this can't be expressed in a sentence or two. +Wagtail-TreeModelAdmin allows for a Wagtail page explorer-like navigation of Django one-to-many relationships within the Wagtail admin. In doing this, it conceptualizes the Django [`ForeignKey`](https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.ForeignKey) relationship as one of parents-to-children. The parent is the destination `to` of the `ForeignKey` relationship, the child is the source of the relationship. +Wagtail-TreeModelAdmin is an extension of [Wagtail's ModelAdmin](http://docs.wagtail.io/en/latest/reference/contrib/modeladmin/index.html). It is intended to be used exactly like `ModelAdmin`. -**Screenshot**: If the software has visual components, place a screenshot after the description; e.g., +## Usage -![](https://raw.githubusercontent.com/cfpb/open-source-project-template/master/screenshot.png) +### Quickstart +To use Wagtail-TreeModelAdmin you first need to define some models that will be exposed in the Wagtail Admin. -## Dependencies +``` +# libraryapp/models.py -Describe any dependencies that must be installed for this software to work. -This includes programming languages, databases or other storage mechanisms, build tools, frameworks, and so forth. -If specific versions of other software are required, or known not to work, call that out. +from django.db import models -## Installation -Detailed instructions on how to install, configure, and get the project running. -This should be frequently tested to ensure reliability. Alternatively, link to -a separate [INSTALL](INSTALL.md) document. +class Author(models.Model): + name = models.CharField(max_length=255) -## Configuration +class Book(models.Model): + author = models.ForeignKey(Author, on_delete=models.PROTECT) + title = models.CharField(max_length=255) +``` -If the software is configurable, describe it in detail, either here or in other documentation to which you link. +Then create the `TreeModelAdmin` subclasses and register the root the tree using `modeladmin_register`: -## Usage +```python +# libraryapp/wagtail_hooks.py +from wagtail.contrib.modeladmin.options import modeladmin_register -Show users how to use the software. -Be specific. -Use appropriate formatting when showing code snippets. +from treemodeladmin.options import TreeModelAdmin +from libraryapp.models import Author, Book -## How to test the software -If the software includes automated tests, detail how to run those tests. +class BookModelAdmin(TreeModelAdmin): + model = Book + parent_field = 'author' -## Known issues -Document any known significant shortcomings with the software. +@modeladmin_register +class AuthorModelAdmin(TreeModelAdmin): + menu_label = 'Library' + menu_icon = 'list-ul' + model = Author + child_field = 'book_set' + child_model_admin = BookModelAdmin +``` -## Getting help +Then visit the Wagtail admin. `Library` will be in the menu, and will give you a list of authors, and each author will have a link that will take to their books. -Instruct users how to get help with this software; this might include links to an issue tracker, wiki, mailing list, etc. +## API -**Example** +Wagtail-TreeModelAdmin uses three new attributes on ModelAdmin subclasses to express parent/child relationships: -If you have questions, concerns, bug reports, etc, please file an issue in this repository's Issue Tracker. +- `parent_field`: The name of the Django [`ForeignKey`](https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.ForeignKey) on a child model. +- `child_field`: The [`related_name`](https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.ForeignKey.related_name) on a Django `ForeignKey`. +- `child_model_admin` -## Getting involved +Any `TreeModelAdmin` subclass can specify both parent and child relationships. The root of the tree (either the `TreeModelAdmin` included in a `ModelAdminGroup` or the `@modeladmin_register`ed `TreeModelAdmin` subclass) should only include `child_*` fields. -This section should detail why people should get involved and describe key areas you are -currently focusing on; e.g., trying to get feedback on features, fixing certain bugs, building -important pieces, etc. +## Getting help -General instructions on _how_ to contribute should be stated with a link to [CONTRIBUTING](CONTRIBUTING.md). +Please add issues to the [issue tracker](https://github.com/cfpb/wagtail-treemodeladmin/issues). +## Getting involved ----- +General instructions on _how_ to contribute can be found in [CONTRIBUTING](CONTRIBUTING.md). -## Open source licensing info +## Licensing 1. [TERMS](TERMS.md) 2. [LICENSE](LICENSE) 3. [CFPB Source Code Policy](https://github.com/cfpb/source-code-policy/) - ----- - ## Credits and references -1. Projects that inspired you -2. Related projects -3. Books, papers, talks, or other sources that have meaningful impact or influence on this project +1. Forked from [cfgov-refresh](https://github.com/cfpb/cfgov-refresh) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0547353 --- /dev/null +++ b/setup.py @@ -0,0 +1,53 @@ +from setuptools import find_packages, setup + +try: + import pypandoc + long_description = pypandoc.convert('README.md', 'rst') +except (IOError, ImportError): + long_description = open('README.md', 'r').read() + + +install_requires = [ + 'Django>=1.8,<2.1', + 'wagtail>=1.10,<2.1', +] + + +testing_extras = [ + 'mock>=2.0.0', + 'coverage>=3.7.0', +] + + +setup( + name='wagtail-treemodeladmin', + url='https://github.com/cfpb/wagtail-treemodeladmin', + author='CFPB', + author_email='tech@cfpb.gov', + description='TreeModelAdmin for Wagtail', + long_description=long_description, + license='CC0', + version='0.1.0', + include_package_data=True, + packages=find_packages(), + install_requires=install_requires, + extras_require={ + 'testing': testing_extras, + }, + classifiers=[ + 'Framework :: Django', + 'Framework :: Django :: 1.11', + 'Framework :: Django :: 1.8', + 'Framework :: Django :: 2.0', + 'Framework :: Wagtail', + 'Framework :: Wagtail :: 1', + 'Framework :: Wagtail :: 2', + 'License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication', + 'License :: Public Domain', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + ] +) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..2d5202f --- /dev/null +++ b/tox.ini @@ -0,0 +1,60 @@ +[tox] +skipsdist=True +envlist= + lint, + py{27,36}-dj{18,111}-wag{113}, + py{36}-dj{20}-wag{20} + +[testenv] +install_command=pip install -e ".[testing]" -U {opts} {packages} +commands= + coverage erase + coverage run --source='treemodeladmin' {envbindir}/django-admin.py test {posargs} +setenv= + DJANGO_SETTINGS_MODULE=treemodeladmin.tests.settings + +basepython= + py27: python2.7 + py36: python3.6 + +deps= + dj18: Django>=1.8,<1.9 + dj111: Django>=1.11,<1.12 + dj20: Django>=2.0,<2.1 + wag113: wagtail>=1.13,<1.14 + wag20: wagtail>=2.0,<2.1 + +[testenv:lint] +basepython=python3.6 +deps= + flake8>=2.2.0 + isort>=4.2.15 +commands= + flake8 . + isort --check-only --diff --recursive treemodeladmin + +[flake8] +ignore=E731,W503 +exclude= + .tox, + __pycache__, + treemodeladmin/migrations/*, + treemodeladmin/tests/treemodeladmintest/migrations/* + +[isort] +combine_as_imports=1 +lines_after_imports=2 +include_trailing_comma=1 +multi_line_output=3 +skip=.tox,migrations +not_skip=__init__.py +use_parentheses=1 +known_django=django +known_wagtail=wagtail +known_future_library=future,six +default_section=THIRDPARTY +sections=FUTURE,STDLIB,DJANGO,WAGTAIL,THIRDPARTY,FIRSTPARTY,LOCALFOLDER + +[coverage:run] +omit = + treemodeladmin/tests/* diff --git a/treemodeladmin/__init__.py b/treemodeladmin/__init__.py new file mode 100644 index 0000000..2e03ad6 --- /dev/null +++ b/treemodeladmin/__init__.py @@ -0,0 +1 @@ +default_app_config = 'treemodeladmin.apps.WagtailTreeModelAdminAppConfig' diff --git a/treemodeladmin/apps.py b/treemodeladmin/apps.py new file mode 100644 index 0000000..785fc89 --- /dev/null +++ b/treemodeladmin/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ + + +class WagtailTreeModelAdminAppConfig(AppConfig): + name = 'treemodeladmin' + label = 'wagtailtreemodeladmin' + verbose_name = _("Wagtail TreeModelAdmin") diff --git a/treemodeladmin/helpers.py b/treemodeladmin/helpers.py new file mode 100644 index 0000000..e20d639 --- /dev/null +++ b/treemodeladmin/helpers.py @@ -0,0 +1,9 @@ +from wagtail.contrib.modeladmin.helpers import ButtonHelper + + +class TreeButtonHelper(ButtonHelper): + + def add_button(self, classnames_add=None, classnames_exclude=None): + return super(TreeButtonHelper, self).add_button( + classnames_add=['button-small'] + ) diff --git a/treemodeladmin/options.py b/treemodeladmin/options.py new file mode 100644 index 0000000..b4ec84f --- /dev/null +++ b/treemodeladmin/options.py @@ -0,0 +1,55 @@ +from wagtail.contrib.modeladmin.options import ModelAdmin + +from treemodeladmin.helpers import TreeButtonHelper +from treemodeladmin.views import TreeIndexView + + +class TreeModelAdmin(ModelAdmin): + child_field = None + child_model_admin = None + child_instance = None + parent_field = None + index_view_class = TreeIndexView + index_template_name = 'treemodeladmin/index.html' + button_helper_class = TreeButtonHelper + + def __init__(self, parent=None): + super(TreeModelAdmin, self).__init__(parent=parent) + + if self.has_child(): + self.child_instance = self.child_model_admin(parent=self) + + def has_child(self): + return ( + (self.child_field is not None) and + (self.child_model_admin is not None) and + hasattr(self.model, self.child_field) + ) + + def has_parent(self): + return (self.parent is not None and + isinstance(self.parent, TreeModelAdmin)) + + def get_child_field(self): + if self.has_child(): + return self.child_field + + def get_child_name(self): + if self.has_child(): + return self.child_instance.model._meta.verbose_name + + def get_child_name_plural(self): + if self.has_child(): + return self.child_instance.model._meta.verbose_name_plural + + def get_parent_field(self): + if self.has_parent(): + return self.parent_field + + def get_admin_urls_for_registration(self, parent=None): + urls = super(TreeModelAdmin, self).get_admin_urls_for_registration() + + if self.has_child(): + urls = urls + self.child_instance.get_admin_urls_for_registration() + + return urls diff --git a/treemodeladmin/templates/treemodeladmin/includes/breadcrumb.html b/treemodeladmin/templates/treemodeladmin/includes/breadcrumb.html new file mode 100644 index 0000000..9cd5d92 --- /dev/null +++ b/treemodeladmin/templates/treemodeladmin/includes/breadcrumb.html @@ -0,0 +1,8 @@ +{% load i18n treemodeladmin_tags %} + diff --git a/treemodeladmin/templates/treemodeladmin/includes/header.html b/treemodeladmin/templates/treemodeladmin/includes/header.html new file mode 100644 index 0000000..7776acf --- /dev/null +++ b/treemodeladmin/templates/treemodeladmin/includes/header.html @@ -0,0 +1,24 @@ +{% load i18n modeladmin_tags treemodeladmin_tags %} +
+ {% include "treemodeladmin/includes/breadcrumb.html" %} +
+
+
+
+
+ {% block h1 %}

{{ view.get_page_title }}

{% endblock %} + {% include 'modeladmin/includes/button.html' with button=view.get_parent_edit_button %} + {% include 'modeladmin/includes/button.html' with button=view.button_helper.add_button %} +
+ {% block search %}{% search_form %}{% endblock %} +
+ {% block header_extra %} + {% if user_can_create %} +
+
+
+
+ {% endif %} + {% endblock %} +
+
diff --git a/treemodeladmin/templates/treemodeladmin/includes/tree_result_list.html b/treemodeladmin/templates/treemodeladmin/includes/tree_result_list.html new file mode 100644 index 0000000..27de270 --- /dev/null +++ b/treemodeladmin/templates/treemodeladmin/includes/tree_result_list.html @@ -0,0 +1,28 @@ +{% load i18n modeladmin_tags treemodeladmin_tags %} +{% if results %} + + + + {% for header in result_headers %} + + {% endfor %} + {% if view.has_child_admin %} + + {% endif %} + + + + {% for result in results %} + {% tree_result_row_display forloop.counter0 %} + {% endfor %} + +
+ {% if header.sortable %}{% endif %} + {{ header.text|capfirst }} + {% if header.sortable %}{% endif %} +
+{% else %} +
+

{% blocktrans with view.verbose_name_plural as name %}Sorry, there are no {{ name }} matching your search parameters.{% endblocktrans %}

+
+{% endif %} diff --git a/treemodeladmin/templates/treemodeladmin/includes/tree_result_row.html b/treemodeladmin/templates/treemodeladmin/includes/tree_result_row.html new file mode 100644 index 0000000..a7b58e8 --- /dev/null +++ b/treemodeladmin/templates/treemodeladmin/includes/tree_result_row.html @@ -0,0 +1,15 @@ +{% load modeladmin_tags %} + + {% for item in result %} + {% result_row_value_display forloop.counter0 %} + {% endfor %} + {% if children.count > 0 %} + + Explore {{ obj }}'s {{ view.child_name_plural }} + + {% else %} + + Add a {{ obj }} {{ view.child_name }} + + {% endif %} + diff --git a/treemodeladmin/templates/treemodeladmin/index.html b/treemodeladmin/templates/treemodeladmin/index.html new file mode 100644 index 0000000..850ec09 --- /dev/null +++ b/treemodeladmin/templates/treemodeladmin/index.html @@ -0,0 +1,77 @@ +{% extends "wagtailadmin/base.html" %} +{% load i18n modeladmin_tags treemodeladmin_tags %} + +{% block titletag %}{{ view.get_meta_title }}{% endblock %} + +{% block css %} + {{ block.super }} + {{ view.media.css }} +{% endblock %} + +{% block extra_js %} + {{ view.media.js }} +{% endblock %} + +{% block content %} + {% block header %} + {% include "treemodeladmin/includes/header.html" %} + {% endblock %} + + {% block content_main %} +
+
+ {% block content_cols %} + + {% block filters %} + {% if view.has_filters and all_count %} +
+

{% trans 'Filter' %}

+ {% for spec in view.filter_specs %}{% admin_list_filter view spec %}{% endfor %} +
+ {% endif %} + {% endblock %} + +
+ {% block result_list %} + {% if not all_count %} +
+ {% if no_valid_parents %} +

{% blocktrans with view.verbose_name_plural as name %}No {{ name }} have been created yet. One of the following must be created before you can add any {{ name }}:{% endblocktrans %}

+
    + {% for type in required_parent_types %}
  • {{ type|title }}
  • {% endfor %} +
+ {% else %} +

{% blocktrans with view.verbose_name_plural as name %}No {{ name }} have been created yet.{% endblocktrans %} + {% if user_can_create %} + {% blocktrans with view.create_url as url %} + Why not add one? + {% endblocktrans %} + {% endif %}

+ {% endif %} +
+ {% elif view.has_child %} + {% tree_result_list %} + {% else %} + {% result_list %} + {% endif %} + {% endblock %} +
+ + {% block pagination %} + + {% endblock %} + + {% endblock %} +
+
+ {% endblock %} + +{% endblock %} diff --git a/treemodeladmin/templatetags/__init__.py b/treemodeladmin/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/treemodeladmin/templatetags/treemodeladmin_tags.py b/treemodeladmin/templatetags/treemodeladmin_tags.py new file mode 100644 index 0000000..ecb7e5f --- /dev/null +++ b/treemodeladmin/templatetags/treemodeladmin_tags.py @@ -0,0 +1,36 @@ +from django.contrib.admin.utils import quote +from django.template import Library + +from wagtail.contrib.modeladmin.templatetags.modeladmin_tags import ( + result_list, + result_row_display, +) + + +register = Library() + + +@register.inclusion_tag("treemodeladmin/includes/tree_result_list.html", + takes_context=True) +def tree_result_list(context): + """ Displays the headers and data list together with a link to children """ + context = result_list(context) + return context + + +@register.inclusion_tag( + "treemodeladmin/includes/tree_result_row.html", takes_context=True) +def tree_result_row_display(context, index): + context = result_row_display(context, index) + obj = context['object_list'][index] + view = context['view'] + + if view.has_child_admin: + context.update({ + 'children': view.get_children(obj), + 'child_index_url': view.child_url_helper.index_url, + 'child_filter': view.get_child_filter(quote(obj.pk)), + 'child_create_url': view.child_url_helper.create_url, + }) + + return context diff --git a/treemodeladmin/tests/__init__.py b/treemodeladmin/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/treemodeladmin/tests/settings.py b/treemodeladmin/tests/settings.py new file mode 100644 index 0000000..8e87e64 --- /dev/null +++ b/treemodeladmin/tests/settings.py @@ -0,0 +1,109 @@ +from __future__ import absolute_import, unicode_literals + +import os + +import django + + +DEBUG = True + +ALLOWED_HOSTS = ['*'] + +SECRET_KEY = 'not needed' + +ROOT_URLCONF = 'treemodeladmin.tests.urls' + +DATABASES = { + 'default': { + 'ENGINE': os.environ.get( + 'DATABASE_ENGINE', + 'django.db.backends.sqlite3' + ), + 'NAME': os.environ.get('DATABASE_NAME', 'wagtailsharing.sqlite'), + 'USER': os.environ.get('DATABASE_USER', None), + 'PASSWORD': os.environ.get('DATABASE_PASS', None), + 'HOST': os.environ.get('DATABASE_HOST', None), + + 'TEST': { + 'NAME': os.environ.get('DATABASE_NAME', None), + }, + }, +} + +if django.VERSION >= (1, 10): + MIDDLEWARE = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'wagtail.wagtailcore.middleware.SiteMiddleware', + ) +else: + MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'wagtail.wagtailcore.middleware.SiteMiddleware', + ) + +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.staticfiles', + + 'taggit', + + 'wagtail.contrib.modeladmin', + 'wagtail.contrib.settings', + 'wagtail.tests.testapp', + 'wagtail.wagtailadmin', + 'wagtail.wagtailcore', + 'wagtail.wagtaildocs', + 'wagtail.wagtailforms', + 'wagtail.wagtailimages', + 'wagtail.wagtailsites', + 'wagtail.wagtailusers', + + 'treemodeladmin', + 'treemodeladmin.tests.treemodeladmintest', +) + +STATIC_URL = '/static/' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'django.template.context_processors.request', + ], + 'debug': True, + }, + }, +] + +WAGTAIL_SITE_NAME = 'Test Site' + +WAGTAILADMIN_RICH_TEXT_EDITORS = { + 'default': { + 'WIDGET': 'wagtail.wagtailadmin.rich_text.HalloRichTextArea' + }, + 'custom': { + 'WIDGET': 'wagtail.tests.testapp.rich_text.CustomRichTextArea' + }, +} diff --git a/treemodeladmin/tests/test_options.py b/treemodeladmin/tests/test_options.py new file mode 100644 index 0000000..1cf922f --- /dev/null +++ b/treemodeladmin/tests/test_options.py @@ -0,0 +1,107 @@ +from django.test import TestCase + +from treemodeladmin.options import TreeModelAdmin +from treemodeladmin.tests.treemodeladmintest.models import Author, Book + + +class BookHasParentModelAdmin(TreeModelAdmin): + model = Book + parent_field = 'author' + + +class AuthorHasChildModelAdmin(TreeModelAdmin): + model = Author + child_field = 'book_set' + child_model_admin = BookHasParentModelAdmin + + +class AuthorPlainModelAdmin(TreeModelAdmin): + model = Author + + +class TestTreeModelAdmin(TestCase): + + def setUp(self): + self.author_model_admin = AuthorHasChildModelAdmin() + self.book_model_admin = self.author_model_admin.child_instance + self.plain_model_admin = AuthorPlainModelAdmin() + + def test_has_child(self): + self.assertTrue(self.author_model_admin.has_child()) + self.assertFalse(self.book_model_admin.has_child()) + + def test_has_child_no_child_field(self): + self.assertFalse(self.plain_model_admin.has_child()) + + def test_has_child_no_child_model_admin(self): + self.plain_model_admin.child_field = 'book_set' + self.assertFalse(self.plain_model_admin.has_child()) + + def test_has_child_no_child_field_on_model(self): + self.plain_model_admin.child_field = 'authored_books' + self.plain_model_admin.child_model_admin = BookHasParentModelAdmin + self.assertFalse(self.plain_model_admin.has_child()) + + def test_has_parent(self): + self.assertFalse(self.author_model_admin.has_parent()) + self.assertTrue(self.book_model_admin.has_parent()) + + def test_get_child_field(self): + self.assertEqual( + self.author_model_admin.get_child_field(), + 'book_set' + ) + + def test_get_child_field_none(self): + self.assertEqual( + self.book_model_admin.get_child_field(), + None + ) + + def test_get_child_name(self): + self.assertEqual( + self.author_model_admin.get_child_name(), + 'book' + ) + + def test_get_child_name_none(self): + self.assertEqual( + self.book_model_admin.get_child_field(), + None + ) + + def test_get_child_name_plural(self): + self.assertEqual( + self.author_model_admin.get_child_name_plural(), + 'books' + ) + + def test_get_child_name_plural_none(self): + self.assertEqual( + self.book_model_admin.get_child_field(), + None + ) + + def test_get_parent_field(self): + self.assertEqual( + self.book_model_admin.get_parent_field(), + 'author' + ) + + def test_get_parent_field_none(self): + self.assertEqual( + self.author_model_admin.get_parent_field(), + None + ) + + def test_get_admin_urls_for_registration_child(self): + self.assertEqual( + len(self.author_model_admin.get_admin_urls_for_registration()), + 8 + ) + + def test_get_admin_urls_for_registration_no_child(self): + self.assertEqual( + len(self.book_model_admin.get_admin_urls_for_registration()), + 4 + ) diff --git a/treemodeladmin/tests/test_views.py b/treemodeladmin/tests/test_views.py new file mode 100644 index 0000000..cc783f4 --- /dev/null +++ b/treemodeladmin/tests/test_views.py @@ -0,0 +1,69 @@ +from django.test import TestCase + +from wagtail.tests.utils import WagtailTestUtils + + +class TestAuthorIndexView(TestCase, WagtailTestUtils): + fixtures = ['treemodeladmin_test.json'] + + def setUp(self): + self.user = self.login() + + def get(self, **params): + return self.client.get('/admin/treemodeladmintest/author/', params) + + def test_author_listing(self): + response = self.get() + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['result_count'], 4) + self.assertContains(response, "Explore J. R. R. Tolkien's books") + self.assertContains(response, "Add a J. R. Hartley book") + + def test_has_child_admin(self): + response = self.get() + self.assertTrue(response.context['view'].has_child_admin) + + def test_breadcrumbs(self): + resposne = self.get() + self.assertEqual( + list(resposne.context['view'].breadcrumbs), + [('/admin/treemodeladmintest/author/', 'authors')] + ) + + +class TestBookIndexView(TestCase, WagtailTestUtils): + fixtures = ['treemodeladmin_test.json'] + + def setUp(self): + self.user = self.login() + + def get(self, **params): + return self.client.get('/admin/treemodeladmintest/book/', params) + + def test_book_listing(self): + response = self.get() + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['result_count'], 4) + + def test_book_listing_filtered(self): + response = self.get(author=1) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['result_count'], 2) + self.assertEqual( + response.context['view'].get_page_title(), + 'J. R. R. Tolkien' + ) + + def test_has_child_admin(self): + response = self.get(author=1) + self.assertFalse(response.context['view'].has_child_admin) + + def test_breadcrumbs(self): + resposne = self.get(author=1) + self.assertEqual( + list(resposne.context['view'].breadcrumbs), + [ + ('/admin/treemodeladmintest/author/', 'authors'), + ('/admin/treemodeladmintest/book/', 'books') + ] + ) diff --git a/treemodeladmin/tests/treemodeladmintest/__init__.py b/treemodeladmin/tests/treemodeladmintest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/treemodeladmin/tests/treemodeladmintest/apps.py b/treemodeladmin/tests/treemodeladmintest/apps.py new file mode 100644 index 0000000..75b3cf5 --- /dev/null +++ b/treemodeladmin/tests/treemodeladmintest/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ + + +class TreeModelAdminTestAppConfig(AppConfig): + name = 'treemodeladmin.tests.treemodeladmintest' + label = 'test_treemodeladmintest' + verbose_name = _("Test Tree Model Admin") diff --git a/treemodeladmin/tests/treemodeladmintest/fixtures/treemodeladmin_test.json b/treemodeladmin/tests/treemodeladmintest/fixtures/treemodeladmin_test.json new file mode 100644 index 0000000..482c589 --- /dev/null +++ b/treemodeladmin/tests/treemodeladmintest/fixtures/treemodeladmin_test.json @@ -0,0 +1,62 @@ +[ + { + "pk": 1, + "model": "treemodeladmintest.author", + "fields": { + "name": "J. R. R. Tolkien" + } + }, + { + "pk": 2, + "model": "treemodeladmintest.author", + "fields": { + "name": "Roald Dahl" + } + }, + { + "pk": 3, + "model": "treemodeladmintest.author", + "fields": { + "name": "C. S. Lewis" + } + }, + { + "pk": 4, + "model": "treemodeladmintest.author", + "fields": { + "name": "J. R. Hartley" + } + }, + { + "pk": 1, + "model": "treemodeladmintest.book", + "fields": { + "title": "The Lord of the Rings", + "author_id": 1 + } + }, + { + "pk": 2, + "model": "treemodeladmintest.book", + "fields": { + "title": "The Hobbit", + "author_id": 1 + } + }, + { + "pk": 3, + "model": "treemodeladmintest.book", + "fields": { + "title": "Charlie and the Chocolate Factory", + "author_id": 2 + } + }, + { + "pk": 4, + "model": "treemodeladmintest.book", + "fields": { + "title": "The Chronicles of Narnia", + "author_id": 3 + } + } +] diff --git a/treemodeladmin/tests/treemodeladmintest/migrations/0001_initial.py b/treemodeladmin/tests/treemodeladmintest/migrations/0001_initial.py new file mode 100644 index 0000000..ec120a7 --- /dev/null +++ b/treemodeladmin/tests/treemodeladmintest/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Author', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='Book', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('title', models.CharField(max_length=255)), + ('author', models.ForeignKey(to='treemodeladmintest.Author', on_delete=django.db.models.deletion.PROTECT)), + ], + ), + ] diff --git a/treemodeladmin/tests/treemodeladmintest/migrations/__init__.py b/treemodeladmin/tests/treemodeladmintest/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/treemodeladmin/tests/treemodeladmintest/models.py b/treemodeladmin/tests/treemodeladmintest/models.py new file mode 100644 index 0000000..71de471 --- /dev/null +++ b/treemodeladmin/tests/treemodeladmintest/models.py @@ -0,0 +1,16 @@ +from django.db import models + + +class Author(models.Model): + name = models.CharField(max_length=255) + + def __str__(self): + return self.name + + +class Book(models.Model): + author = models.ForeignKey(Author, on_delete=models.PROTECT) + title = models.CharField(max_length=255) + + def __str__(self): + return self.title diff --git a/treemodeladmin/tests/treemodeladmintest/wagtail_hooks.py b/treemodeladmin/tests/treemodeladmintest/wagtail_hooks.py new file mode 100644 index 0000000..2763419 --- /dev/null +++ b/treemodeladmin/tests/treemodeladmintest/wagtail_hooks.py @@ -0,0 +1,25 @@ +from wagtail.contrib.modeladmin.options import ( + ModelAdminGroup, + modeladmin_register, +) + +from treemodeladmin.options import TreeModelAdmin +from treemodeladmin.tests.treemodeladmintest.models import Author, Book + + +class BookModelAdmin(TreeModelAdmin): + model = Book + parent_field = 'author' + + +class AuthorModelAdmin(TreeModelAdmin): + model = Author + child_field = 'book_set' + child_model_admin = BookModelAdmin + + +@modeladmin_register +class TreeModelAdminTestGroup(ModelAdminGroup): + menu_label = 'TreeModelAdmin Test' + menu_icon = 'list-ul' + items = (AuthorModelAdmin,) diff --git a/treemodeladmin/tests/urls.py b/treemodeladmin/tests/urls.py new file mode 100644 index 0000000..8730b0a --- /dev/null +++ b/treemodeladmin/tests/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import include, url + +from wagtail.wagtailadmin import urls as wagtailadmin_urls + + +urlpatterns = [ + url(r'^admin/', include(wagtailadmin_urls)), +] diff --git a/treemodeladmin/views.py b/treemodeladmin/views.py new file mode 100644 index 0000000..ec6dfba --- /dev/null +++ b/treemodeladmin/views.py @@ -0,0 +1,123 @@ +from django.contrib.admin.utils import unquote +from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied +from django.shortcuts import get_object_or_404 +from django.utils.decorators import method_decorator +from django.utils.encoding import force_text +from django.utils.functional import cached_property + +from wagtail.contrib.modeladmin.views import IndexView + + +class TreeIndexView(IndexView): + parent_instance = None + parent_model = None + parent_model_admin = None + parent_opts = None + parent_pk = None + + def __init__(self, model_admin): + super(TreeIndexView, self).__init__(model_admin) + + if self.model_admin.has_parent(): + self.parent_model_admin = self.model_admin.parent + self.parent_model = self.parent_model_admin.model + self.parent_opts = self.parent_model._meta + + @method_decorator(login_required) + def dispatch(self, request, *args, **kwargs): + # Only continue if logged in user has list permission + if not self.permission_helper.user_can_list(request.user): + raise PermissionDenied + + self.params = dict(request.GET.items()) + if self.model_admin.has_parent(): + parent_filter_name = self.model_admin.parent_field + if parent_filter_name in self.params: + self.parent_pk = unquote(self.params[parent_filter_name]) + filter_kwargs = {self.parent_opts.pk.attname: self.parent_pk} + parent_qs = self.parent_model._default_manager.get_queryset( + ).filter(**filter_kwargs) + self.parent_instance = get_object_or_404(parent_qs) + + return super(TreeIndexView, self).dispatch(request, *args, **kwargs) + + def get_queryset(self, request=None): + qs = super(TreeIndexView, self).get_queryset(request=request) + + if self.parent_instance is not None: + parent_filter = { + self.model_admin.parent_field: self.parent_instance + } + qs = qs.filter(**parent_filter) + return qs + + def get_page_title(self): + if self.parent_instance is not None: + return str(self.parent_instance) + return super(TreeIndexView, self).get_page_title() + + def get_parent_edit_button(self): + if self.parent_instance is None: + return None + parent_button_helper_class = \ + self.parent_model_admin.get_button_helper_class() + parent_button_helper = parent_button_helper_class(self, self.request) + return parent_button_helper.edit_button( + self.parent_pk, + classnames_add=['button-secondary', 'button-small'] + ) + + def get_child_filter(self, parent_pk): + if self.has_child: + return ( + self.child_model_admin.parent_field + '=' + + str(parent_pk) + ) + + def get_children(self, obj): + if self.has_child: + return getattr(obj, self.model_admin.get_child_field()) + + @cached_property + def has_child_admin(self): + return self.model_admin.child_instance is not None + + @cached_property + def child_model_admin(self): + return self.model_admin.child_instance + + @cached_property + def has_child(self): + return self.model_admin.has_child() + + @cached_property + def child_name(self): + return self.model_admin.get_child_name() + + @cached_property + def child_name_plural(self): + return self.model_admin.get_child_name_plural() + + @cached_property + def child_url_helper(self): + if self.has_child: + return self.model_admin.child_instance.url_helper + + @property + def breadcrumbs(self): + model_admin = self.model_admin + breadcrumbs = [] + + while model_admin is not None: + breadcrumbs.append(( + model_admin.url_helper.index_url, + force_text(model_admin.model._meta.verbose_name_plural) + )) + + if model_admin.has_parent(): + model_admin = model_admin.parent + else: + model_admin = None + + return reversed(breadcrumbs) From 1decc3bec0a168af69698f8caf1342a513777d08 Mon Sep 17 00:00:00 2001 From: Will Barton Date: Mon, 14 May 2018 10:23:28 -0400 Subject: [PATCH 2/6] Wagtail 2.0 compatibility --- treemodeladmin/tests/settings.py | 87 ++++++++++++++++++++++---------- treemodeladmin/tests/urls.py | 6 ++- 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/treemodeladmin/tests/settings.py b/treemodeladmin/tests/settings.py index 8e87e64..8eec3cc 100644 --- a/treemodeladmin/tests/settings.py +++ b/treemodeladmin/tests/settings.py @@ -4,6 +4,8 @@ import django +import wagtail + DEBUG = True @@ -19,7 +21,7 @@ 'DATABASE_ENGINE', 'django.db.backends.sqlite3' ), - 'NAME': os.environ.get('DATABASE_NAME', 'wagtailsharing.sqlite'), + 'NAME': os.environ.get('DATABASE_NAME', 'treemodeladmin.sqlite'), 'USER': os.environ.get('DATABASE_USER', None), 'PASSWORD': os.environ.get('DATABASE_PASS', None), 'HOST': os.environ.get('DATABASE_HOST', None), @@ -30,17 +32,68 @@ }, } +if wagtail.VERSION >= (2, 0): + WAGTAIL_APPS = ( + 'wagtail.contrib.forms', + 'wagtail.contrib.modeladmin', + 'wagtail.contrib.settings', + 'wagtail.tests.testapp', + 'wagtail.admin', + 'wagtail.core', + 'wagtail.documents', + 'wagtail.images', + 'wagtail.sites', + 'wagtail.users', + ) + + WAGTAIL_MIDDLEWARE = ( + 'wagtail.core.middleware.SiteMiddleware', + ) + + WAGTAILADMIN_RICH_TEXT_EDITORS = { + 'default': { + 'WIDGET': 'wagtail.admin.rich_text.DraftailRichTextArea' + }, + 'custom': { + 'WIDGET': 'wagtail.tests.testapp.rich_text.CustomRichTextArea' + }, + } +else: + WAGTAIL_APPS = ( + 'wagtail.contrib.modeladmin', + 'wagtail.contrib.settings', + 'wagtail.tests.testapp', + 'wagtail.wagtailadmin', + 'wagtail.wagtailcore', + 'wagtail.wagtaildocs', + 'wagtail.wagtailforms', + 'wagtail.wagtailimages', + 'wagtail.wagtailsites', + 'wagtail.wagtailusers', + ) + + WAGTAIL_MIDDLEWARE = ( + 'wagtail.wagtailcore.middleware.SiteMiddleware', + ) + + WAGTAILADMIN_RICH_TEXT_EDITORS = { + 'default': { + 'WIDGET': 'wagtail.wagtailadmin.rich_text.HalloRichTextArea', + }, + 'custom': { + 'WIDGET': 'wagtail.tests.testapp.rich_text.CustomRichTextArea' + }, + } + if django.VERSION >= (1, 10): MIDDLEWARE = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'wagtail.wagtailcore.middleware.SiteMiddleware', - ) + ) + WAGTAIL_MIDDLEWARE else: MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', @@ -50,8 +103,7 @@ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'wagtail.wagtailcore.middleware.SiteMiddleware', - ) + ) + WAGTAIL_MIDDLEWARE INSTALLED_APPS = ( 'django.contrib.admin', @@ -59,20 +111,8 @@ 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.staticfiles', - 'taggit', - - 'wagtail.contrib.modeladmin', - 'wagtail.contrib.settings', - 'wagtail.tests.testapp', - 'wagtail.wagtailadmin', - 'wagtail.wagtailcore', - 'wagtail.wagtaildocs', - 'wagtail.wagtailforms', - 'wagtail.wagtailimages', - 'wagtail.wagtailsites', - 'wagtail.wagtailusers', - +) + WAGTAIL_APPS + ( 'treemodeladmin', 'treemodeladmin.tests.treemodeladmintest', ) @@ -98,12 +138,3 @@ ] WAGTAIL_SITE_NAME = 'Test Site' - -WAGTAILADMIN_RICH_TEXT_EDITORS = { - 'default': { - 'WIDGET': 'wagtail.wagtailadmin.rich_text.HalloRichTextArea' - }, - 'custom': { - 'WIDGET': 'wagtail.tests.testapp.rich_text.CustomRichTextArea' - }, -} diff --git a/treemodeladmin/tests/urls.py b/treemodeladmin/tests/urls.py index 8730b0a..30ea3a4 100644 --- a/treemodeladmin/tests/urls.py +++ b/treemodeladmin/tests/urls.py @@ -1,6 +1,10 @@ from django.conf.urls import include, url -from wagtail.wagtailadmin import urls as wagtailadmin_urls + +try: + from wagtail.admin import urls as wagtailadmin_urls +except ImportError: + from wagtail.wagtailadmin import urls as wagtailadmin_urls urlpatterns = [ From 0db4c77db374b8dd789e62274722c434618434db Mon Sep 17 00:00:00 2001 From: Will Barton Date: Mon, 14 May 2018 11:31:46 -0400 Subject: [PATCH 3/6] Use Markdown for the long_description --- setup.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 0547353..ec285bd 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,13 @@ from setuptools import find_packages, setup -try: - import pypandoc - long_description = pypandoc.convert('README.md', 'rst') -except (IOError, ImportError): - long_description = open('README.md', 'r').read() + +with open('README.md') as f: + long_description = f.read() install_requires = [ 'Django>=1.8,<2.1', - 'wagtail>=1.10,<2.1', + 'wagtail>=1.13,<2.1', ] From a1043ace69a74a0dd669cc45e01a30680c3e2e97 Mon Sep 17 00:00:00 2001 From: Will Barton Date: Tue, 15 May 2018 09:22:39 -0400 Subject: [PATCH 4/6] Add coverage report to test environments --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 2d5202f..1cc5af3 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ install_command=pip install -e ".[testing]" -U {opts} {packages} commands= coverage erase coverage run --source='treemodeladmin' {envbindir}/django-admin.py test {posargs} + coverage report -m setenv= DJANGO_SETTINGS_MODULE=treemodeladmin.tests.settings From 5e6b38b373f6d526b5fe6916f6bfa590d1134437 Mon Sep 17 00:00:00 2001 From: Will Barton Date: Tue, 15 May 2018 09:22:54 -0400 Subject: [PATCH 5/6] Add links to ModelAdmin and fix grammar --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d06a9a4..def6de5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://travis-ci.org/cfpb/wagtail-treemodeladmin.svg?branch=master)](https://travis-ci.org/cfpb/wagtail-treemodeladmin) [![Coverage Status](https://coveralls.io/repos/github/cfpb/wagtail-treemodeladmin/badge.svg?branch=master)](https://coveralls.io/github/cfpb/wagtail-treemodeladmin?branch=master) -Wagtail-TreeModelAdmin is an extension for Wagtail's ModelAdmin that allows for a page explorer-like navigation of Django model relationships within the Wagtail admin. +Wagtail-TreeModelAdmin is an extension for Wagtail's [ModelAdmin](http://docs.wagtail.io/en/latest/reference/contrib/modeladmin/) that allows for a page explorer-like navigation of Django model relationships within the Wagtail admin. - [Dependencies](#dependencies) - [Installation](#installation) @@ -30,11 +30,12 @@ Wagtail-TreeModelAdmin is an extension for Wagtail's ModelAdmin that allows for pip install wagtail-treemodeladmin ``` -2. Add `treemodeladmin` as an installed app in your Django `settings.py`: +2. Add `treemodeladmin` (and `wagtail.contrib.modeladmin` if it's not already) as an installed app in your Django `settings.py`: ```python INSTALLED_APPS = ( ... + 'wagtail.contrib.modeladmin', 'treemodeladmin', ... ) @@ -90,7 +91,7 @@ class AuthorModelAdmin(TreeModelAdmin): child_model_admin = BookModelAdmin ``` -Then visit the Wagtail admin. `Library` will be in the menu, and will give you a list of authors, and each author will have a link that will take to their books. +Then visit the Wagtail admin. `Library` will be in the menu, and will give you a list of authors, and each author will have a link that will take you to their books. ## API From d136f45f8862cf823c1533d3e33d829fe7ae4da2 Mon Sep 17 00:00:00 2001 From: Will Barton Date: Tue, 15 May 2018 09:23:03 -0400 Subject: [PATCH 6/6] Remove unnecessary template tag inclusion --- .../templates/treemodeladmin/includes/breadcrumb.html | 2 +- treemodeladmin/templates/treemodeladmin/includes/header.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/treemodeladmin/templates/treemodeladmin/includes/breadcrumb.html b/treemodeladmin/templates/treemodeladmin/includes/breadcrumb.html index 9cd5d92..09d74b5 100644 --- a/treemodeladmin/templates/treemodeladmin/includes/breadcrumb.html +++ b/treemodeladmin/templates/treemodeladmin/includes/breadcrumb.html @@ -1,4 +1,4 @@ -{% load i18n treemodeladmin_tags %} +{% load i18n %}