Skip to content

Commit

Permalink
Merge branch 'v0.10.0' into text-area-rows
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmiller committed Mar 19, 2018
2 parents 2623c95 + 2325f4e commit 23860be
Show file tree
Hide file tree
Showing 20 changed files with 206 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -14,3 +14,4 @@ coverage-js
*.bak
node_modules
*.swp
doc/site
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -77,6 +77,8 @@ in `opal.core.views.
* Jinja2: 2.9.6 -> 2.10
* Ffs: 0.0.8.1 -> 0.0.8.2
* Requests: 2.7.0 -> 2.18.4
* django-celery: 3.1.17 -> 3.2.2
* celery: 3.1.19 -> 3.1.25

#### Testing options

Expand All @@ -86,6 +88,9 @@ failure.
If you are a plugin developer upgrading an existing plugin you will have to
manually add support for `--failfast` passthrough to your `runtests.py`.

If you are a plugin developer upgrading an existing plugin you will have to
manually add support for `--failfast` passthrough to your `runtests.py`.


#### Moves scaffold to be a django management command

Expand All @@ -102,6 +107,8 @@ Adds a setting `OPAL_FAVICON_PATH` to specify the application Favicon to use.

Adds the `rows` option to the textarea template tag which just fills in the html textarea `rows` attribute. Text areas are defaulted to 5 rows (the same as before).

Configures the setting `CSRF_FAILURE_VIEW` to use the bundled `opal.views.csrf_failure` view.


### 0.9.1 (Minor Release)

Expand Down
15 changes: 15 additions & 0 deletions Makefile
@@ -0,0 +1,15 @@
SHELL := /bin/bash

help:
@echo "usage:"
@echo " make docs -- setup and serve docs locally"
@echo " make release -- build and release to PyPI"

docs:
pip install -r doc/requirements.txt
mkdocs serve

release:
rm -rf dist/*
python setup.py register bdist_wheel sdist
twine upload dist/*
2 changes: 0 additions & 2 deletions doc/docs/guides/list_views.md
Expand Up @@ -98,8 +98,6 @@ this order.
As a [discoverable](discoverable.md) feature, the slug for each list is determined by
either setting the `slug` property, or returning a string from the `get_slug` classmethod.

### Templates

### Tagged Patient Lists

A common model for working with lists is to use lists based on the tags assigned to an episode.
Expand Down
25 changes: 25 additions & 0 deletions doc/docs/guides/releasing.md
@@ -0,0 +1,25 @@
# Making a Release

1. Tag the HEAD of the release branch and push to github: `git tag <version> && git push --tags`.
Opal branch names are in the format `vX.Y.Z` the tag for a release should be the `X.Y.Z` component
of the branch name.

1. Check version is correct in `_version.py`.

1. Make sure you have `twine` installed and then: `make release`

1. Merge the branch you are releasing into `master`

1. Update the Github release page and make sure that it has the relevant Changelog contents.

1. If the branch for the next version does not already exist, create that branch. For instance, if you
have released `x.y.z` then create `x.y.(z+1)`. If you are creating a new version branch, ensure you have
also changed the version number in the documentation, `opal._version.__version__`, and the branches that
any badges in the project README are pointing at.

1. Change the github default branch to be the new in development version

1. Update the Opal website: Change the current release and development version on the homepage, and run
`rake docs` to regenerate the documentation site with a new latest stable release version.

1. Post to the Opal mailing list to announce the new release.
85 changes: 74 additions & 11 deletions doc/docs/guides/templates.md
Expand Up @@ -2,24 +2,87 @@

Opal uses a mixture of Django and Angular templates.

### Angular templates
### Angular and Django templates, living in harmony

In order to be compatible with Django templating, we use the `[[ ... ]]` notation for
Angular interpolation, which allows us to mix Django template interpolation in templates
designed to be rendered by Angular on the client side.
Natively, both Angular and Django use the {{ ... }} syntax for interpolation in
templates. In order for Opal to be compatible with both Django **and** Angular
interpolation, we use the `[[ ... ]]` notation for Angular interpolation and
the {{ ... }} syntax for Django interpolation, which allows us to include
Django template interpolation in templates designed to be rendered by Angular on
the client side.

<!-- app_details_snippet.html -->
{{ OPAL_BRAND_NAME }} [[ Opal_VERSION ]]
```html
<!-- app_details_snippet.html -->

<!-- Django interpolation uses {{ ... }} -->
{{ OPAL_BRAND_NAME }}

<!-- Angular interpolation uses [[ ... ]] -->
[[ OPAL_BRAND_NAME ]]
```

### Generic Template URL

On many occasions we simply want to fetch a template from the server in our Angular code
On many occasions we want to fetch a template from the server in our Angular code
without any further processing. Opal provides a default catchall HTML template url which
will render .html files direct from disk.

# opal.urls
```python
# opal.urls

url(r'templates/(?P<template_name>[a-z_/]+.html)', views.RawTemplateView.as_view())
```

So if our template is at `./myapp/templates/foo/bar.html`, then the url
`/templates/foo/bar.html` will return it.

### Overriding Opal templates

Opal's built-in templates can help you be productive rapidly, and prototype an
application quickly. However, as your application develops you are likely to
need to override Opal's templates.

#### How to override a template

In general, if you want to override an Opal template, you will need to place a file
in your application with the same filename, and at the same relative location
as that file in Opal's source code. The Django template loader system will then
select the file from your application rather than the file in the Opal templates
directory.

For example if you want to override the default form for the Demographics
model, you create a new template:

```
yourapp/
templates/
forms/
demographic_form.html
```

#### How do I know which template I need to override?

You will need to look at the templates in the Opal source
code and work out which one is being used.

Templates related to the display or editing of **subrecords**, are named
according to the [API name](../reference/subrecords.md#subrecordget_api_name) of the subrecord
and can be found in `./templates/forms` or `./templates/records/`.

For instance, the demographics form is located in `./templates/forms/demographics_form.html` and
the display template for a demographics instance is located in
`./templates/records/demographics.html`.

Templates related to **form widgets** are found in `./templates/_helpers` and named after the
type of widget they represent. For instance, the template for the `{% input %}`
templatetag is found at `./templates/_helpers/input.html`

Once logged in, the **main layout** of the application is provided by the template at
`./templates/opal.html`. By default, this simply extends
`./templates/app_layouts/layout_base.html`.

url(r'templates/(?P<template_name>[a-z_/]+.html)', views.RawTemplateView.as_view())
The **patient detail** page is provided by the template at `./templates/patient_detail.html`. By
default this uses the page layout from `./templates/patient_detail_base.html`.

So if our template is at `./myapp/templates/foo/bar.html`, then the url `/templates/foo/bar.html`
will return it.
The detail template for an **episode** of any given episode category is set on the
[category class](../reference/episode_categories.md#episodecategorydetail_template).
1 change: 1 addition & 0 deletions doc/docs/guides/topic-guides.md
Expand Up @@ -56,6 +56,7 @@ A list of all available topic guides.
|--|--|
[Contributing](CONTRIBUTING.md) | Contributing to Opal
[Development environment](development_environment.md) | Setting up the Opal development environment
[Making a Release](releasing.md) | Releasing a version of Opal

### Other Guides
|||
Expand Down
11 changes: 11 additions & 0 deletions doc/docs/reference/subrecords.md
Expand Up @@ -95,6 +95,17 @@ This is used when creating data extracts to exclude PID from e.g. CSV downloads.

### Methods

#### Subrecord.get_api_name()

Classmethod that returns a snake case version of the API name for this subrecord.
This will be used in the URL for the subrecord API, and as a property name in Javascript
representations of the data.

```python
>>> Demographics.get_api_name()
"demographics"
```

#### Subrecord.get_display_template()

Classmethod to locate the display template for our record. By default this
Expand Down
12 changes: 12 additions & 0 deletions doc/docs/reference/upgrading.md
Expand Up @@ -22,6 +22,8 @@ you have specified them in for instance, a requirements.txt.
ffs==0.0.8.2
opal==0.10.0
requests==2.18.4
django-celery==3.2.2
celery==3.1.25


After re-installing (via for instance `pip install -r requirements.txt`) you will
Expand Down Expand Up @@ -91,6 +93,16 @@ Django now ships with `django.contrib.auth.mixins.LoginRequiredMixin`. According
removed `opal.core.views.LoginRequiredMixin`. A direct switch to the Django class should
work seamlessly without any functional differences.

##### CSRF_FAILURE_VIEW
We now ship the `opal.views.csrf_failure` view which can be enabled by adding
`CSRF_FAILURE_VIEW = 'opal.views.csrf_failure'` in your settings.py. This will
redirect a user to their intended destination on a CSRF failure. This mitigates
an edge case where an unauthenticated user opens two pages at the same time.
Both pages will get redirected to the login form and whichever page the user
logs into second will throw a CSRF failure because Django invalidates CSRF
tokens on login.


### 0.9.0 -> 0.9.1

#### Upgrading Opal
Expand Down
1 change: 1 addition & 0 deletions doc/mkdocs.yml
Expand Up @@ -48,6 +48,7 @@ pages:
# Working on Opal itself
- Contributing: guides/CONTRIBUTING.md
- Development environment: guides/development_environment.md
- Releasing: guides/releasing.md

# Other guides
- Deployment: guides/deployment.md
Expand Down
3 changes: 3 additions & 0 deletions doc/requirements.txt
@@ -0,0 +1,3 @@
mkdocs-bootstrap==0.1.1
mkdocs-bootswatch==0.4.0
mkdocs==0.15.3
2 changes: 0 additions & 2 deletions doc/rtd-requirements.txt

This file was deleted.

1 change: 1 addition & 0 deletions opal/scaffolding/scaffold/app/settings.py.jinja2
Expand Up @@ -192,6 +192,7 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True
# CSRF
# https://docs.djangoproject.com/en/1.10/ref/settings/#csrf-cookie-name
CSRF_COOKIE_NAME = 'XSRF-TOKEN'
CSRF_FAILURE_VIEW = 'opal.views.csrf_failure'


# ========== THIRD PARTY ==========
Expand Down
4 changes: 2 additions & 2 deletions opal/scaffolding/scaffold/requirements.txt.jinja2
Expand Up @@ -14,6 +14,6 @@ requests==2.18.4
djangorestframework==3.4.7
django-compressor==1.5
python-dateutil==2.4.2
django-celery==3.1.17
celery==3.1.19
django-celery==3.2.2
celery==3.1.25
opal=={{version}}
10 changes: 10 additions & 0 deletions opal/static/css/opal.css
Expand Up @@ -1125,3 +1125,13 @@ hr.bold{
.text-avatar{
height: 20px;
}

.form-group .ui-select-container.ui-select-multiple {
height: auto;
}

.select2-choices {
min-height: 150px;
max-height: 150px;
overflow-y: auto;
}
2 changes: 1 addition & 1 deletion opal/templates/forms/demographics_form.html
@@ -1,6 +1,6 @@
{% load forms %}
{% input "autofocus" field="Demographics.hospital_number" %}
{% input field="Demographics.nhs_number" label="NHS Number" %}
{% input field="Demographics.nhs_number" %}
{% input field="Demographics.first_name" %}
{% input field="Demographics.surname" %}
{% date_of_birth_field %}
Expand Down
29 changes: 29 additions & 0 deletions opal/tests/test_views.py
Expand Up @@ -5,13 +5,17 @@

from django import http
from django.core.urlresolvers import reverse
from django.http import QueryDict
from django.test import TestCase
from django.test.client import RequestFactory
from mock import patch, MagicMock

from opal import models, views
from opal.core import detail, patient_lists
from opal.core.subrecords import subrecords
from opal.core.episodes import InpatientEpisode
from opal.core.test import OpalTestCase
from opal.views import csrf_failure

# this is used just to import the class for EpisodeListApiTestCase
from opal.tests.test_patient_lists import TaggingTestPatientList, TestTabbedPatientListGroup # flake8: noqa
Expand Down Expand Up @@ -438,3 +442,28 @@ def test_copy_to_category(self):
self.assertEqual(
new_episode.episodename_set.filter(name="episode name").count(), 0
)


class CSRFFailureTestCase(TestCase):
def test_get(self):
factory = RequestFactory()
request = factory.get('/')
response = csrf_failure(request, '')
self.assertEqual(response.status_code, 403)

def test_post_with_next_arg(self):
factory = RequestFactory()
request = factory.post('/test', data={'test': 'data'})
request.GET = QueryDict('next=/foo')
response = csrf_failure(request, '')

self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/foo')

def test_post_without_next_arg(self):
factory = RequestFactory()
request = factory.post('/test', data={'test': 'data'})
response = csrf_failure(request, '')

self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/')
10 changes: 9 additions & 1 deletion opal/views.py
Expand Up @@ -4,7 +4,7 @@
from django.core.urlresolvers import reverse
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import login
from django.http import HttpResponseNotFound
from django.http import HttpResponseForbidden, HttpResponseNotFound
from django.shortcuts import get_object_or_404, redirect
from django.template.loader import get_template
from django.template import TemplateDoesNotExist
Expand Down Expand Up @@ -295,3 +295,11 @@ def get(self, *args, **kw):
except TemplateDoesNotExist:
return HttpResponseNotFound()
return super(RawTemplateView, self).get(*args, **kw)


def csrf_failure(request, reason):
if request.POST:
next_url = request.GET.get('next', '/')
return redirect(next_url)

return HttpResponseForbidden
4 changes: 2 additions & 2 deletions setup.py
Expand Up @@ -55,8 +55,8 @@
'djangorestframework==3.4.7',
'django-compressor==1.5',
'python-dateutil==2.4.2',
'django-celery==3.1.17',
'celery==3.1.19',
'django-celery==3.2.2',
'celery==3.1.25',
'six>=1.10.0',
],
classifiers = [
Expand Down
4 changes: 2 additions & 2 deletions test-requirements.txt
Expand Up @@ -3,7 +3,7 @@ pytz
python-dateutil==2.4.2
coverage
coveralls
celery==3.1.19
django-celery==3.1.17
celery==3.1.25
django-celery==3.2.2
flake8==3.2.1
requests==2.18.4

0 comments on commit 23860be

Please sign in to comment.