diff --git a/README.md b/README.md
index 2e88cc7e..41c023a1 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Viewflow comes in two flavors:
## Installation
-Viewflow works with Python 3.7 or greater and Django 3.1+
+Viewflow works with Python 3.7 or greater and Django 3.2+
Viewflow:
diff --git a/setup.py b/setup.py
index 3047dc95..99ed742e 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
setuptools.setup(
name='django-viewflow',
- version='2.0.0a0',
+ version='2.0.0a2',
author_email='kmmbvnr@gmail.com',
author='Mikhail Podgurskiy',
description="Reusable library to build business applications fast",
diff --git a/tox.ini b/tox.ini
index b5a4c221..a95a5665 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py{37}-dj{31}
+envlist = py{37}-dj{32}
skipsdist = True
[testenv]
@@ -8,39 +8,43 @@ commands = {posargs:./manage.py test --exclude-tag=selenium --exclude-tag=integr
deps =
# core dependences
- dj31: Django==3.1.6
- dj32: Django==3.2a1
- django-filter==2.4.0
- djangorestframework==3.12.2
- pyyaml==5.3.1
- uritemplate==3.0.1
+ dj32: Django==3.2.9
+ dj40: Django==4.0b1
+ django-filter==21.1
+ djangorestframework==3.12.4
+ pyyaml==6.0
+ uritemplate==4.1.1
# 3d party integration
- celery==5.0.5
- django-formtools==2.2
- django-guardian==2.3.0
- django-redis==4.12.1
- django-reversion==3.0.9
- django-polymodels==1.6.0
- django-allauth==0.44.0
+ celery==5.2.0
dash==2.0.0
-
- # developement
- django-environ==0.4.5
- dj-database-url==0.5.0
- django-extensions==3.1.0
- sentry-sdk==0.19.5
- flake8==3.8.3
- ipython==7.20.0
- ipdb==0.13.4
- tblib==1.7.0
+ django-allauth==0.46.0
+ django-formtools==2.3
+ django-guardian==2.4.0
+ django-import-export==2.6.1
+ django-polymodels==1.7.0
+ django-redis==5.0.0
+ django-reversion==4.0.1
+
+ # development
ansible==2.10.6
- twine==3.3.0
- psycopg2-binary==2.8.6
+ colorlover==0.3.0
+ dj-database-url==0.5.0
django_sendmail_backend==0.1.2
- selenium==3.141.0
+ django-environ==0.8.1
+ django-extensions==3.1.5
+ flake8==4.0.1
+ hiredis==2.0.0
html5lib==1.1
+ ipdb==0.13.4
+ ipython==7.20.0
numpy==1.21.2
+ pandas==1.3.3
+ psycopg2-binary==2.8.6
+ selenium==3.141.0
+ sentry-sdk==0.19.5
+ tblib==1.7.0
+ twine==3.6.0
# packaging
pyc-wheel==1.2.4
diff --git a/viewflow/__init__.py b/viewflow/__init__.py
index e6411151..d6ed6226 100644
--- a/viewflow/__init__.py
+++ b/viewflow/__init__.py
@@ -1,4 +1,12 @@
-"""Viewflow - dev toolkit for backoffice automation."""
+"""Viewflow - The Django extension for perfectionists with yesterday’s deadlines."""
+
+# Copyright (c) 2017-2020, Mikhail Podgurskiy
+# All Rights Reserved.
+
+# This work is dual-licensed under AGPL defined in file 'LICENSE' with
+# LICENSE_EXCEPTION and the Commercial license defined in file 'COMM_LICENSE',
+# which is part of this source code package.
+
from django.conf import settings as django_settings
from .conf import settings
from .this_object import this
@@ -21,6 +29,6 @@
site_middleware = 'viewflow.middleware.SiteMiddleware'
if site_middleware not in django_settings.MIDDLEWARE:
django_settings.MIDDLEWARE += (site_middleware, )
- turbolinks_middleware = 'viewflow.middleware.TurbolinksMiddleware'
- if turbolinks_middleware not in django_settings.MIDDLEWARE:
- django_settings.MIDDLEWARE += (turbolinks_middleware, )
+ turbo_middleware = 'viewflow.middleware.HotwireTurboMiddleware'
+ if turbo_middleware not in django_settings.MIDDLEWARE:
+ django_settings.MIDDLEWARE += (turbo_middleware, )
diff --git a/viewflow/components/vf-card/index.scss b/viewflow/components/vf-card/index.scss
index d1fe2bdd..9dced82a 100644
--- a/viewflow/components/vf-card/index.scss
+++ b/viewflow/components/vf-card/index.scss
@@ -84,4 +84,6 @@
justify-content: flex-end;
padding: 24px;
padding-bottom: 12px;
+ padding-left: 8px;
+ padding-top: 32px;
}
diff --git a/viewflow/components/vf-field-checkbox/index.js b/viewflow/components/vf-field-checkbox/index.js
index fec94d8a..d86bf81c 100644
--- a/viewflow/components/vf-field-checkbox/index.js
+++ b/viewflow/components/vf-field-checkbox/index.js
@@ -1,5 +1,4 @@
-import {noShadowDOM} from 'component-register';
-import {onCleanup, createEffect} from 'solid-js';
+import {onCleanup, createEffect} from 'solid-js';
import {customElement} from 'solid-element';
import {MDCCheckbox} from '@material/checkbox';
import cc from 'classcat';
@@ -23,7 +22,9 @@ const VCheckboxField = customElement('vf-field-checkbox', defaultProps, (props,
let control;
let checkbox;
- noShadowDOM(element);
+ Object.defineProperty(element, 'renderRoot', {
+ value: element,
+ });
createEffect(() => {
checkbox = new MDCCheckbox(control);
diff --git a/viewflow/components/vf-field-input/index.js b/viewflow/components/vf-field-input/index.js
index 58f07cf9..e0a4d833 100644
--- a/viewflow/components/vf-field-input/index.js
+++ b/viewflow/components/vf-field-input/index.js
@@ -2,7 +2,6 @@
import {customElement} from 'solid-element';
import {MDCTextField} from '@material/textfield';
-import {noShadowDOM} from 'component-register';
import {onCleanup, createEffect} from 'solid-js';
import cc from 'classcat';
@@ -48,6 +47,13 @@ export const Input = (props) => {
});
});
+ createEffect(() => {
+ props.value;
+ if (textfield) {
+ textfield.layout();
+ }
+ });
+
onCleanup(() => {
textfield.destroy();
});
@@ -90,6 +96,7 @@ export const Input = (props) => {
required={ !!props.required }
step = { props.step }
type={ props.type }
+ tabindex={ props.tabIndex }
value={ props.value }
aria-labelledby={ props.id + '_label' }
oninput={props.onInput}
@@ -119,7 +126,9 @@ export const Input = (props) => {
};
const VInputField = customElement('vf-field-input', defaultProps, (props, {element}) => {
- noShadowDOM(element);
+ Object.defineProperty(element, 'renderRoot', {
+ value: element,
+ });
return (
diff --git a/viewflow/components/vf-field-password/index.js b/viewflow/components/vf-field-password/index.js
index dd3da1e1..dcf5e491 100644
--- a/viewflow/components/vf-field-password/index.js
+++ b/viewflow/components/vf-field-password/index.js
@@ -2,7 +2,6 @@
import {customElement} from 'solid-element';
import {createState} from 'solid-js';
-import {noShadowDOM} from 'component-register';
import {Input, HelpText} from '../vf-field-input';
import './index.scss';
@@ -28,18 +27,22 @@ const VPasswordField = customElement('vf-field-password', defaultProps, (props,
'visible': props.type !== 'password',
});
- noShadowDOM(element);
+ Object.defineProperty(element, 'renderRoot', {
+ value: element,
+ });
const onBtnClick = (event) => {
event.preventDefault();
setState({'visible': !state.visible});
};
+ const inputType = () => state.visible ? 'text': 'password';
+
return (
state.visible ? 'text': 'password'}
+ type={inputType()}
trailingButton={() => state.visible ? 'visibility' : 'visibility_off'}
onTrailingButtonClick={onBtnClick}/>
{ props.helpText || props.error ? : '' }
diff --git a/viewflow/components/vf-field-radio-select/index.js b/viewflow/components/vf-field-radio-select/index.js
index 07324869..6e289ac7 100644
--- a/viewflow/components/vf-field-radio-select/index.js
+++ b/viewflow/components/vf-field-radio-select/index.js
@@ -1,4 +1,3 @@
-import {noShadowDOM} from 'component-register';
import {customElement} from 'solid-element';
import cc from 'classcat';
@@ -20,7 +19,9 @@ const defaultProps = {
const VRadioSelectField = customElement('vf-field-radio-select', defaultProps, (props, {element}) => {
- noShadowDOM(element);
+ Object.defineProperty(element, 'renderRoot', {
+ value: element,
+ });
const items = (props) => {
const items = [];
diff --git a/viewflow/components/vf-field/jhtml.js b/viewflow/components/vf-field/jhtml.js
index 9d2ecc7b..39490404 100644
--- a/viewflow/components/vf-field/jhtml.js
+++ b/viewflow/components/vf-field/jhtml.js
@@ -35,7 +35,7 @@ class DOMBuilder {
element.textContent = this.textContent;
}
- for (let attr in this.attrs) {
+ for (const attr in this.attrs) {
if (this.attrs.hasOwnProperty(attr)) {
let value = this.attrs[attr];
@@ -54,8 +54,8 @@ class DOMBuilder {
}
} else if (! (typeof(value) === 'string' || typeof(value) === 'number')) {
// join hash key if enabled
- let valuesList = [];
- for (let key in value) {
+ const valuesList = [];
+ for (const key in value) {
if (value.hasOwnProperty(key)) {
const keyValue = value[key];
if (keyValue) {
diff --git a/viewflow/components/vf-page/_menu.scss b/viewflow/components/vf-page/_menu.scss
index 68914f0d..bf6e01b4 100644
--- a/viewflow/components/vf-page/_menu.scss
+++ b/viewflow/components/vf-page/_menu.scss
@@ -129,6 +129,21 @@
>i.material-icons {
color: inherit;
}
+
+ margin-left: 8px;
+ margin-right: 8px;
+ padding-left: 8px;
+ padding-right: 8px;
+
+ &::before {
+ background: #3f51b5;
+ background: var(--mdc-theme-primary, #3f51b5);
+ opacity: 0.1;
+ content: " ";
+ width: 100%;
+ height: 100;
+ position: absolute;
+ }
}
}
diff --git a/viewflow/components/vf-page/index.js b/viewflow/components/vf-page/index.js
index 5dd15aca..1cb57690 100644
--- a/viewflow/components/vf-page/index.js
+++ b/viewflow/components/vf-page/index.js
@@ -1,4 +1,6 @@
/* eslint-env browser */
+import './_grid.scss';
+import './_menu.scss';
import './index.scss';
import {drawer, topAppBar} from 'material-components-web';
diff --git a/viewflow/components/vf-page/index.scss b/viewflow/components/vf-page/index.scss
index 62e21847..18f1c388 100644
--- a/viewflow/components/vf-page/index.scss
+++ b/viewflow/components/vf-page/index.scss
@@ -1,6 +1,3 @@
-@import './_grid.scss';
-@import './_menu.scss';
-
html {
height: 100%;
}
diff --git a/viewflow/fsm/base.py b/viewflow/fsm/base.py
index d0375201..76f5cbb2 100644
--- a/viewflow/fsm/base.py
+++ b/viewflow/fsm/base.py
@@ -4,16 +4,23 @@
# All Rights Reserved.
# This work is dual-licensed under AGPL defined in file 'LICENSE' with
-# LICENSE_EXCEPTION and the Commercial licence defined in file 'COMM_LICENSE',
+# LICENSE_EXCEPTION and the Commercial license defined in file 'COMM_LICENSE',
# which is part of this source code package.
from __future__ import annotations
import inspect
-from typing import Callable, List, Type
+from typing import Any, Dict, Mapping, Iterable, List, Type, Optional
from viewflow.this_object import ThisObject
from viewflow.utils import MARKER
-from .typing import Condition, StateTransitions
+from .typing import (
+ UserModel,
+ Condition,
+ StateTransitions,
+ Permission,
+ StateValue,
+ TransitionFunction,
+)
class TransitionNotAllowed(Exception):
@@ -23,41 +30,51 @@ class TransitionNotAllowed(Exception):
class Transition(object):
"""State transition definition."""
- def __init__(self, func, source, target, label=None, conditions=None, permission=None): # noqa D102
+ def __init__(
+ self,
+ func: TransitionFunction,
+ source: StateValue,
+ target: Optional[StateValue],
+ label: Optional[str] = None,
+ conditions: Optional[List[Condition]] = None,
+ permission: Optional[Permission] = None,
+ ): # noqa D102
self.func = func
self.source = source
self.target = target
self._label = label
self.permission = permission
- self.conditions: List[Condition] = conditions if conditions else []
+ self.conditions = conditions if conditions else []
- def __str__(self):
- return f'{self.label} Transition'
+ def __str__(self) -> str:
+ return f"{self.label} Transition"
@property
- def label(self):
+ def label(self) -> str:
"""Transition human-readable label."""
if self._label:
return self._label
else:
try:
- return self.func.label
+ return self.func.label # type: ignore
except AttributeError:
return self.func.__name__.title()
@property
- def slug(self):
+ def slug(self) -> str:
return self.func.__name__
- def conditions_met(self, instance) -> bool:
+ def conditions_met(self, instance: object) -> bool:
"""Check that all associated conditions is True."""
conditions = [
- condition.resolve(instance.__class__) if isinstance(condition, ThisObject) else condition
+ condition.resolve(instance.__class__)
+ if isinstance(condition, ThisObject)
+ else condition
for condition in self.conditions
]
return all(map(lambda condition: condition(instance), conditions))
- def has_perm(self, instance, user) -> bool:
+ def has_perm(self, instance: object, user: UserModel) -> bool:
"""Check the permission of the transition."""
if self.permission is None:
return True
@@ -65,7 +82,7 @@ def has_perm(self, instance, user) -> bool:
return self.permission(instance, user)
elif isinstance(self.permission, ThisObject):
permission = self.permission.resolve(instance)
- return permission(user)
+ return permission(user) # type: ignore
else:
raise ValueError(f"Unknown permission type {type(self.permission)}")
@@ -80,7 +97,13 @@ class TransitionMethod(object):
do_not_call_in_templates = True
- def __init__(self, state: State, func: Callable, descriptor: TransitionDescriptor, owner: Type):
+ def __init__(
+ self,
+ state: State,
+ func: TransitionFunction,
+ descriptor: TransitionDescriptor,
+ owner: Type[object],
+ ):
self._state = state
self._func = func
self._descriptor = descriptor
@@ -88,13 +111,13 @@ def __init__(self, state: State, func: Callable, descriptor: TransitionDescripto
self.__doc__ = func.__doc__
- def get_transitions(self) -> List[Transition]:
+ def get_transitions(self) -> Iterable[Transition]:
return self._descriptor.get_transitions()
@property
- def slug(self):
+ def slug(self) -> str:
"""Transition name."""
- return self.func.__name__
+ return self._func.__name__
class TransitionBoundMethod(object):
@@ -104,18 +127,21 @@ class TransitionBoundMethod(object):
class Wrapper(object):
"""Wrapper context object, to simplify __call__ method debug"""
- def __init__(self, parent: 'TransitionBoundMethod', kwargs):
+
+ def __init__(self, parent: "TransitionBoundMethod", kwargs: Mapping[str, Any]):
self.parent = parent
self.caller_kwargs = kwargs
- self.initial_state = None
- self.target_state = None
+ self.initial_state: StateValue
+ self.target_state: StateValue
- def __enter__(self):
+ def __enter__(self) -> None:
self.initial_state = self.parent._state.get(self.parent._instance)
transition = self.parent._descriptor.get_transition(self.initial_state)
if transition is None:
- raise TransitionNotAllowed(f'{self.parent.label} :: no transition from "{self.initial_state}"')
+ raise TransitionNotAllowed(
+ f'{self.parent.label} :: no transition from "{self.initial_state}"'
+ )
if not transition.conditions_met(self.parent._instance):
raise TransitionNotAllowed(
@@ -126,27 +152,40 @@ def __enter__(self):
if self.target_state:
self.parent._state.set(self.parent._instance, self.target_state)
- def __exit__(self, exc_type, exc_val, exc_tb):
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Any,
+ ) -> None:
if exc_type is not None:
self.parent._state.set(self.parent._instance, self.initial_state)
else:
self.parent._state.transition_succeed(
- self.parent._instance, self.parent,
- self.initial_state, self.target_state,
- **self.caller_kwargs
+ self.parent._instance,
+ self.parent,
+ self.initial_state,
+ self.target_state,
+ **self.caller_kwargs,
)
- def __init__(self, state, func, descriptor, instance):
- self._state: State = state
- self._func: Callable = func
- self._descriptor: TransitionDescriptor = descriptor
+ def __init__(
+ self,
+ state: State,
+ func: TransitionFunction,
+ descriptor: TransitionDescriptor,
+ instance: object,
+ ):
+ self._state = state
+ self._func = func
+ self._descriptor = descriptor
self._instance = instance
- def original(self, *args, **kwargs):
+ def original(self, *args: Any, **kwargs: Any) -> Any:
"""Call the unwrapped class method."""
return self._func(self._instance, *args, **kwargs)
- def can_proceed(self, check_conditions=True):
+ def can_proceed(self, check_conditions: bool = True) -> bool:
"""Check is transition available."""
current_state = self._state.get(self._instance)
transition = self._descriptor.get_transition(current_state)
@@ -154,7 +193,7 @@ def can_proceed(self, check_conditions=True):
return transition.conditions_met(self._instance)
return False
- def has_perm(self, user):
+ def has_perm(self, user: UserModel) -> bool:
current_state = self._state.get(self._instance)
transition = self._descriptor.get_transition(current_state)
if transition:
@@ -162,7 +201,7 @@ def has_perm(self, user):
return False
@property
- def label(self):
+ def label(self) -> str:
"""Transition human-readable label."""
current_state = self._state.get(self._instance)
transition = self._descriptor.get_transition(current_state)
@@ -170,15 +209,15 @@ def label(self):
return transition.label
else:
try:
- return self._func.label
+ return self._func.label # type: ignore
except AttributeError:
return self._func.__name__.title()
- def __call__(self, *args, **kwargs):
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
with TransitionBoundMethod.Wrapper(self, kwargs=kwargs):
return self._func(self._instance, *args, **kwargs)
- def get_transitions(self):
+ def get_transitions(self) -> Iterable[Transition]:
return self._descriptor.get_transitions()
@@ -187,25 +226,26 @@ class TransitionDescriptor(object):
do_not_call_in_templates = True
- def __init__(self, state, func): # noqa D102
+ def __init__(self, state: StateValue, func: TransitionFunction): # noqa D102
self._state = state
self._func = func
- self._transitions = {}
+ self._transitions: Dict[StateValue, Transition] = {}
- def __get__(self, instance, owner=None):
+ def __get__(self, instance: object, owner: Optional[Type[object]] = None) -> TransitionMethod | TransitionBoundMethod:
if instance:
return TransitionBoundMethod(self._state, self._func, self, instance)
else:
+ assert owner is not None # make mypy happy
return TransitionMethod(self._state, self._func, self, owner)
- def add_transition(self, transition):
+ def add_transition(self, transition: Transition) -> None:
self._transitions[transition.source] = transition
- def get_transitions(self) -> List[Transition]:
+ def get_transitions(self) -> Iterable[Transition]:
"""List of all transitions."""
return self._transitions.values()
- def get_transition(self, source_state):
+ def get_transition(self, source_state: StateValue) -> Optional[Transition]:
"""Get a transition of a source_state.
Returns None if there is no outgoing transitions.
@@ -219,17 +259,25 @@ def get_transition(self, source_state):
class SuperTransitionDescriptor(object):
do_not_call_in_templates = True
- def __init__(self, state, func): # noqa D102
+ def __init__(self, state: State, func: TransitionFunction): # noqa D102
self._state = state
self._func = func
- def __get__(self, instance, owner=None):
+ def __get__(self, instance: object, owner: Optional[Type[object]] = None) -> TransitionBoundMethod | TransitionMethod:
if instance:
- return TransitionBoundMethod(self._state, self._func, self.get_descriptor(instance.__class__), instance)
+ return TransitionBoundMethod(
+ self._state,
+ self._func,
+ self.get_descriptor(instance.__class__),
+ instance,
+ )
else:
- return TransitionMethod(self._state, self._func, self.get_descriptor(owner), owner)
+ assert owner is not None # make mypy happy
+ return TransitionMethod(
+ self._state, self._func, self.get_descriptor(owner), owner
+ )
- def get_descriptor(self, owner) -> TransitionDescriptor:
+ def get_descriptor(self, owner: Type[object]) -> TransitionDescriptor:
"""Lookup for the transition descriptor in the base classes."""
for cls in owner.__mro__[1:]:
if hasattr(cls, self._func.__name__):
@@ -237,7 +285,7 @@ def get_descriptor(self, owner) -> TransitionDescriptor:
if isinstance(super_method, TransitionMethod):
break
else:
- raise ValueError('Base transition not found')
+ raise ValueError("Base transition not found")
return super_method._descriptor
@@ -250,60 +298,58 @@ class StateDescriptor(object):
Review.state.get_transitions()
"""
- def __init__(self, state: 'State', owner: type):
+ def __init__(self, state: "State", owner: type):
self._state = state
self._owner = owner
- def __getattr__(self, attr):
+ def __getattr__(self, attr: str) -> Any:
return getattr(self._state, attr)
def get_transitions(self) -> StateTransitions:
- propname = '__fsm_{}_transitions'.format(self._state.propname)
- transitions = self._owner.__dict__.get(propname, None)
+ propname = "__fsm_{}_transitions".format(self._state.propname)
+ transitions: StateTransitions = self._owner.__dict__.get(propname, None)
if transitions is None:
transitions = {}
methods = inspect.getmembers(
- self._owner,
- lambda attr: isinstance(attr, TransitionMethod)
+ self._owner, lambda attr: isinstance(attr, TransitionMethod)
)
- transitions = {
- method: method.get_transitions()
- for _, method in methods
- }
+ transitions = {method: method.get_transitions() for _, method in methods}
setattr(self._owner, propname, transitions)
return transitions
- def get_outgoing_transitions(self, state) -> List[Transition]:
+ def get_outgoing_transitions(self, state: State) -> List[Transition]:
return [
transition
for transitions in self.get_transitions().values()
for transition in transitions
- if transition.source == state or (transition.source == State.ANY and transition.target != state)
+ if transition.source == state
+ or (transition.source == State.ANY and transition.target != state)
]
class State(object):
"""State slot field."""
- ANY = MARKER('ANY')
+ ANY = MARKER("ANY")
- def __init__(self, states, default=None):
+ def __init__(self, states: Any, default: StateValue = None):
self._default = default
self._setter = None
self._getter = None
self._on_success = None
- def __get__(self, instance, owner=None):
+ def __get__(self, instance: object, owner: Optional[Type[object]] = None) -> Any:
if instance:
return self.get(instance)
+ assert owner is not None # make mypy happy
return StateDescriptor(self, owner)
- def __set__(self, instance, value):
- raise AttributeError('Direct state modification is not allowed')
+ def __set__(self, instance: object, value: StateValue) -> None:
+ raise AttributeError("Direct state modification is not allowed")
- def get(self, instance):
+ def get(self, instance: object) -> Any:
"""Get the state from the underline class instance."""
if self._getter:
value = self._getter(instance)
@@ -313,28 +359,40 @@ def get(self, instance):
return value
return getattr(instance, self.propname, self._default)
- def set(self, instance, value):
+ def set(self, instance: object, value: StateValue) -> None:
"""Get the state of the underline class instance."""
if self._setter:
self._setter(instance, value)
else:
setattr(instance, self.propname, value)
- def transition_succeed(self, instance, descriptor, source, target, **kwargs):
+ def transition_succeed(
+ self,
+ instance: object,
+ descriptor: TransitionBoundMethod,
+ source: State,
+ target: State,
+ **kwargs: Any,
+ ) -> None:
if self._on_success:
self._on_success(instance, descriptor, source, target, **kwargs)
@property
- def propname(self):
+ def propname(self) -> str:
"""State storage attribute."""
- return '__fsm{}'.format(id(self))
+ return "__fsm{}".format(id(self))
def transition(
- self, source=None, target=None, label=None,
- conditions=None, permission=None,
- ):
+ self,
+ source: StateValue,
+ target: Optional[StateValue] = None,
+ label: Optional[str] = None,
+ conditions: Optional[List[Condition]] = None,
+ permission: Optional[Permission] = None,
+ ) -> Any:
"""Transition method decorator."""
- def _wrapper(func):
+
+ def _wrapper(func: Any) -> Any:
if isinstance(func, TransitionDescriptor):
descriptor = func
else:
@@ -351,34 +409,39 @@ def _wrapper(func):
target=target,
label=label,
conditions=conditions,
- permission=permission
+ permission=permission,
)
descriptor.add_transition(transition)
return descriptor
+
return _wrapper
- def super(self):
- def _wrapper(func):
+ def super(self) -> Any:
+ def _wrapper(func: Any) -> Any:
return SuperTransitionDescriptor(self, func)
+
return _wrapper
- def setter(self):
- def _wrapper(func):
+ def setter(self) -> Any:
+ def _wrapper(func: Any) -> Any:
self._setter = func
return func
+
return _wrapper
- def getter(self):
- def _wrapper(func):
+ def getter(self) -> Any:
+ def _wrapper(func: Any) -> Any:
self._getter = func
return func
+
return _wrapper
- def on_success(self):
- def _wrapper(func):
+ def on_success(self) -> Any:
+ def _wrapper(func: Any) -> Any:
self._on_success = func
return func
+
return _wrapper
class CONDITION(object):
@@ -389,8 +452,8 @@ def __init__(self, is_true: bool, unmet: str = ""):
self.unmet = unmet
@property
- def message(self):
- return self.message if self.unmet else ''
+ def message(self) -> str:
+ return self.message if self.unmet else ""
- def __bool__(self):
+ def __bool__(self) -> bool:
return self.is_true
diff --git a/viewflow/fsm/typing.py b/viewflow/fsm/typing.py
index 79c86003..119018eb 100644
--- a/viewflow/fsm/typing.py
+++ b/viewflow/fsm/typing.py
@@ -2,14 +2,18 @@
# All Rights Reserved.
# This work is dual-licensed under AGPL defined in file 'LICENSE' with
-# LICENSE_EXCEPTION and the Commercial licence defined in file 'COMM_LICENSE',
+# LICENSE_EXCEPTION and the Commercial license defined in file 'COMM_LICENSE',
# which is part of this source code package.
-from typing import Callable, Any, List, Mapping, TYPE_CHECKING
+from typing import Callable, Any, List, Union, Mapping, TYPE_CHECKING
+from viewflow.this_object import ThisObject
if TYPE_CHECKING:
from .base import TransitionMethod, Transition # NOQA
+UserModel = Any
StateValue = Any
-Condition = Callable[[Any], bool]
+Condition = Union[ThisObject, Callable[[object, object], bool]]
+Permission = Union[ThisObject, Callable[[object, Any], bool]]
StateTransitions = Mapping['TransitionMethod', List['Transition']]
+TransitionFunction = Callable[..., Any]
diff --git a/viewflow/templates/viewflow/base_page.html b/viewflow/templates/viewflow/base_page.html
index 83daa921..0284b328 100644
--- a/viewflow/templates/viewflow/base_page.html
+++ b/viewflow/templates/viewflow/base_page.html
@@ -39,8 +39,8 @@
{% block page-menu-primary %}
{% endblock page-menu-primary %}
@@ -51,13 +51,13 @@
{% block page-menu-user-actions %}
{% url 'profile' as profile_url %}
{% if profile_url %}
-
{% endif %}
{% url 'logout' as logout_url %}
{% if logout_url %}
-
{% endif %}
diff --git a/viewflow/templates/viewflow/includes/app_menu.html b/viewflow/templates/viewflow/includes/app_menu.html
index 7284baaa..bcc7f584 100644
--- a/viewflow/templates/viewflow/includes/app_menu.html
+++ b/viewflow/templates/viewflow/includes/app_menu.html
@@ -4,7 +4,7 @@
{% block viewset_links %}{% for viewset in app.menu_items %}
{% reverse viewset 'index' as index_url %}{% if index_url %}
-
{% endif %}
diff --git a/viewflow/templates/viewflow/includes/site_menu.html b/viewflow/templates/viewflow/includes/site_menu.html
index 212c7525..3ac802a9 100644
--- a/viewflow/templates/viewflow/includes/site_menu.html
+++ b/viewflow/templates/viewflow/includes/site_menu.html
@@ -9,7 +9,7 @@