Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/create package cms 2350 #1

Merged
merged 32 commits into from Nov 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
211e901
initial commit
cleder Nov 9, 2018
3c78c6c
fixes
cleder Nov 9, 2018
ef12c62
documentation
cleder Nov 9, 2018
27d6893
add tests
cleder Nov 12, 2018
7e4d541
add circle config
cleder Nov 12, 2018
d3ed7a7
fix requirements
cleder Nov 12, 2018
e4d5cf6
add django 2.1
cleder Nov 12, 2018
e9b12e7
add flake8 requirements
cleder Nov 12, 2018
22f36ff
fix includes for requirements
cleder Nov 12, 2018
80a7db1
install graphviz
cleder Nov 12, 2018
0962890
sudo install
cleder Nov 12, 2018
3602f0e
use url not path
cleder Nov 12, 2018
686830f
test django 1.11 with python 2.7
cleder Nov 12, 2018
e0d2fad
flake8
cleder Nov 12, 2018
1ba7dd3
add mock for python 2.7
cleder Nov 12, 2018
b830afc
lint config yaml
cleder Nov 12, 2018
c92797a
activate coverage
cleder Nov 12, 2018
aa41ec1
update docs
cleder Nov 12, 2018
f30e2cc
add requirements for rtfd
cleder Nov 12, 2018
4c0e2a6
move index.rst one level up
cleder Nov 12, 2018
7db3e92
move index.rst one level up
cleder Nov 12, 2018
4a81673
move index.rst back into source
cleder Nov 12, 2018
2fc0025
Rely on the state not the raw fielsd in admin
cleder Nov 12, 2018
e7b2f35
add bages
cleder Nov 12, 2018
e2af290
add quickstart walktrough
cleder Nov 12, 2018
f07a0fc
list imports
cleder Nov 12, 2018
46d9efb
document the template
cleder Nov 12, 2018
fe1e6a3
include README in docs stay DRY
cleder Nov 12, 2018
1088291
remove unused code
cleder Nov 13, 2018
68e715b
improve documentation
cleder Nov 13, 2018
62c28aa
use autoclass for correct sorting of mixins
cleder Nov 13, 2018
bce6caa
CR modifications
cleder Nov 14, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
116 changes: 116 additions & 0 deletions .circleci/config.yml
@@ -0,0 +1,116 @@
---
version: 2

apt-run: &apt-install
name: Install apt packages
command: |
sudo apt update
sudo apt install -y graphviz build-essential

jobs:
test-static:
docker:
- image: circleci/python:3.6
user: circleci
steps:
- checkout
- run:
name: Flake8 and complexity
command: |
python3 -m venv venv
source venv/bin/activate
pip install -r requirements/flake8.txt
flake8 django_transitions
bandit django_transitions
yamllint .circleci/config.yml
radon cc --min B django_transitions
radon mi --min B django_transitions
lizard -l python -w django_transitions

test-django-latest-py-3.6:
docker:
- image: circleci/python:3.6
user: circleci
steps:
- checkout
- run: *apt-install
- run:
name: Latest Django with Python 3.6
command: |
python3 -m venv venv
source venv/bin/activate
pip install -r requirements/base.txt
py.test testapp

test-django-2.0-py-3.6:
docker:
- image: circleci/python:3.6
user: circleci
steps:
- checkout
- run: *apt-install
- run:
name: Django 2.0 with Python 3.6
command: |
python3 -m venv venv
source venv/bin/activate
pip install -r requirements/django-2.0.txt
py.test testapp

test-django-2.1-py-3.6:
docker:
- image: circleci/python:3.6
user: circleci
steps:
- checkout
- run: *apt-install
- run:
name: Django 2.1 with Python 3.6
command: |
python3 -m venv venv
source venv/bin/activate
pip install -r requirements/django-2.1.txt
py.test testapp --cov=django_transitions
codecov

test-django-1.11-py-2.7:
docker:
- image: circleci/python:2.7
user: circleci
steps:
- checkout
- run: *apt-install
- run:
name: Django 1.11 with Python 2.7
command: |
virtualenv venv
source venv/bin/activate
pip install mock
pip install -r requirements/django-1.11.txt
py.test testapp

test-django-1.11-py-3.6:
docker:
- image: circleci/python:3.6
user: circleci
steps:
- checkout
- run: *apt-install
- run:
name: Django 1.11 with Python 3.6
command: |
python3 -m venv venv
source venv/bin/activate
pip install -r requirements/django-1.11.txt
py.test testapp

workflows:
version: 2
build-and-test:
jobs:
- test-django-latest-py-3.6
- test-django-2.0-py-3.6
- test-django-2.1-py-3.6
- test-django-1.11-py-3.6
- test-django-1.11-py-2.7
- test-static
7 changes: 7 additions & 0 deletions .coveragerc
@@ -0,0 +1,7 @@
# .coveragerc to control coverage.py
[run]
branch = True
include =
django_transitions/*
omit =
*/migrations/*
14 changes: 14 additions & 0 deletions .isort.cfg
@@ -0,0 +1,14 @@
[settings]
combine_star=1
force_single_line=1
import_heading_django=Django
import_heading_firstparty=Project
import_heading_future=Future
import_heading_localfolder=Local
import_heading_stdlib=Standard Library
import_heading_thirdparty=3rd-party
known_django=django
known_third_party=transitions
line_length=79
order_by_type=1
sections=FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
2 changes: 0 additions & 2 deletions README.md

This file was deleted.

37 changes: 37 additions & 0 deletions README.rst
@@ -0,0 +1,37 @@
django-transitions
====================

.. inclusion-marker-do-not-remove

A wrapper of pytransitions_ for django_

.. image:: https://circleci.com/gh/PrimarySite/django-transitions.svg?style=svg
:target: https://circleci.com/gh/PrimarySite/django-transitions
:alt: Test Status

.. image:: https://codecov.io/gh/PrimarySite/django-transitions/branch/master/graph/badge.svg
:target: https://codecov.io/gh/PrimarySite/django-transitions
:alt: Test Coverage

.. image:: https://readthedocs.org/projects/django-transitions/badge/?version=latest
:target: https://django-transitions.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status

You do not *need* django-transitions to integrate django_ and pytransitions_.
It is meant to be a lightweight wrapper (it has just over 50 logical lines of code)
and documentation how to go about using pytransitions inside a django application.

This package provides:

- Example workflow implementation.
- Base classes and mixins to
- Keep it DRY
- Keep transitions consistent
- Reduce cut and paste
- Avoid boiler plate.
- Admin mixin to add workflow actions to the django admin.
- Admin templates


.. _django: https://www.djangoproject.com/
.. _pytransitions: https://pypi.org/project/transitions/
2 changes: 2 additions & 0 deletions django_transitions/__init__.py
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
"""Django transitions."""
38 changes: 38 additions & 0 deletions django_transitions/admin.py
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
"""Mixins for the django admin."""
# Django
from django.contrib import messages
from django.http import HttpResponseRedirect


class WorkflowAdminMixin(object):
"""
A mixin to provide workflow transition actions.

It will create an admin log entry.
"""

change_form_template = 'transitions/change_form.html'

def response_change(self, request, obj):
"""Add actions for the workflow events."""
events = list(obj.get_available_events())
for event in events:
if '_' + event['transition'].name not in request.POST:
continue

before = obj.state
if getattr(obj, event['transition'].name)():
obj.save()
after = obj.state
message = ('Status changed from {0} to {1} by transition {2}'
.format(before, after, event['transition'].name))
self.message_user(request, message, messages.SUCCESS)
self.log_change(request, obj, message)
else:
message = ('Status could not be changed from '
'{0} by transition {1}'
.format(before, event['transition'].name))
self.message_user(request, message, messages.ERROR)
return HttpResponseRedirect('.')
return super(WorkflowAdminMixin, self).response_change(request, obj)
31 changes: 31 additions & 0 deletions django_transitions/models.py
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
"""An opinonated example for a workflow mixin."""

# Django
from django.db import models


class WorkflowMigrationMixin(models.Model):
"""
A mixin to provide workflow state and workflow date fields.

This is a minimal example implementation.
"""

class Meta: # noqa: D106
abstract = True

wf_state = models.CharField(
verbose_name='Workflow Status',
null=True,
blank=True,
max_length=32,
help_text='Workflow state',
)

wf_date = models.DateTimeField(
verbose_name='Workflow Date',
null=True,
blank=True,
help_text='Indicates when this workflowstate was entered.',
)
15 changes: 15 additions & 0 deletions django_transitions/templates/transitions/change_form.html
@@ -0,0 +1,15 @@
{% extends 'admin/change_form.html' %}

{% block submit_buttons_bottom %}

<!-- add the save and delete buttons -->
{{ block.super }}

<!-- Add buttons for available transitions -->
<div class="submit-row">
{% for event in original.get_available_events %}
<input type="submit" value="{{ event.label }}" name="_{{ event.transition.name }}">
{% endfor %}
</div>

{% endblock %}
@@ -0,0 +1,11 @@
{% extends 'admin/change_form.html' %}

{% block submit_buttons_bottom %}

<div class="submit-row">
{% for event in original.get_available_events %}
<input type="submit" value="{{ event.label }}" name="_{{ event.transition.name }}">
{% endfor %}
</div>

{% endblock %}
107 changes: 107 additions & 0 deletions django_transitions/workflow.py
@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
"""Mixins for transition workflows."""

# Standard Library
from functools import partial

# 3rd-party
from transitions.extensions import MachineFactory


class StatusBase(object):
"""Base class for transitions and status definitions."""

STATE_CHOICES = (
# Override this!
# https://docs.djangoproject.com/en/2.1/ref/models/fields/#choices
# to provide human readable labels for states
# (state, 'My Workflow state'),
)

TRANSITION_LABELS = {
# Override this!
# Provide human readable labels for transitions
# transition: 'Label',
}

SM_STATES = [
# Override this!
# list of available workflow states
]

SM_INITIAL_STATE = None # Initial state of the machine. Override this!

SM_TRANSITIONS = [
# Override this!
# trigger, source, destination
]

@classmethod
def get_kwargs(cls):
"""Get the kwargs to initialize the state machine."""
kwargs = {
'initial': cls.SM_INITIAL_STATE,
'states': cls.SM_STATES,
'transitions': cls.SM_TRANSITIONS,
}
return kwargs


class StateMachineMixinBase(object):
"""
Base class for state machine mixins.

Class attributes:

* ``status_class`` must provide ``TRANSITION_LABELS`` property
and the ``get_kwargs`` class method (see ``StatusBase``).
* ``machine`` is a transition machine e.g::

machine = Machine(
model=None,
finalize_event='wf_finalize',
auto_transitions=False,
**status_class.get_kwargs() # noqa: C815
)

The transition events of the machine will be added as methods to
the mixin.
"""

status_class = None # Override this!
machine = None # Override this!

def get_available_events(self):
"""
Get available workflow transition events for the current state.

Returns a dictionary:
* ``transition``: transition event.
* ``label``: human readable label for the event
"""
for trigger in self.machine.get_triggers(self.state):
event = self.machine.events[trigger]
yield {
'transition': event,
'label': self.status_class.TRANSITION_LABELS[event.name],
}

def get_wf_graph(self):
"""Get the graph for this machine."""
diagram_cls = MachineFactory.get_predefined(graph=True)
machine = diagram_cls(
model=self,
auto_transitions=False,
title=type(self).__name__,
**self.status_class.get_kwargs() # noqa: C815
)
return machine.get_graph()

def __getattribute__(self, item):
"""Propagate events to the workflow state machine."""
try:
return super(StateMachineMixinBase, self).__getattribute__(item)
except AttributeError:
if item in self.machine.events:
return partial(self.machine.events[item].trigger, self)
raise