Git Product home page Git Product logo

django-fsm's Introduction

Viewflow

The low-code for developers with yesterday's deadline

build coverage pypi-version py-versions

Viewflow is a low-code, reusable component library for creating comprehensive business applications with ease. Built on top of Django, Viewflow simplifies development by providing pre-built components for user management, workflows, and reporting, while still offering flexibility to customize and integrate with existing systems.

With Viewflow, you can create full-featured business applications in just a few lines of code using its reusable component library. It's shipped as a single package with batteries included, and each part of Viewflow can be used independently of the others, but they all work well together.

Viewflow comes in two flavors:

  • Viewflow Core: A lightweight, open-source library with only non-opinionated core classes that allows you to build your custom solution on top.
  • Viewflow PRO: A comprehensive package that includes reference functionality implementation and integrated with third-party Django packages. This package has a commercial-friendly license that allows private forks and modifications of Viewflow.

drawing

Features

  • Modern, responsive user interface with an SPA-style look and feel
  • Reusable workflow library for quick implementation of BPMN workflows
  • Built-in customizable CRUD for managing complex forms and data
  • Integrated reporting dashboard
  • Small and concise API

Installation

Viewflow works with Python 3.8 or greater and Django 4.0+

Viewflow:

pip install django-viewflow

Viewflow-PRO:

pip install django-viewflow-pro  --extra-index-url https://pypi.viewflow.io/<licence_id>/simple/

Add 'viewflow' and, in case you need workflow capabilities 'viewflow.workflow' to the INSTALLED_APPS settings.py

    INSTALLED_APPS = [
        ....
        'viewflow',
        'viewflow.workflow',
    ]

Quick start

Here's an example of how to create a simple pizza ordering workflow using Viewflow:

  1. Create a model to store process data

Before creating the workflow, you'll need to define a model to store the process data. Viewflow provides a Process model as the base model for your process instances. You can add your own fields to the model using jsonstore fields to avoid model inheritance and additional joins:

    from viewflow import jsonstore
    from viewflow.workflow.models import Process

    class PizzaOrder(Process):
        customer_name = jsonstore.CharField(max_length=250)
        address = jsonstore.TextField()
        toppings = jsonstore.TextField()
        tips_received = jsonstore.IntegerField(default=0)
        baking_time = jsonstore.IntegerField(default=10)

        class Meta:
            proxy = True
  1. Create a new flow definition file flows.py

Next, create a new flow definition file flows.py and define your workflow. In this example, we'll create a PizzaFlow class that inherits from flow.Flow. We'll define three steps in the workflow: start, bake, and deliver. We'll use CreateProcessView and UpdateProcessView to create and update the process data from PizzaOrder:

    from viewflow import this
    from viewflow.workflow import flow
    from viewflow.workflow.flow.views import CreateProcessView, UpdateProcessView
    from .models import PizzaOrder

    class PizzaFlow(flow.Flow):
        process_class = PizzaOrder

        start = flow.Start(
            CreateProcessView.as_view(
                fields=["customer_name", "address", "toppings"]
            )
        ).Next(this.bake)

        bake = flow.View(
            UpdateProcessView.as_view(fields=["baking_time"])
        ).Next(this.deliver)

        deliver = flow.View(
            UpdateProcessView.as_view(fields=["tips_received"])
        ).Next(this.end)

        end = flow.End()
  1. Add the flow to your URL configuration:

Finally, add the PizzaFlow to your URL configuration. You can use the Site and FlowAppViewset classes to register your workflow with the pre-built frontend.

    from django.urls import path
    from viewflow.contrib.auth import AuthViewset
    from viewflow.urls import Application, Site
    from viewflow.workflow.flow import FlowAppViewset
    from my_pizza.flows import PizzaFlow

    site = Site(
        title="Pizza Flow Demo",
        viewsets=[
            FlowAppViewset(PizzaFlow, icon="local_pizza"),
        ]
    )

    urlpatterns = [
        path("accounts/", AuthViewset().urls),
        path("", site.urls),
    ]
  1. Make and run migrations and access the workflow through the pre-built frontend.

Make and run migrations to create the necessary database tables, then start your Django server and access the workflow through the pre-built frontend. You should be able to create and track pizza orders with the workflow.

Documentation

Viewflow's documentation for the latest version is available at http://docs.viewflow.io/

Documentarian for Viewflow 1.xx series available at http://v1-docs.viewflow.io

Demo

http://demo.viewflow.io/

Cookbook

For sample applications and code snippets, check out the Viewflow PRO cookbook at

https://github.com/viewflow/cookbook

License

Viewflow is an Open Source project licensed under the terms of the AGPL license - The GNU Affero General Public License v3.0 with the Additional Permissions described in LICENSE_EXCEPTION

The AGPL license with Additional Permissions is a free software license that allows commercial use and distribution of the software. It is similar to the GNU GCC Runtime Library license, and it includes additional permissions that make it more friendly for commercial development.

You can read more about AGPL and its compatibility with commercial use at the AGPL FAQ

If you use Linux already, this package license likely won't bring anything new to your stack.

Viewflow PRO has a commercial-friendly license allowing private forks and modifications of Viewflow. You can find the commercial license terms in COMM-LICENSE.

Changelog

2.0.2 2024-04-19

  • Fix logout link
  • Change admin user autocomplete field to readonly

2.0.1 2024-04-17

  • Fix for AjaxModelSelect in m2m relations

2.0.0 2024-04-09

  • Added support for Django 5.0+
  • Updated to Material Components Web 1.4.0
  • Improved help text styles
  • Fixed default app_name configuration for Viewsets
  • List View initial filter values support
  • Enhanced localization support
  • Corrected object permission checks for delete actions

django-fsm's People

Contributors

blueyed avatar codingjoe avatar coredumperror avatar dariaknyazeva avatar decentral1se avatar drmeers avatar foarsitter avatar frague59 avatar ftobia avatar islamgulov avatar justinabrahms avatar kaharlichenko avatar kbussell avatar klen avatar kmmbvnr avatar knaperek avatar knutin avatar llybin avatar lucacorti avatar marcgibbons avatar michaelgruenewald avatar mwesterhof avatar orf avatar peterfarrell avatar pfouque avatar scotta avatar seddonym avatar ticosax avatar timgates42 avatar tirkarthi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-fsm's Issues

IndexError while using graph_transitions

Edit: I needed to include the choices kwarg to FSMIntegerField. Perhaps it would be a good idea to print an error message when this happens rather than failing with an exception?

Original:

Hello,
I have this model that simulates a QA process using django-fsm:

class QAState(enum.IntEnum):
    NEW = 1
    TECH_QA = 2
    COMPLETED = 5


class QAMixin(ConcurrentTransitionMixin, models.Model):
    qa_state = FSMIntegerField(default=QAState.NEW, protected=True)

    @transition(qa_state, source=QAState.NEW, target=QAState.TECH_QA)
    def send_for_tech_qa(self, user: User):
        pass

    @transition(qa_state, source=QAState.TECH_QA, target=QAState.NEW)
    def failed_tech_qa(self, user: User):
        pass

    @transition(qa_state, source=QAState.TECH_QA, target=QAState.COMPLETED)
    def passed_tech_qa(self, user: User):
        pass

    class Model:
        abstract = True

Whenever I try to run graph_transitions the following error occurs:

Traceback (most recent call last):
  File "/home/vagrant/.pycharm_helpers/pycharm/django_manage.py", line 41, in <module>
    run_module(manage_file, None, '__main__', True)
  File "/usr/lib/python3.4/runpy.py", line 182, in run_module
    return _run_module_code(code, init_globals, run_name, mod_spec)
  File "/usr/lib/python3.4/runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/doge/manage.py", line 11, in <module>
    execute_from_command_line(sys.argv)
  File "/home/vagrant/venv/lib/python3.4/site-packages/django/core/management/__init__.py", line 338, in execute_from_command_line
    utility.execute()
  File "/home/vagrant/venv/lib/python3.4/site-packages/django/core/management/__init__.py", line 330, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/vagrant/venv/lib/python3.4/site-packages/django/core/management/base.py", line 393, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/vagrant/venv/lib/python3.4/site-packages/raven/contrib/django/management/__init__.py", line 41, in new_execute
    return original_func(self, *args, **kwargs)
  File "/home/vagrant/venv/lib/python3.4/site-packages/django/core/management/base.py", line 444, in execute
    output = self.handle(*args, **options)
  File "/home/vagrant/venv/lib/python3.4/site-packages/django_fsm/management/commands/graph_transitions.py", line 161, in handle
    dotdata = generate_dot(fields_data)
  File "/home/vagrant/venv/lib/python3.4/site-packages/django_fsm/management/commands/graph_transitions.py", line 49, in generate_dot
    source_label = [smart_text(name[1]) for name in field.choices if name[0] == transition.source][0]
IndexError: list index out of range

This doesn't happen if there are no transitions on the model, but as far as I can tell the QAMixin is working? I've tested the transitions themselves and that all works?

State in django admin

Hi,

How can I handle an object state in the django admin?

All I can do is setting a 'choices' attribute in my field, to display a choice widget. However, it will not use the transitions methods.

How can I be sure to always use those methods?

Thanks.

get_available_FIELD_transitions for proxy models?

Hi there,

I have a few proxy models setup, but I can't figure out how to get the get_available_FIELD_transitions thing to work.

class SubStep(models.Model):

    STATE_START = 'start'
    STATE_END = 'done'

    state = FSMField(default=STATE_START)

class ApprovalStep(SubStep):
    class Meta:
        proxy = True

    @transition(source='start', target='done')
    def approve(self):
        pass

If I do transition(field=state, ...) in the proxy model, it can't seem to get a reference to the field in question. Do you have any suggestions for how I can accomplish this?

-justin

FSMIntegerField transition fails when target state value is 0

I spent some time debugging a state transition issue where everything seemed to b correct, except the state is not updated after executing the handler.

It seems the issue is with the target state value being 0. In this case the state transition is not performed. Changing the target state value to anything different than 0 fixes this.

Is this expected behavior? Looks like a bug to me.

FSMKeyField doesnt handle states with capital letters in label

I get the error

"session state instance with id SessionState: TEST_1 does not exist."

whenever adding a model that uses the FSMKeyField to reference a state that has a CAPITAL character in its label.

The KeyField works fine if it is replaced with all lowercase letters, ie "test_1" works fine.

TypeError: Error when calling the metaclass bases contribute_to_class() got an unexpected keyword argument 'virtual_only'

If I use the latest version from pip (2.0.1) with Django 1.5.5, I encounter a TypeError when I try to define a field like this:

state = FSMField(default='new')

I also tried using the FSMIntegerField and I get the same error. I tried installing 1.6 from pip exactly as above (although the imports are different) and the above works.

Let me know if there's something I'm doing wrong, or there's anything else I can provide to help troubleshoot.

Missing South introspection rules

In the move to 2.0 it looks like the previous support for South was removed: 1ed0e2c

I think all that needs happen is those introspection rules get added back to the new django_fsm/__init__.py and it should all work again.

Was the removal deliberate or would it be OK to have these back? I'm happy to make a quick pull request doing so if that's preferred.

Refactor to follow DRY

def can_proceed(bound_method, check_conditions=True):
    """
    Returns True if model in state allows to call bound_method

    Set ``check_conditions`` argument to ``False`` to skip checking
    conditions.
    """
    if not hasattr(bound_method, '_django_fsm'):
        raise TypeError('%s method is not transition' % bound_method.im_func.__name__)

    meta = bound_method._django_fsm
    im_self = getattr(bound_method, 'im_self', getattr(bound_method, '__self__'))
    current_state = meta.field.get_state(im_self)

    return meta.has_transition(current_state) and (
        not check_conditions or meta.conditions_met(im_self, current_state))


def has_transition_perm(bound_method, user):
    """
    Returns True if model in state allows to call bound_method and user have rights on it
    """
    if not hasattr(bound_method, '_django_fsm'):
        raise TypeError('%s method is not transition' % bound_method.im_func.__name__)

    meta = bound_method._django_fsm
    im_self = getattr(bound_method, 'im_self', getattr(bound_method, '__self__'))
    current_state = meta.field.get_state(im_self)

    return (meta.has_transition(current_state)
            and meta.conditions_met(im_self, current_state)
            and meta.has_transition_perm(im_self, current_state, user))

translatable transition name

Wouldn't it make sense to allow something such as:

@transition(..., name=_("translatable transitionname"))

and only if name is missing, then fall back to transition.method.__name__

In django-fsm-admin this name then could be used in multilingual environments. Currently the name is derived from the method name, which does not always give satisfying results.

conditions do not work with boolean properties

the conditions parameter currently must be a list of callables, that take a model instance as argument.

suppose the model has a boolean property, called is_happy. to make is_happy a condition for some transition, i would have to write some method is_happy_func(self): return self.is_happy or put some lambda into the conditions, which doesn't look cool.

the same problem arises when the model has a lot of functions that are @propertys, e.g. because the project guidelines say that all methods that begin with is_, has_ or can_ should be properties (like in my case).

in case the current syntax does not work: one approach would be to allow strings in the conditions list that are used to "look up" properties on the model instance. that might be a bit weird at first sight but django uses such string parameters extensively e.g. in the QuerySet API, so django-fsm users should be familiar with that.

"non-explicit field transition" warning firing on inherited classes.

I have a class that inherits its state field from a base class. This means that the FSMField doesn't exist in the class itself, and so I'm getting the "Non explicid (sic) field transition support going to be removed" deprecation warning.

The decorator should take inheritance into account?

Error on graph_transitions

I'm getting this when running the graph_transitions management command.

venv/lib/python2.7/site-packages/django_fsm_log/models.py:5: RemovedInDjango19Warning: django.contrib.contenttypes.generic is deprecated and will be removed in Django 1.9. Its contents have been moved to the fields, forms, and admin submodules of django.contrib.contenttypes.
  from django.contrib.contenttypes.generic import GenericForeignKey

venv/lib/python2.7/site-packages/django_fsm_log/apps.py:14: RemovedInDjango19Warning: import_by_path() has been deprecated. Use import_string() instead.
  backend = import_by_path(settings.DJANGO_FSM_LOG_STORAGE_METHOD)

venv/lib/python2.7/site-packages/django/core/management/base.py:259: RemovedInDjango19Warning: "requires_model_validation" is deprecated in favor of "requires_system_checks".
  RemovedInDjango19Warning)

venv/lib/python2.7/site-packages/django_fsm/management/commands/graph_transitions.py:102: RemovedInDjango19Warning: django.db.models.get_model is deprecated.
  model = get_model(field_spec[0], field_spec[1])

venv/python2.7/site-packages/django/db/models/__init__.py:55: RemovedInDjango19Warning: The utilities in django.db.models.loading are deprecated in favor of the new application loading system.
  from . import loading

Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "venv/lib/python2.7/site-packages/django/core/management/__init__.py", line 338, in execute_from_command_line
    utility.execute()
  File "venv/lib/python2.7/site-packages/django/core/management/__init__.py", line 330, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "venv/lib/python2.7/site-packages/django/core/management/base.py", line 390, in run_from_argv
    self.execute(*args, **cmd_options)
  File "venv/lib/python2.7/site-packages/django/core/management/base.py", line 441, in execute
    output = self.handle(*args, **options)
  File "venv/lib/python2.7/site-packages/django_fsm/management/commands/graph_transitions.py", line 112, in handle
    dotdata = generate_dot(fields_data)
  File "venv/lib/python2.7/site-packages/django_fsm/management/commands/graph_transitions.py", line 55, in generate_dot
    subgraph.node(name, label=label, shape='doublecircle')
  File "venv/lib/python2.7/site-packages/graphviz/dot.py", line 99, in node
    attributes = self.attributes(label, attrs, _attributes)
  File "venv/lib/python2.7/site-packages/graphviz/lang.py", line 91, in attributes
    result = ['label=%s' % quote(label)]
  File "venv/lib/python2.7/site-packages/graphviz/lang.py", line 44, in quote
    if html(identifier):
TypeError: expected string or buffer

Refactor FSMMeta

  • Create standalone Transiton class and incorporate permission check method in it
  • Store Transition class instances list in FSMMeta instad of unnamed tuple

<Model>, which has either not been installed or is abstract.

I just upgraded from django-fsm 1.6 to 2.1 and now get this error when trying to run any management command:

CommandError: One or more models did not validate:
anx.dimensioncre: 'cre' has a relation with model portal.Cre, which has either not been installed or is abstract.

The only change I made was upgrading from django-fsm 1.6. The changelog says something about the release being backwards incompatible -- but doesn't document an upgrade procedure.

I'm running Django 1.6.5.

Lacking atomicity of state transitions

Really nice project, I appreciate the effort put into the reliability and foolproof design, especially the protected option. From that I sense it was the author's desire to enforce FSM constrains on state transitions, so that no unwanted changes or executions happen. I second this intent, but want to point out one serious race condition that the current implementation suffers from: atomicity of transitions.

While it is very nice that the FSMField checks source and target states, this is not enforced on DB level. Multiple workers trying to manipulate the same object at the same time can cause serious issues, like one transition executing more than once (I have tested this and can prove it).

On the other hand, I understand the lightweight design of FSMField and realize that its current policy of not touching the DB whatsoever might be useful in many cases and thus is totally justified. At the same time though, it prevents developers from using this great app in any serious applications where race conditions cannot be neglected or overlooked.

I suggest one backward-compatible feature that would optionally enable DB integrity enforcement. It could be activated by an additional atomic=True parameter passed to FSMField (or FSMFieldMixin). When activated, all the field transitions would be executed in django.db.transaction.atomic() block and the state would also be automatically changed, using an atomic ORM operation, e.g.

blog = BlogPost.objects.first()
blog.publish()

...would execute this in addition:

self._default_manager.filter(pk=self.pk, state='new').update(state='published')

The result of the update operation would be checked and an exception raised if it did not match any object. This would cause the whole transaction inside the atomic() block to rollback, negating the effect of any persistent operations.

This is just an example; more sophisticated code would of course be used in real, but it covers the idea. I'm willing to implement this if you agree.

Cheers!
Joe

Error Change state in method

@transition(field=state, source=['ready','wait'])
def command_spin(self, args={}):
    if ... :
        self.state='error'
    else:
        self.state='process'
    self.save()

Exception: File " ... /lib/python2.7/site-packages/django_fsm/db/fields/fsmfield.py", line 73, in next_state
result = self.transitions['']
KeyError: '
'

Get from current state to a new state mapping

Hi,

I implemented a dirty approach to asking a Model() what transitions/states are available from it's current state, implemented in https://github.com/bradwhittington/django-fsm / bradwhittington/django-fsm@ea5d7d14f5abed5d040d9b2184bda80f6b47d1f8 (minor fixes in bradwhittington/django-fsm@262c171f777588c2c0398dbb2377a92f159c35f8

Would like to hear your thoughts on better approaches, and if you feel it even belongs where I have put it.

An example:
In [1]: from task import models

In [2]: u=models.User.objects.get()

In [3]: m=models.Task.objects.get()

In [4]: m.get_available_status_transitions()
Out[4]:
[('clarification',
  <bound method Task.get_clarification of <Task: Task object>>),
 ('assigned', <bound method Task.reassign of <Task: Task object>>),
 ('in-progress', <bound method Task.start_work of <Task: Task object>>),
 ('unassigned', <bound method Task.unassign of <Task: Task object>>)]

In [5]: m.unassign()

In [6]: m.get_available_status_transitions()
Out[6]:
[('assigned', <bound method Task.assign of <Task: Task object>>),
 ('clarification',
  <bound method Task.get_clarification of <Task: Task object>>)]

In [7]: m.assign(u)

In [8]: m.get_available_status_transitions()
Out[8]:
[('clarification',
  <bound method Task.get_clarification of <Task: Task object>>),
 ('assigned', <bound method Task.reassign of <Task: Task object>>),
 ('in-progress', <bound method Task.start_work of <Task: Task object>>),
 ('unassigned', <bound method Task.unassign of <Task: Task object>>)]

incoming_states shortcut

Add Model.method.incoming_states() shortcut.

Usecase

  for blog in Blog.objects.filter(Q(**Blog.publish.incoming_states())):
      blog.publish()

“Set to change state” descriptor mode

At the moment, FSM field descriptors work in either of two modes: protected, in which setting the state raises an exception, and unprotected, in which setting the state bypasses all transitions.

I was wondering whether an additional mode could be added, in which setting the state would trigger a transition, using change_state with no optional arguments. I know I can do that easily by inheriting FSMFieldMixin and setting a custom descriptor_class, but direct support by fsm would be much cleaner.

Maybe it would be sensible to make TransitionNotAllowed a subclass of ValueError.

(Great module by the way, abstracting out this common pattern comes in really handy).

FSMKeyField not working

Hi,
I've already used fsm with CharField based stats (FSMField instance)
For a new project, I would like to use fsm with a FSMKeyField, but it doesn't work for me...

  • As I record a state field in may model, record transitions... I've the following error :

AttributeError: 'FSMKeyField' object has no attribute 'transitions'

  • The code in django_fsm/db/fsmfields.py is empty

Extract from my pip freeze :
Django==1.6.1
django-fsm==1.5.1

Thanks for your work !

User permission checking

There was several tries to add parameters passing to trahsition conditions. And every time pull requesters clain the only one use case for that - user permission checking.

Doing that in conditions is basically wrong. And it makes get_all_available_transition method ugly. B/c you can't mix conditions with user param and without, or have to use **kwargs everywhere.

At the end if user have no permission TransitionNotAllowed would be raisen that makes no sence.

The right solution should allow to make model transition without user (in case of background celery task or admin console when user are not available) And checking user permission should raise PermissionDenied exception.

So, i think right solution should be follows:

  1. Add permission arg to transition method that accepts django permission string name or callable that accepts user
  2. Provide additional method for perm check on transition, Ex
      @transition(field=status, source='NEW', target='PENDING',
                  permission='myapp.can_start_entry')
     def start(self):
          pass

Usage

     def start(request, entry_pk):
         entry = get_object_or_404(Entry, pk=entry_pk)
         if not enty.start.has_perm(request.user): 
               raise PermissionDenied
         if request.POST:
               entry.start()
               entry.save()
               return redirect('/')
         return render(request, {'entry': entry})

Ugly text formatting on PyPI

You use a README.md file, which is good. PyPI however expects text in RST format. I also do this, but in my projects setup.py I added this line

def read(fname):
    readme_file = os.path.join(os.path.dirname(__file__), fname)
    return os.popen('[ -x "$(which pandoc 2>/dev/null)" ] && pandoc -t rst {0} || cat {0}'.format(readme_file)).read()

setup(
    ...
    long_description=read('README.md'),
    ...
)

Of course you have to install pandoc, but thats worth the trouble.

Have a look here: https://pypi.python.org/pypi/django-angular , it is generated by pandoc.

Re-add save parameter to @transition decorator as a feature

I looked through the history and it looks like this used to be a feature. Why was it removed? I would like to use something like this for my own project.

@transition(... save=True)

Thanks for providing a nice library for me to use! :)

readable status value (i18n)

Hi,

First, thank you for this nice piece of software.

One thing is bugging me though: how can I set a readable, translated attribute for the state values?

If my object is in the 'new' state, I have to display 'Nouveau' in my frontend. Is there an easy way to do this? If no, what would be the easiest way to add it?

Thanks.

object has no attribute 'get_available_user_state_transitions'

Hello,

I'm trying to use Django FSM and Django FSM Admin but I get the error:
'Employee' object has no attribute 'get_available_user_state_transitions'
In template C:\Python27\lib\site-packages\fsm_admin\templates\fsm_admin\change_form.html, error at line 4: {% fsm_submit_row %}

The last traceback is:
transitions = getattr(obj, transitions_func)(user) if obj else []
with transitions_func = u'get_available_user_state_transitions'
(in Python27\lib\site-packages\fsm_admin\mixins.py in _fsm_get_transitions)

I don't understand...

in models.py:
class Employee(models.Model):
step = FSMField(default=STEP_1, choices=CHOICES, protected=True)
...
@transition(field=step, source=STEP_1, target=STEP_2)
def step1to2(self):
pass

in admin.py:
@admin.register(Employee)
class EmployeeAdmin(FSMTransitionMixin, reversion.VersionAdmin, admin.ModelAdmin):
...

in settings.py:
INSTALLED_APPS = (
'suit',
'django.contrib.admin',
'django_fsm',
'fsm_admin',
...

What is transition_type='manual'?

In the README.md file there is an example using transition_type='manual', but I can't find any information telling what this means.
Any explanation?

shortcut to specify transitions from all states except the target

Currently the * shortcut can be used to specify that a transition can happen from any state.

It would be great if there was a simple shortcut that allowed a transition from any state except the target. For example, if I have a state called 'Publish' then it doesn't make sense to transition from 'Publish' to 'Publish'.

Possible syntax:

from django_fsm import ALL_BUT_TARGET

@transition(field=state, source=ALL_BUT_TARGET, target='published')
def publish(self):
    pass

AttributeError while running through document coverage with Sphinx

When I use Sphinx doc coverage build, I get the following error:

Traceback (most recent call last):
  File "/home/polesz/dem/venv/lib/python2.7/site-packages/sphinx/cmdline.py", line 245, in main
    app.build(opts.force_all, filenames)
  File "/home/polesz/dem/venv/lib/python2.7/site-packages/sphinx/application.py", line 264, in build
    self.builder.build_update()
  File "/home/polesz/dem/venv/lib/python2.7/site-packages/sphinx/builders/__init__.py", line 240, in build_update
    self.build(['__all__'], to_build)
  File "/home/polesz/dem/venv/lib/python2.7/site-packages/sphinx/builders/__init__.py", line 316, in build
    self.write(docnames, list(updated_docnames), method)
  File "/home/polesz/dem/venv/lib/python2.7/site-packages/sphinx/ext/coverage.py", line 77, in write
    self.build_py_coverage()
  File "/home/polesz/dem/venv/lib/python2.7/site-packages/sphinx/ext/coverage.py", line 190, in build_py_coverage
    attr = getattr(obj, attr_name)
  File "/home/polesz/dem/venv/lib/python2.7/site-packages/django_fsm/__init__.py", line 205, in __get__
    raise AttributeError('Can only be accessed via an instance.')
AttributeError: Can only be accessed via an instance.

After some debugging it turned out that coverage.py gets the attributes of my model class, not an actual object. Adding an

import types

at the beginning of __init__.py, and a changing the beginning of __get__() like this (note that I changed the type parameter to obj_type as type is a reserved word):

def __get__(self, instance, obj_type=None):
    if isinstance(obj_type, (type, types.ClassType)):
        return
…

fixes the problem (although seems to be a bit ugly).

Several @transition decorator signal side effect

From the changelogs it looks like having having multiple transition decorators for a same method is supported:
Correct support for several @transitions decorator with different source states and conditions on same method

However, it looks like it has a side effect on the signals (triggered as many times as number of decorators).

###############################################
########           In your models.py             #############
###############################################

from django.db import models
from django_fsm import FSMField, transition
from django_fsm.signals import post_transition

class PostStateMachine(models.Model):

    state = FSMField(default="CREATED")

    @transition(field=state, source="CREATED", target="SUBMITTED_BY_USER")
    def submit_as_user(self):
        pass

    @transition(field=state, source="CREATED", target="SUBMITTED_BY_ADMIN")
    def submit_as_admin(self):
        pass

    @transition(field=state, source="CREATED", target="SUBMITTED_BY_ANONYMOUS")
    def submit_as_anonymous(self):
        pass

    @transition(field=state, source="SUBMITTED_BY_USER", target="REVIEW_USER")
    @transition(field=state, source="SUBMITTED_BY_ADMIN", target="REVIEW_ADMIN")
    @transition(field=state, source="SUBMITTED_BY_ANONYMOUS", target="REVIEW_ANONYMOUS")
    def review(self):
        pass


def count_calls(sender, instance, name, source, target, **kwargs):
    if name == "review":
        print name, source, target   # You will see 3 print statements

post_transition.connect(count_calls, sender=PostStateMachine)

###############################################
########           In your tests                     #############
###############################################

psm = PostStateMachine.objects.create()
psm.submit_as_admin()  # No print
psm.review()  # 3 prints

protected=True does not work with django's refresh_from_db

when calling refresh_from_db on a model that has an FSMField with protected=True, i get the following trace:

Traceback (most recent call last):
  File "/vagrant/evap/staff/tests.py", line 284, in test_has_enough_questionnaires
    course.refresh_from_db()
  File "/usr/local/lib/python3.4/dist-packages/django/db/models/base.py", line 627, in refresh_from_db
    setattr(self, field.attname, getattr(db_instance, field.attname))
  File "/usr/local/lib/python3.4/dist-packages/django_fsm/__init__.py", line 210, in __set__
    raise AttributeError('Direct {0} modification is not allowed'.format(self.field.name))
AttributeError: Direct state modification is not allowed

in case you can't fix that at all with reasonably clean code, this might at least be worth a note in the docs.

Calling a transition on a FSMKeyField raises TransitionNotAllowed

If you have a model with a FSMKeyField state like this one of the README:

class DbState(models.Model):
    id = models.CharField(primary_key=True, max_length=50)
    label = models.CharField(max_length=255)

    def __unicode__(self);
        return self.label

class BlogPost(models.Model):
    state = FSMKeyField(DbState, default='new')

    @transition(field=state, source='new', target='published')
    def publish(self):
        pass

And then you call the decorated publish() method on a BlogPost instance, it will raise this exception:

"TransitionNotAllowed: Can't switch from state 'new' using method 'publish'"

Reading the code it seems that the has_transition method of the FSMMeta is looking for the current state in its transitions property, but the current state actually is an instance of DbState, and transitions are strings, so this method will always cast to False causing the validation to fail and raise the exception.

Ability to skip a state

I am modeling requests against a resource, where some resources require requests to be approved and others don't. I'd like my request creation logic (in a view) to be unaware of this, and have just a single method to send a request.

class Request(models.Model):
    resource = models.ForiegnKey(Resource)
    # intentionally no default -- must be request()ed to create
    state = FSMField()

    @transition(field=state, source=None, target=REQUESTED)
    def request(self):
        if self.resource.approval_required:
            send_approval_request()
        else:
            # this obviously doesn't work -- state will get overwritten to
            # REQUESTED once we return.
            self.state = APPROVED

I can work around this by making request not actually a transition, but I have to duplicate the checking logic that a transition would have automatically:

    def request(self):
        assert self.state == None, "Can only send a request once. Why am I checking this instead of django-fsm?"
        if self.resource.approval_required:
            send_approval_request()
            self.state = REQUESTED
        else:
            self.state = APPROVED

It would be nice for a transition method to be able to decide the next state to choose based on something external to the state machine, like a resource's configuration.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.