From cbd0dcb5d4553ab60d848400007e822483780efc Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 25 Jan 2021 17:45:43 +0100 Subject: [PATCH 1/7] Add GitHub Actions test workflow. --- .coveragerc | 6 +++ .github/workflows/test.yml | 51 ++++++++++++++++++ .gitignore | 3 ++ README.rst | 11 ++++ flatblocks/urls.py | 6 +-- runtests.py | 56 ------------------- tests/settings.py | 41 ++++++++++++++ tests/tests.py | 107 ++++++++++++++++++------------------- tests/urls.py | 9 ++-- tox.ini | 34 ++++++++++++ 10 files changed, 203 insertions(+), 121 deletions(-) create mode 100644 .coveragerc create mode 100644 .github/workflows/test.yml delete mode 100644 runtests.py create mode 100644 tests/settings.py create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..817f74c --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +source = flatblocks +branch = 1 + +[report] +omit = *tests*,*migrations*,.tox/*,setup.py,*settings.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..fa59ccd --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,51 @@ +name: Test + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 5 + matrix: + python-version: ['3.6', '3.7', '3.8', '3.9'] + django-version: ['2.2', '3.0', '3.1', 'dev'] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }} + restore-keys: | + ${{ matrix.python-version }}-v1- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade tox tox-gh-actions + + - name: Tox tests + run: | + tox -v + env: + DJANGO: ${{ matrix.django-version }} + + - name: Upload coverage + uses: codecov/codecov-action@v1 + with: + name: Python ${{ matrix.python-version }} diff --git a/.gitignore b/.gitignore index 273602d..00b9940 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ local_settings.py django_flatblocks.egg-info dist build +.tox +coverage.xml +.coverage diff --git a/README.rst b/README.rst index b94c60a..7ecb772 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,17 @@ django-flatblocks ================= +.. image:: https://jazzband.co/static/img/badge.svg + :target: https://jazzband.co/ + :alt: Jazzband + +.. image:: https://github.com/jazzband/django-flatblocks/workflows/Test/badge.svg + :target: https://github.com/jazzband/django-flatblocks/actions + :alt: GitHub Actions + +.. image:: https://codecov.io/gh/jazzband/django-flatblocks/branch/master/graph/badge.svg + :target: https://codecov.io/gh/jazzband/django-flatblocks + django-flatblocks is a simple application for handling small text-blocks on websites. Think about it like ``django.contrib.flatpages`` just not for a whole page but for only parts of it, like an information text describing what diff --git a/flatblocks/urls.py b/flatblocks/urls.py index ab39554..bf2be9f 100644 --- a/flatblocks/urls.py +++ b/flatblocks/urls.py @@ -1,9 +1,7 @@ -from django.conf.urls import url from django.contrib.admin.views.decorators import staff_member_required +from django.urls import re_path from flatblocks.views import edit urlpatterns = [ - url('^edit/(?P\d+)/$', - staff_member_required(edit), - name='flatblocks-edit'), + re_path("^edit/(?P\d+)/$", staff_member_required(edit), name="flatblocks-edit"), ] diff --git a/runtests.py b/runtests.py deleted file mode 100644 index b9fd27a..0000000 --- a/runtests.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python - -# Adapted from https://raw.githubusercontent.com/hzy/django-polarize/master/runtests.py - -import sys - -from django.conf import settings -from django.core.management import execute_from_command_line - - -if not settings.configured: - settings.configure( - DATABASES={ - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - } - }, - INSTALLED_APPS=( - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.auth', - 'django.contrib.admin', - 'django.contrib.messages', - 'flatblocks', - 'tests', - ), - ROOT_URLCONF='tests.urls', - MIDDLEWARE=[ - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - ], - TEMPLATES=[ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'debug': True, - 'context_processors': [ - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - } - }, - ], - ) - - -def runtests(): - argv = sys.argv[:1] + ['test', 'tests'] - execute_from_command_line(argv) - - -if __name__ == '__main__': - runtests() diff --git a/tests/settings.py b/tests/settings.py new file mode 100644 index 0000000..a276b39 --- /dev/null +++ b/tests/settings.py @@ -0,0 +1,41 @@ +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + } +} + +INSTALLED_APPS = ( + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.auth", + "django.contrib.admin", + "django.contrib.messages", + "flatblocks", + "tests", +) + +ROOT_URLCONF = "tests.urls" + +MIDDLEWARE = [ + "django.contrib.sessions.middleware.SessionMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", +] + +SECRET_KEY = "super-secret" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "debug": True, + "context_processors": [ + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] diff --git a/tests/tests.py b/tests/tests.py index 30759fa..04a4f25 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -8,48 +8,46 @@ class BasicTests(TestCase): - def setUp(self): - self.testblock = FlatBlock.objects.create(slug='block', - header='HEADER', - content='CONTENT' - ) - self.admin = User.objects.create_superuser('admin', 'admin@localhost', - 'adminpwd') + self.testblock = FlatBlock.objects.create( + slug="block", header="HEADER", content="CONTENT" + ) + self.admin = User.objects.create_superuser( + "admin", "admin@localhost", "adminpwd" + ) def testURLConf(self): - resp = self.client.get('/flatblocks/edit/1/', follow=True) - self.assertTemplateUsed(resp, 'admin/login.html') - self.client.login(username='admin', password='adminpwd') - resp = self.client.get('/flatblocks/edit/1/') - self.assertTemplateUsed(resp, 'flatblocks/edit.html') + resp = self.client.get("/flatblocks/edit/1/", follow=True) + self.assertTemplateUsed(resp, "admin/login.html") + self.client.login(username="admin", password="adminpwd") + resp = self.client.get("/flatblocks/edit/1/") + self.assertTemplateUsed(resp, "flatblocks/edit.html") def testSaveForceUpdate(self): - block = FlatBlock(slug='missing') + block = FlatBlock(slug="missing") with self.assertRaises(ValueError): block.save(force_update=True) def testSaveForceInsert(self): - block = FlatBlock.objects.get(slug='block') + block = FlatBlock.objects.get(slug="block") with self.assertRaises(db.IntegrityError): block.save(force_insert=True) class TagTests(TestCase): def setUp(self): - self.testblock = FlatBlock.objects.create(slug='block', - header='HEADER', - content='CONTENT' - ) + self.testblock = FlatBlock.objects.create( + slug="block", header="HEADER", content="CONTENT" + ) def testLoadingTaglib(self): """Tests if the taglib defined in this app can be loaded""" - tpl = template.Template('{% load flatblocks %}') + tpl = template.Template("{% load flatblocks %}") tpl.render(template.Context({})) def testExistingPlain(self): tpl = template.Template('{% load flatblocks %}{% plain_flatblock "block" %}') - self.assertEqual('CONTENT', tpl.render(template.Context({})).strip()) + self.assertEqual("CONTENT", tpl.render(template.Context({})).strip()) def testExistingTemplate(self): expected = """
@@ -64,15 +62,15 @@ def testExistingTemplate(self): def testUsingMissingTemplate(self): tpl = template.Template( - '{% load flatblocks %}' - '{% flatblock "block" using="missing_template.html" %}') + "{% load flatblocks %}" + '{% flatblock "block" using="missing_template.html" %}' + ) exception = template.TemplateDoesNotExist self.assertRaises(exception, tpl.render, template.Context({})) def testBlockAsVariable(self): - tpl = template.Template( - '{% load flatblocks %}{% flatblock blockvar %}') - tpl.render(template.Context({'blockvar': 'block'})) + tpl = template.Template("{% load flatblocks %}{% flatblock blockvar %}") + tpl.render(template.Context({"blockvar": "block"})) def testContentEvaluation(self): """ @@ -80,44 +78,42 @@ def testContentEvaluation(self): the block is treated as a Django template and receives the parent template's context. """ - FlatBlock.objects.create(slug='tmpl_block', - header='HEADER', - content='{{ variable }}' - ) + FlatBlock.objects.create( + slug="tmpl_block", header="HEADER", content="{{ variable }}" + ) tpl = template.Template( - '{% load flatblocks %}' - '{% plain_flatblock "tmpl_block" evaluated=True %}') - result = tpl.render(template.Context({'variable': 'value'})) - self.assertEqual('value', result) + "{% load flatblocks %}" '{% plain_flatblock "tmpl_block" evaluated=True %}' + ) + result = tpl.render(template.Context({"variable": "value"})) + self.assertEqual("value", result) def testDisabledEvaluation(self): """ If "evaluated" is not passed, no evaluation should take place. """ - FlatBlock.objects.create(slug='tmpl_block', - header='HEADER', - content='{{ variable }}' - ) + FlatBlock.objects.create( + slug="tmpl_block", header="HEADER", content="{{ variable }}" + ) tpl = template.Template( - '{% load flatblocks %}{% plain_flatblock "tmpl_block" %}') - result = tpl.render(template.Context({'variable': 'value'})) - self.assertEqual('{{ variable }}', result) + '{% load flatblocks %}{% plain_flatblock "tmpl_block" %}' + ) + result = tpl.render(template.Context({"variable": "value"})) + self.assertEqual("{{ variable }}", result) def testHeaderEvaluation(self): """ Also the header should receive the context and get evaluated. """ - FlatBlock.objects.create(slug='tmpl_block', - header='{{ header_variable }}', - content='{{ variable }}' - ) + FlatBlock.objects.create( + slug="tmpl_block", header="{{ header_variable }}", content="{{ variable }}" + ) tpl = template.Template( - '{% load flatblocks %}{% flatblock "tmpl_block" evaluated=True %}') - result = tpl.render(template.Context({ - 'variable': 'value', - 'header_variable': 'header-value' - })) - self.assertTrue('header-value' in result) + '{% load flatblocks %}{% flatblock "tmpl_block" evaluated=True %}' + ) + result = tpl.render( + template.Context({"variable": "value", "header_variable": "header-value"}) + ) + self.assertTrue("header-value" in result) class AutoCreationTest(TestCase): @@ -142,10 +138,9 @@ def testNotAutocreatedMissingStaticBlock(self): feature is disabled""" expected = "" settings.AUTOCREATE_STATIC_BLOCKS = False - tpl = template.Template( - '{% load flatblocks %}{% flatblock "block" %}') + tpl = template.Template('{% load flatblocks %}{% flatblock "block" %}') self.assertEqual(expected, tpl.render(template.Context({})).strip()) - self.assertEqual(FlatBlock.objects.filter(slug='block').count(), 0) + self.assertEqual(FlatBlock.objects.filter(slug="block").count(), 0) def _testMissingVariableBlock(self): """ @@ -153,6 +148,6 @@ def _testMissingVariableBlock(self): string """ settings.AUTOCREATE_STATIC_BLOCKS = True - tpl = template.Template('{% load flatblocks %}{% flatblock name %}') - output = tpl.render(template.Context({'name': 'foo'})).strip() - self.assertEqual('', output) + tpl = template.Template("{% load flatblocks %}{% flatblock name %}") + output = tpl.render(template.Context({"name": "foo"})).strip() + self.assertEqual("", output) diff --git a/tests/urls.py b/tests/urls.py index e31d5b1..a609bc9 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,10 +1,9 @@ -from django.conf.urls import url, include -from django.contrib.admin.views.decorators import staff_member_required from django.contrib import admin from django.shortcuts import render +from django.urls import path, include urlpatterns = [ - url('^flatblocks/', include("flatblocks.urls")), - url('^admin/', admin.site.urls), - url('^$', render, {'template_name': 'index.html'}), + path('flatblocks/', include("flatblocks.urls")), + path('admin/', admin.site.urls), + path('', render, {'template_name': 'index.html'}), ] diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..8c5702e --- /dev/null +++ b/tox.ini @@ -0,0 +1,34 @@ +[tox] +envlist = + py{36,37,38,39}-dj{22,30,31,dev} + +[testenv] +deps = + coverage + dj22: Django>=2.2,<3.0 + dj30: Django>=3.0,<3.1 + dj31: Django>=3.1,<3.2 + djdev: https://github.com/django/django/archive/master.tar.gz +usedevelop = True +ignore_outcome = + djdev: True +commands = + coverage run {envbindir}/django-admin test -v2 + coverage report + coverage xml +setenv = + DJANGO_SETTINGS_MODULE=tests.settings + +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38 + 3.9: py39 + +[gh-actions:env] +DJANGO = + 2.2: dj22 + 3.0: dj30 + 3.1: dj31 + dev: djdev From 15a2d80fa8ec9ce8477125514fc31610a784e217 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 25 Jan 2021 17:46:03 +0100 Subject: [PATCH 2/7] Remove Travis config. --- .travis.yml | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 808330a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -language: python -python: - - "3.5" - - "3.6" - - "3.7" - - "3.8" - - "pypy" - # - "pypy3" -env: - - DJANGO="Django~=2.2.0" - - DJANGO="Django~=3.0.0" - - DJANGO="Django~=3.1.0" - -install: - - pip install $DJANGO - -script: - - python runtests.py - -matrix: - exclude: - - env: DJANGO="Django~=1.11.0" - python: "3.8" - - env: DJANGO="Django~=1.11.0" - python: "pypy3" - - env: DJANGO="Django~=2.2.0" - python: "pypy" - - env: DJANGO="Django~=3.0.0" - python: "3.5" - - env: DJANGO="Django~=3.0.0" - python: "pypy" - - env: DJANGO="Django~=3.1.0" - python: "3.5" - - env: DJANGO="Django~=3.1.0" - python: "pypy" From fbdf5a1da51b93d9e3be2675523cda3ceed3ff96 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 25 Jan 2021 17:50:06 +0100 Subject: [PATCH 3/7] Add release workflow. --- .github/workflows/release.yml | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..bb7405e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,53 @@ +name: Release + +on: + push: + tags: + - '*' + +jobs: + build: + if: github.repository == 'jazzband/django-flatblocks' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: release-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }} + restore-keys: | + release- + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U setuptools twine wheel + + - name: Build package + run: | + python setup.py --version + python setup.py sdist --format=gztar bdist_wheel + twine check dist/* + + - name: Upload packages to Jazzband + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@master + with: + user: jazzband + password: ${{ secrets.JAZZBAND_RELEASE_KEY }} + repository_url: https://jazzband.co/projects/django-flatblocks/upload From 1f5b1e7acb2f35a734a228db7a6bbbbea0a4ed9c Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 25 Jan 2021 17:51:14 +0100 Subject: [PATCH 4/7] Add custom name for test workflow. --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa59ccd..d88c710 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,7 @@ name: Test on: [push, pull_request] jobs: + name: build (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}) build: runs-on: ubuntu-latest strategy: From f839d26e41850a9059b42af010af7fd1653d9739 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 25 Jan 2021 17:52:10 +0100 Subject: [PATCH 5/7] Fix syntax. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d88c710..ea9b9fd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,8 +3,8 @@ name: Test on: [push, pull_request] jobs: - name: build (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}) build: + name: build (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}) runs-on: ubuntu-latest strategy: fail-fast: false From c3a23e051ab83abaef32a06d8cba22e7de0bcb2b Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 25 Jan 2021 18:01:30 +0100 Subject: [PATCH 6/7] Use setuptools_scm. --- flatblocks/__init__.py | 7 ++++ setup.py | 77 +++++++++++++++++++++--------------------- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/flatblocks/__init__.py b/flatblocks/__init__.py index e69de29..328f7af 100644 --- a/flatblocks/__init__.py +++ b/flatblocks/__init__.py @@ -0,0 +1,7 @@ +from pkg_resources import get_distribution, DistributionNotFound + +try: + __version__ = get_distribution("django-flatblocks").version +except DistributionNotFound: + # package is not installed + __version__ = None diff --git a/setup.py b/setup.py index 2e72336..ff38c81 100644 --- a/setup.py +++ b/setup.py @@ -1,51 +1,52 @@ import io - from setuptools import setup, find_packages setup( - name='django-flatblocks', - version='0.9.4', - description='django-flatblocks acts like django.contrib.flatpages but ' - 'for parts of a page; like an editable help box you want ' - 'show alongside the main content.', - long_description=io.open('README.rst', encoding='utf-8').read(), - keywords='django apps', - license='New BSD License', - author='Horst Gutmann, Curtis Maloney', - author_email='curtis@tinbrain.net', - url='http://github.com/funkybob/django-flatblocks/', + name="django-flatblocks", + use_scm_version={"version_scheme": "post-release"}, + setup_requires=["setuptools_scm"], + description="django-flatblocks acts like django.contrib.flatpages but " + "for parts of a page; like an editable help box you want " + "show alongside the main content.", + long_description=io.open("README.rst", encoding="utf-8").read(), + keywords="django apps", + license="New BSD License", + author="Horst Gutmann, Curtis Maloney", + author_email="curtis@tinbrain.net", + url="https://github.com/jazzband/django-flatblocks/", classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: Plugins', - 'Environment :: Web Environment', - 'Framework :: Django', - 'Framework :: Django :: 2.2', - 'Framework :: Django :: 3.0', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + "Development Status :: 4 - Beta", + "Environment :: Plugins", + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 2.2", + "Framework :: Django :: 3.0", + "Framework :: Django :: 3.1", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", ], - packages=find_packages(exclude=['tests']), + packages=find_packages(exclude=["tests"]), package_data={ - 'flatblocks': [ - 'templates/flatblocks/*.html', - 'locale/*/*/*.mo', - 'locale/*/*/*.po', + "flatblocks": [ + "templates/flatblocks/*.html", + "locale/*/*/*.mo", + "locale/*/*/*.po", ] }, zip_safe=False, - requires = [ - 'Django (>=2.2)', + requires=[ + "Django (>=2.2)", ], ) From 1ba07a2d2bfdb12714003b146cb053cf8e994b6d Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 25 Jan 2021 18:01:37 +0100 Subject: [PATCH 7/7] Update changelog. --- README.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.rst b/README.rst index 7ecb772..a5f8fc8 100644 --- a/README.rst +++ b/README.rst @@ -213,6 +213,15 @@ the `django-better-chunks`_ fork (``django.contrib.site``- and i18n-support). Releases -------- +Unreleased: + * Support for Django 2.2, 3.0 and 3.1. + * Support for Python 3.6, 3.7, 3.8 and 3.9. + * Moved CI to GitHub Actions: https://github.com/jazzband/django-flatblocks/actions + +0.9.4: + * Drop Python 3.3 support. + * Add support for Django 1.11. + 0.9.3: * Fixed Django 1.10 compatibility