Git Product home page Git Product logo

django-river's Introduction

Django River

Logo

Build Status Coverage Status Documentation Status Codacy Badge PyPI - Downloads Discord

River is an open source workflow framework for Django which supports on the fly changes instead of hard-coding states, transitions and authorization rules.

The main goal of developing this framework is to be able to modify literally everything about the workflows on the fly. This means that all the elements in a workflow like states, transitions or authorizations rules are editable at any time so that no changes requires a re-deploying of your application anymore.

Playground: There is a fake jira example repository as a playground of django-river. https://github.com/javrasya/fakejira

Donations

This is a fully open source project and it can be better with your donations.

If you are using django-river to create a commercial product, please consider becoming our sponsor , patron or donate over PayPal

Documentation

Online documentation is available at http://django-river.rtfd.org/

Advance Admin

A very modern admin with some user friendly interfaces that is called River Admin has been published.

Requirements

  • Python (3.5 (for Django 2.2 only), 3.6, 3.7, 3.8)
  • Django (2.2, 3.0, 3.1)
  • Django = 2.2 is supported for Python >= 3.5
  • Django >= 3.0 is supported for Python >= 3.6

Supported (Tested) Databases:

PostgreSQL Tested Support
9
10
11
12
MySQL Tested Support
5.6
5.7
8.0
MSSQL Tested Support
19
17

Usage

  1. Install and enable it

    pip install django-river
    INSTALLED_APPS=[
        ...
        river
        ...
    ]
  2. Create your first state machine in your model and migrate your db

    from django.db import models
    from river.models.fields.state import StateField
    
    class MyModel(models.Model):
        my_state_field = StateField()
  3. Create all your states on the admin page

  4. Create a workflow with your model ( MyModel - my_state_field ) information on the admin page

  5. Create your transition metadata within the workflow created earlier, source and destination states

  6. Create your transition approval metadata within the workflow created earlier and authorization rules along with their priority on the admin page

  7. Enjoy your django-river journey.

    my_model=MyModel.objects.get(....)
    
    my_model.river.my_state_field.approve(as_user=transactioner_user)
    my_model.river.my_state_field.approve(as_user=transactioner_user, next_state=State.objects.get(label='re-opened'))
    
    # and much more. Check the documentation

Note

Whenever a model object is saved, it's state field will be initialized with the state is given at step-4 above by django-river.

Hooking Up With The Events

django-river provides you to have your custom code run on certain events. And since version v2.1.0 this has also been supported for on the fly changes. You can create your functions and also the hooks to a certain events by just creating few database items. Let's see what event types that can be hooked a function to;

  • An approval is approved
  • A transition goes through
  • The workflow is complete

For all these event types, you can create a hooking with a given function which is created separately and preliminary than the hookings for all the workflow objects you have or you will possible have, or for a specific workflow object. You can also hook up before or after the events happen.

1. Create Function

This will be the description of your functions. So you define them once and you can use them with multiple hooking up. Just go to /admin/river/function/ admin page and create your functions there. django-river function admin support python code highlights.

INSTALLED_APPS=[
    ...
    codemirror2
    river
    ...
]

Here is an example function;

from datetime import datetime

def handle(context):
    print(datetime.now())

Important: YOUR FUNCTION SHOULD BE NAMED AS handle. Otherwise django-river won't execute your function.

django-river will pass a context down to your function in order for you to know why the function is triggered or for which object or so. And the context will look different for different type of events. Please see detailed context documentation to know more on what you would get from context in your functions.

You can find an advance function example on the link.

Create Function Page

2. Hook It Up

The hookings in django-river can be created both specifically for a workflow object or for a whole workflow. django-river comes with some model objects and admin interfaces which you can use to create the hooks.

  • To create one for whole workflow regardless of what the workflow object is, go to

    • /admin/river/onapprovedhook/ to hook up to an approval
    • /admin/river/ontransithook/ to hook up to a transition
    • /admin/river/oncompletehook/ to hook up to the completion of the workflow
  • To create one for a specific workflow object you should use the admin interface for the workflow object itself. One amazing feature of django-river is now that it creates a default admin interface with the hookings for your workflow model class. If you have already defined one, django-river enriches your already defined admin with the hooking section. It is default disabled. To enable it just define RIVER_INJECT_MODEL_ADMIN to be True in the settings.py.

Note: They can programmatically be created as well since they are model objects. If it is needed to be at workflow level, just don't provide the workflow object column. If it is needed to be for a specific workflow object then provide it.

Here are the list of hook models;

  • OnApprovedHook
  • OnTransitHook
  • OnCompleteHook

Before Reporting A Bug

django-river has behavioral tests that are very easy to read and write. One can easily set up one and see if everything is running as expected. Please look at other examples (that are the files with .feature postfix) under features folder that you can get all the inspiration and create one for yourself before you open an issue Then refer to your behavioral test to point out what is not function as expected to speed the process up for your own sake. It is even better to name it with your issue number so we can persist it in the repository.

Migrations

2.X.X to 3.0.0

django-river v3.0.0 comes with quite number of migrations, but the good news is that even though those are hard to determine kind of migrations, it comes with the required migrations out of the box. All you need to do is to run;

python manage.py migrate river

3.1.X to 3.2.X

django-river started to support Microsoft SQL Server 17 and 19 after version 3.2.0 but the previous migrations didn't get along with it. We needed to reset all the migrations to have fresh start. If you have already migrated to version 3.1.X all you need to do is to pull your migrations back to the beginning.

python manage.py migrate --fake river zero
python manage.py migrate --fake river

FAQ

Have a look at FAQ

Contributors

Code Contributors

This project exists thanks to all the people who contribute 🚀 ❤️

https://opencollective.com/django-river/contributors.svg?width=890&button=false

Financial Contributors

Become a financial contributor and help us sustain our community. Contribute

Individuals

https://opencollective.com/django-river/individuals.svg?width=890

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. Contribute

License

This software is licensed under the New BSD License. See the LICENSE file in the top distribution directory for the full license text.

django-river's People

Contributors

attapurposevc avatar bgbrwr avatar bitdeli-chef avatar foobarquaxx avatar h3 avatar javrasya avatar jmyles avatar manickam91 avatar monkeywithacupcake avatar psychok7 avatar simkimsia avatar timgates42 avatar vinceyang15 avatar xuziheng1002 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

django-river's Issues

[Conceptual] Workflow model

Hello all,

Ok, I've been playing with river and it seems very nice. I have a question, of conceptual nature:

We have states, which have transitions and proceedings meta, which governs who can trigger the transitions.

But what if I want to model multiple workflows? Let's say I have one workflow for approving blueprints of homes in a city hall. And I have a specific workflow for approving tickets, commercial buildings, etc.

This not something a layman will mess around, I know, but shouldn't we be able to say, something like:

class Blueprint(models.Model):

    field_a = models....

    state = StateField(workflow='blueprint-approval')

class ComercialBuilding(models.Model):

    field_a = models...
    state = StateField(workflow='comercial-approval')

We could just create the blueprint-approval workflow when migrating.

Currently there is no easy way to group a set of states and transitions. We are implictly saying to the user that river can only track a single workflow?

Assign to someone a task

Hi, is it possible to assign a task to a user via API? Or it is possible just to set permissions to the transition via Django permissions? I've searched the API docs but I didn't find any useful information.

In the Jira example, I would like to achieve the assignment of the task to another person who could close or re-open it.

Adding new state/transition to existing objects

In my workflow, I wanted to add a new state/transition, and I'm getting the available proceedings through get_available_proceedings method, but the old objects don't show the new created transition, only shows in new records.
Is it supposed? There is a way to "update" or "rebuild" in order to show the new transition in old objects?

Thanks in advance

AttributeError: 'NoneType' object has no attribute '_meta'

When I try add processing meta, I get error:

AttributeError: 'NoneType' object has no attribute '_meta'

and get 2 warnings:

WARNINGS:
river.ProceedingMeta.groups: (fields.W340) null has no effect on ManyToManyField.
river.ProceedingMeta.parents: (fields.W340) null has no effect on ManyToManyField.

Django version 1.11.1

Warnings when using Django 1.8

Hello guys,

Cool project! I'm just testing this and it seems that there are a bunch of warnings here related to the deprecation of explicit label apps. I'm not sure why this is happening, but I'm using also django-tenants and I'm treating this as a tenant app (I'll get the same models that we have on river for each schema, each schema corresponding to a single customer).

/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/river/models/proceeding.py:28: RemovedInDjango19Warning: Model class river.models.proceeding.Proceeding doesn't declare an explicit app_label and either isn't in an application in INSTALLED_APPS or else was imported before its application was loaded. This will no longer be supported in Django 1.9.
  class Proceeding(BaseModel):

Not sure if this is only me, or if it's in general.

There is no available initial state for the content type

@javrasya This issue is somehow related to this one #32

So basically after creating my model with the status = StateField(editable=False) and migrating on an existing DB, i noticed that i had to do a makemigrations/migrate again for it to create an automatic proceeding migration 0007_auto_20161014_1736.
Without this migrations, i would run into this error river.utils.exceptions.RiverException: There is no available initial state for the content type mymodel. With it everything worked fine.

Now i am trying to run a unit test for my rest api endpoint were i have instance = ModelClass.workflow.create(**validated_data) to create a new object, but since i don't have that auto migration i am running into river.utils.exceptions.RiverException: There is no available initial state for the content type mymodel. I tried call_command('makemigrations', stdout=out) and call_command('migrate', stdout=out) from within the test but not luck.

Is there a better way to avoid this error all-together? Or a way for me to force that proceeding extra migration?

Thanks

Handlers does not work when there is multiple worker is running like Gunicorn

This is probably because of production environment is working as multiple worker with Gunicorn. Handlers are registered in a worker but triggering that handlers is done probably in another worker. This should be handled by introducing shared memory object for handlers. Redis, Database or another strategy can be used for handler registration

RiverException: There can be only one state field in a model class

@javrasya When running my own unit tests i started getting this river exception river.utils.exceptions.RiverException: There can be only one state field in a model class, but the thing is i am not using more than 1 statefield in my model classes.

I have status = StateField(editable=False, verbose_name=_('status')) in 2 different Models.

Looking at the your code, i found:

        if id(cls) in classes:
            raise RiverException(ErrorCode.MULTIPLE_STATE_FIELDS, "There can be only one state field in a model class.")

I commented this and my tests started working again, but i am not sure if its a good fix. Why does it think i have more than 1 state field??

Here is the full traceback:

root@5ef5a8a65ed5:/code# python3 manage.py test --no-input --settings=xx.settings.test
Creating test database for alias 'default'...
Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/__init__.py", line 367, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/__init__.py", line 359, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/commands/test.py", line 29, in run_from_argv
    super(Command, self).run_from_argv(argv)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/base.py", line 294, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/base.py", line 345, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/commands/test.py", line 72, in handle
    failures = test_runner.run_tests(test_labels)
  File "/usr/local/lib/python3.5/dist-packages/django/test/runner.py", line 549, in run_tests
    old_config = self.setup_databases()
  File "/usr/local/lib/python3.5/dist-packages/django/test/runner.py", line 499, in setup_databases
    self.parallel, **kwargs
  File "/usr/local/lib/python3.5/dist-packages/django/test/runner.py", line 743, in setup_databases
    serialize=connection.settings_dict.get("TEST", {}).get("SERIALIZE", True),
  File "/usr/local/lib/python3.5/dist-packages/django/db/backends/base/creation.py", line 70, in create_test_db
    run_syncdb=True,
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/__init__.py", line 130, in call_command
    return command.execute(*args, **defaults)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/base.py", line 345, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/commands/migrate.py", line 204, in handle
    fake_initial=fake_initial,
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/executor.py", line 115, in migrate
    state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/executor.py", line 145, in _migrate_all_forwards
    state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/executor.py", line 244, in apply_migration
    state = migration.apply(state, schema_editor)
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/migration.py", line 119, in apply
    operation.state_forwards(self.app_label, project_state)
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/operations/fields.py", line 194, in state_forwards
    state.reload_model(app_label, self.model_name_lower)
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/state.py", line 162, in reload_model
    self.apps.render_multiple(states_to_be_rendered)
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/state.py", line 277, in render_multiple
    model.render(self)
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/state.py", line 559, in render
    body,
  File "/usr/local/lib/python3.5/dist-packages/django/db/models/base.py", line 157, in __new__
    new_class.add_to_class(obj_name, obj)
  File "/usr/local/lib/python3.5/dist-packages/django/db/models/base.py", line 316, in add_to_class
    value.contribute_to_class(cls, name)
  File "/code/river/models/fields/state.py", line 95, in contribute_to_class
    raise RiverException(ErrorCode.MULTIPLE_STATE_FIELDS, "There can be only one state field in a model class.")
river.utils.exceptions.RiverException: There can be only one state field in a model class.

Adding comments to workflow

I might not have understood how to implement it, but I would like the user to add comments to the workflow (for example, to explain why they have accepted/rejected the bug fix). Is this currently possible and if so how would I do it?

Thanks for help

ImportError: No module named 'proceeding_meta'

I get this error with 0.8.1

  File "/home/vagrant/.virtualenvs/myenv/lib/python3.4/site-packages/river/admin/__init__.py", line 9, in <module>
    import proceeding_meta
ImportError: No module named 'proceeding_meta'

If I comment out the import statement in river/admin/init.py it works

django 1.9 support

Hi,

when I try django-river with django 1.9 I get an exception when invoking python manage.py

django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.

Any chance to get this fixed?

Thank you very much

Action Button not rendering

There is a problem with the action buttons not rendering for some odd reason.

I'm using Django 2.0.4 and applied all of the compatibility issue changes and fixed, thanks to #56

So far implementing django-river was smooth and easy! Except for the action buttons which I have trouble setting up.

My code:

def create_action_button(obj, proceeding):
    return """
        <input
            type="button"
            style="margin:2px;2px;2px;2px;"
            value="%s"
            onclick = "location.href=\'%s\'"
        />
    """ % (proceeding.meta.transition,
            #reverse('proceed_ticket',
            #    kwargs={'ticket_id': obj.pk, 'next_state_id': proceeding.meta.transition.destination_state.pk})
            ""
            )

class ContractAdmin(admin.ModelAdmin):
    ordering = ['updated_on']
    search_fields = ['contract_number']
    list_display = ('contract_number', 'user_id', 'created_on', 'updated_on', 'contract_status', 'contract_action')
    list_filter = ('created_on', 'updated_on', 'contract_number',)
    list_per_page = 100


    def get_list_display(self, request):
        self.user = request.user
        return super(ContractAdmin, self).get_list_display(request)

    def contract_action(self, obj):
        content = ""
        for proceeding in obj.get_available_proceedings(self.user):
            content += create_action_button(obj, proceeding)

        return content

    contract_action.allow_tags = True

Here's what I got after I runserver and tried to do some workflow business processing stuff.

action-button-not-rendering

Performance issues with get_available_proceedings function

@javrasya there seems to be a performance issue with .get_available_proceedings function.

So, i followed your youtube admin tutorial and in the beginning everything seemed to work fine.

Now i have been noticing a performance issue with just 15 objects in my model. When digging a bit further, i realized the problem was in the get_available_proceedings method:

    def river_actions(self, obj):
        content = ""
        for proceeding in obj.get_available_proceedings(self.user):
            content += create_river_button(obj, proceeding)
        return content

If i leave it like this, my page in the admin takes about 6 seconds to load (with just 15 objects). If i comment the function, it loads in less than 1 second.

My model looks like this:

class QRequest(models.Model):
    # Some fields and:
    status = StateField(editable=False, verbose_name=_('status'))

Any ideas what could be causing this in that function and how it can be fixed?

Django river and pyodbc-azure issue

Hi,
I have the problem with django-river 0.9.0 and django-pyodbc-azure 1.10.4.0.
When I trying to create new object in db I get the following error:

Traceback (most recent call last):
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/query.py", line 488, in update_or_create
core_1 | obj = self.get(**lookup)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/query.py", line 385, in get
core_1 | self.model._meta.object_name
core_1 | river.models.proceeding.DoesNotExist: Proceeding matching query does not exist.
core_1 |
core_1 | During handling of the above exception, another exception occurred:
core_1 |
core_1 | Traceback (most recent call last):
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/backends/utils.py", line 64, in execute
core_1 | return self.cursor.execute(sql, params)
core_1 | File "/usr/local/lib/python3.5/dist-packages/sql_server/pyodbc/base.py", line 539, in execute
core_1 | return self.cursor.execute(sql, params)
core_1 | pyodbc.IntegrityError: ('23000', "[23000] [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Violation of UNIQUE KEY constraint 'UQ__river_pr__6FE969F2141F7B42'. Cannot insert duplicate key in object 'dbo.river_proceeding'. The duplicate key value is (). (2627) (SQLExecDirectW)")
core_1 |
core_1 | The above exception was the direct cause of the following exception:
core_1 |
core_1 | Traceback (most recent call last):
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/exception.py", line 39, in inner
core_1 | response = get_response(request)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/base.py", line 187, in _get_response
core_1 | response = self.process_exception_by_middleware(e, request)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/base.py", line 185, in _get_response
core_1 | response = wrapped_callback(request, *callback_args, **callback_kwargs)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/contrib/admin/options.py", line 544, in wrapper
core_1 | return self.admin_site.admin_view(view)(*args, **kwargs)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/utils/decorators.py", line 149, in _wrapped_view
core_1 | response = view_func(request, *args, **kwargs)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/views/decorators/cache.py", line 57, in _wrapped_view_func
core_1 | response = view_func(request, *args, **kwargs)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/contrib/admin/sites.py", line 211, in inner
core_1 | return view(request, *args, **kwargs)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/contrib/admin/options.py", line 1509, in add_view
core_1 | return self.changeform_view(request, None, form_url, extra_context)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/utils/decorators.py", line 67, in _wrapper
core_1 | return bound_func(*args, **kwargs)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/utils/decorators.py", line 149, in _wrapped_view
core_1 | response = view_func(request, *args, **kwargs)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/utils/decorators.py", line 63, in bound_func
core_1 | return func.get(self, type(self))(*args2, **kwargs2)
core_1 | File "/usr/lib/python3.5/contextlib.py", line 30, in inner
core_1 | return func(*args, **kwds)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/contrib/admin/options.py", line 1449, in changeform_view
core_1 | self.save_model(request, new_object, form, not add)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/contrib/admin/options.py", line 1007, in save_model
core_1 | obj.save()
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/base.py", line 796, in save
core_1 | force_update=force_update, update_fields=update_fields)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/base.py", line 833, in save_base
core_1 | update_fields=update_fields, raw=raw, using=using)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/dispatch/dispatcher.py", line 191, in send
core_1 | response = receiver(signal=self, sender=sender, **named)
core_1 | File "/usr/local/lib/python3.5/dist-packages/river/models/fields/state.py", line 136, in _post_save
core_1 | ObjectService.register_object(instance)
core_1 | File "/usr/local/lib/python3.5/dist-packages/river/services/object.py", line 15, in register_object
core_1 | ProceedingService.init_proceedings(workflow_object)
core_1 | File "/usr/local/lib/python3.5/dist-packages/river/services/proceeding.py", line 31, in init_proceedings
core_1 | 'status': PENDING,
core_1 | File "/usr/local/lib/python3.5/dist-packages/river/models/managers/proceeding.py", line 26, in update_or_create
core_1 | return super(ProceedingManager, self).update_or_create(*args, **kwarg)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/manager.py", line 85, in manager_method
core_1 | return getattr(self.get_queryset(), name)(*args, **kwargs)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/query.py", line 490, in update_or_create
core_1 | obj, created = self._create_object_from_params(lookup, params)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/query.py", line 513, in _create_object_from_params
core_1 | six.reraise(*exc_info)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/utils/six.py", line 686, in reraise
core_1 | raise value
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/query.py", line 505, in _create_object_from_params
core_1 | obj = self.create(**params)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/query.py", line 399, in create
core_1 | obj.save(force_insert=True, using=self.db)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/base.py", line 796, in save
core_1 | force_update=force_update, update_fields=update_fields)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/base.py", line 824, in save_base
core_1 | updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/base.py", line 908, in _save_table
core_1 | result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/base.py", line 947, in _do_insert
core_1 | using=using, raw=raw)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/manager.py", line 85, in manager_method
core_1 | return getattr(self.get_queryset(), name)(*args, **kwargs)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/query.py", line 1045, in _insert
core_1 | return query.get_compiler(using=using).execute_sql(return_id)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/models/sql/compiler.py", line 1054, in execute_sql
core_1 | cursor.execute(sql, params)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/backends/utils.py", line 79, in execute
core_1 | return super(CursorDebugWrapper, self).execute(sql, params)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/backends/utils.py", line 64, in execute
core_1 | return self.cursor.execute(sql, params)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/utils.py", line 94, in exit
core_1 | six.reraise(dj_exc_type, dj_exc_value, traceback)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/utils/six.py", line 685, in reraise
core_1 | raise value.with_traceback(tb)
core_1 | File "/usr/local/lib/python3.5/dist-packages/django/db/backends/utils.py", line 64, in execute
core_1 | return self.cursor.execute(sql, params)
core_1 | File "/usr/local/lib/python3.5/dist-packages/sql_server/pyodbc/base.py", line 539, in execute
core_1 | return self.cursor.execute(sql, params)
core_1 | django.db.utils.IntegrityError: ('23000', "[23000] [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Violation of UNIQUE KEY constraint 'UQ__river_pr__6FE969F2141F7B42'. Cannot insert duplicate key in object 'dbo.river_proceeding'. The duplicate key value is (). (2627) (SQLExecDirectW)")

And my model:
class Prop(models.Model):

subject = models.CharField("Subject", max_length=100, null=False, blank=False)
status = StateField(editable=False)

Can you help me?

NodeNotFoundError Migration error(Python 3)

Hi, i am trying django-river 0.8.2 + Django=1.10 and i am running into a migration error.

my model is:

class QualificationRequest(models.Model):
    rnal_code = models.CharField(max_length=50, null=True, blank=True)
    name = models.CharField(max_length=200)
    phone = models.CharField(max_length=50)
    email = models.EmailField()

    status = StateField(editable=False)

    created_on = models.DateTimeField(auto_now_add=True)
    updated_on = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

here is the traceback:

[localhost] local: docker-compose -f dev.yml run --rm smal_task python3 manage.py makemigrations --settings=smal.settings.local
System check identified some issues:

WARNINGS:
river.ProceedingMeta.groups: (fields.W340) null has no effect on ManyToManyField.
river.ProceedingMeta.parents: (fields.W340) null has no effect on ManyToManyField.
Migrations for 'qualifications':
  qualifications/migrations/0001_initial.py:
    - Create model QualificationRequest
Migrations for 'river':
  /usr/local/lib/python3.5/dist-packages/river/migrations/0007_auto_20161013_1124.py:
    - Alter field parents on proceedingmeta
[localhost] local: docker-compose -f dev.yml run --rm smal_task python3 manage.py migrate --settings=smal.settings.local
System check identified some issues:

WARNINGS:
river.ProceedingMeta.groups: (fields.W340) null has no effect on ManyToManyField.
river.ProceedingMeta.parents: (fields.W340) null has no effect on ManyToManyField.
Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/__init__.py", line 367, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/__init__.py", line 359, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/base.py", line 294, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/base.py", line 345, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/commands/migrate.py", line 83, in handle
    executor = MigrationExecutor(connection, self.migration_progress_callback)
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/executor.py", line 20, in __init__
    self.loader = MigrationLoader(self.connection)
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/loader.py", line 52, in __init__
    self.build_graph()
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/loader.py", line 268, in build_graph
    raise exc
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/loader.py", line 238, in build_graph
    self.graph.validate_consistency()
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/graph.py", line 261, in validate_consistency
    [n.raise_error() for n in self.node_map.values() if isinstance(n, DummyNode)]
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/graph.py", line 261, in <listcomp>
    [n.raise_error() for n in self.node_map.values() if isinstance(n, DummyNode)]
  File "/usr/local/lib/python3.5/dist-packages/django/db/migrations/graph.py", line 104, in raise_error
    raise NodeNotFoundError(self.error_message, self.key, origin=self.origin)
django.db.migrations.exceptions.NodeNotFoundError: Migration qualifications.0001_initial dependencies reference nonexistent parent node ('river', '0007_auto_20161013_1124')

Any ideas??

'NoneType' object has no attribute '_meta'

@javrasya started getting this error for some reason on the Admin with the current master (maybe some of the changes we made).

I have a patch, will submit is soon.

Traceback:

File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/exception.py" in inner
  39.             response = get_response(request)

File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)

File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/usr/local/lib/python3.5/dist-packages/django/contrib/admin/options.py" in wrapper
  544.                 return self.admin_site.admin_view(view)(*args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/django/utils/decorators.py" in _wrapped_view
  149.                     response = view_func(request, *args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/django/views/decorators/cache.py" in _wrapped_view_func
  57.         response = view_func(request, *args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/django/contrib/admin/sites.py" in inner
  211.             return view(request, *args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/django/contrib/admin/options.py" in change_view
  1512.         return self.changeform_view(request, object_id, form_url, extra_context)

File "/usr/local/lib/python3.5/dist-packages/django/utils/decorators.py" in _wrapper
  67.             return bound_func(*args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/django/utils/decorators.py" in _wrapped_view
  149.                     response = view_func(request, *args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/django/utils/decorators.py" in bound_func
  63.                 return func.__get__(self, type(self))(*args2, **kwargs2)

File "/usr/lib/python3.5/contextlib.py" in inner
  30.                 return func(*args, **kwds)

File "/usr/local/lib/python3.5/dist-packages/django/contrib/admin/options.py" in changeform_view
  1466.                 form = ModelForm(instance=obj)

File "/usr/local/lib/python3.5/dist-packages/river/admin/proceeding_meta.py" in __init__
  28.         self.declared_fields['content_type'].queryset = ContentType.objects.filter(pk__in=get_content_types())

File "/usr/local/lib/python3.5/dist-packages/river/admin/proceeding_meta.py" in get_content_types
  14.         for f in model._meta.fields:

Exception Type: AttributeError at /admin/river/proceedingmeta/1/change/
Exception Value: 'NoneType' object has no attribute '_meta'

Change object_id from PositiveIntegerField to CharField to allow UUID on models

object_id = models.PositiveIntegerField(verbose_name=_('Related Object'))

Any model with the StateFIeld must have an integer ID(e.g. 12). If the model's pk is a UUID (e.g. aaa11cfa-c9d0-4ca8-9252-5be05e18d6c0) it will break.

Here is the problem and solution.

The point that is made for a solution for a generic relationship to be valid for both IntegerField and CharField id's is to set object_id to CharField. Integers, Strings, and UUIDs can store in a CharField.

There is no available initial state for the content type while testing

When I create an integration test, I create objects in the DB for which I have defined a state field.

        p = Procedure.objects.create()

In django admin I defined 4 states, and 3 transitions

Screenshot 2019-05-12 at 17 46 39

So I understood from the documentation that since CREATED is never a destination state, django-river would have guessed that this was the initial state.

But I get this error:

raise RiverException(ErrorCode.NO_AVAILABLE_INITIAL_STATE, 'There is no available initial state for the content type %s. ' % self._content_type)
river.utils.exceptions.RiverException: There is no available initial state for the content type procedure. 

I'm completely lost, as I understand that this is common but I haven't find a solution, also looking at similar issues.

Any idea?

Thanks

Naive datetimes

Because Proceeding is using datetime.datetime rather than Django's purpose-built now function, I"m getting lots of warnings like

Marched to state In Progress
.../python3.6/site-packages/django/db/models/fields/__init__.py:1451: RuntimeWarning: DateTimeField Proceeding.transaction_date received a naive datetime (2017-08-04 17:19:10.842537) while time zone support is active.

This can be fixed by passing e.g.
proceeding.transaction_date = now()
and fixing the defaults.

Question about workflow model design

@javrasya I trying out django-river, and i noticed that if i do MyModel.objects.all() i get a AttributeError: type object 'MyModel' has no attribute 'objects', but if i do MyModel.workflow.get_queryset().all() it works.

Is this a bug or a design decision?? How does it work in the admin out of the box then, but when i try it manually i run into that error? Would i always have to use the .workflow to access my models manager?

I needed to make this work with Rest Framework and because of this issue i had to override the create method for example and explicitly say instance = ModelClass.workflow.create(**validated_data) instead of the normal instance = ModelClass.objects.create(**validated_data)

Thanks

next_proceedings crashing

Using latest from github:

In [8]: c = RClaim.objects.first()

In [9]: c
Out[9]: <RClaim: shacker - 34.33>

In [10]: dir(c)
Out[10]: 
[
# truncated
 'get_available_proceedings',
 'get_deferred_fields',
 'get_initial_state',
 'get_state',
 'initial_proceedings',
 'is_workflow_completed',
 'next_proceedings',
 'proceed',
 'proceeding',
 'proceedings',
']

In [11]: c.proceeding
Out[11]: <Proceeding: Proceeding object>

In [13]: c.proceedings.all()
Out[13]: <QuerySet [<Proceeding: Proceeding object>, <Proceeding: Proceeding object>, <Proceeding: Proceeding object>, <Proceeding: Proceeding object>]>


In [14]: c.next_proceedings
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-14-2d0182d11c44> in <module>()
----> 1 c.next_proceedings

~/dev/django-river/river/models/fields/state.py in next_proceedings(self)
     82             from river.services.proceeding import ProceedingService
     83 
---> 84             return self.get_state() in ProceedingService.get_next_proceedings(ContentType.objects.get_for_model(self))
     85 
     86         @property

~/dev/django-river/river/services/proceeding.py in get_next_proceedings(workflow_object, proceeding_pks, current_states, index, limit)
    100         index += 1
--> 101         current_states = list(current_states.values_list('pk', flat=True)) if current_states else [workflow_object.get_state()]
    103         next_proceedings = Proceeding.objects.filter(workflow_object=workflow_object, meta__transition__source_state__in=current_states)
    104         if workflow_object.proceeding:

AttributeError: 'ContentType' object has no attribute 'get_state'

I believe this is because `fields/state.py:84 is returning a ContentType rather than the actual object. But am not quite sure how to fix.

django 2.1

Hello Ahmet ;
will we be able to run django-river on Django 2.1

Cannot set up an initial state

In Transitions you cannot set up an initial state (ie a Transition with a blank Source State) because it is a required field, so you have to specify the initial state when you create the object

null has no effect on ManyToManyField

When installing River into a Django 1.11 project for first time, then running migrate, warning is emitted:

WARNINGS:
river.ProceedingMeta.groups: (fields.W340) null has no effect on ManyToManyField.
river.ProceedingMeta.parents: (fields.W340) null has no effect on ManyToManyField.

So it's a warning not an error, but means the null=True/False property on those models should probably be removed.

There is no available state for destination for the user

raise RiverException(ErrorCode.NO_AVAILABLE_NEXT_STATE_FOR_USER, "There is no available state for destination for the user.")
RiverException: There is no available state for destination for the user.

Hi ,
Need to know why this exception raises, and the details please. Development of the project was done as per the documentation.
The issue is only with Postgres, as sqlite3 is working fine
Please help

Error when creating a simple app with StateField

Hello again.

This migration is failing:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import river.models.fields.state


class Migration(migrations.Migration):

    dependencies = [
        ('river', '__first__'),
    ]

    operations = [
        migrations.CreateModel(
            name='Ticket',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('title', models.CharField(max_length=128, verbose_name='Title')),
                ('proceeding_track', models.ForeignKey(blank=True, to='river.ProceedingTrack', null=True)),
                ('state', river.models.fields.state.StateField(blank=True, to='river.State', null=True)),
            ],
        ),
    ]

The model is pretty darn simple:

from django.contrib.gis.db import models
from django.utils.translation import ugettext as _
from river.models.fields.state import StateField

class Ticket(models.Model):

    title = models.CharField(_('Title'),
                             max_length=128)

    state = StateField()

    def __unicode__(self):

        return self.title

Full trace:

=== Running migrate for schema duratex
Operations to perform:
  Synchronize unmigrated apps: django_tenants, gis, staticfiles, debug_toolbar, messages, django_extensions, rest_framework_gis, rest_framework, river
  Apply all migrations: tickets, customers, sessions, admin, datum, auth, contenttypes
Synchronizing apps without migrations:
  Creating tables...
    Creating table river_application
    Creating table river_state
    Creating table river_transition
    Creating table river_proceedingmeta
    Creating table river_proceeding
    Creating table river_proceedingtrack
    Creating table river_handler
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying customers.0001_initial... OK
  Applying datum.0001_initial... OK
  Applying sessions.0001_initial... OK
  Applying tickets.0001_initial...Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/models/manager.py", line 127, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/models/query.py", line 348, in create
    obj.save(force_insert=True, using=self.db)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django_tenants/models.py", line 88, in save
    self.create_schema(check_if_exists=True, verbosity=verbosity)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django_tenants/models.py", line 136, in create_schema
    verbosity=verbosity)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 120, in call_command
    return command.execute(*args, **defaults)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/core/management/base.py", line 441, in execute
    output = self.handle(*args, **options)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django_tenants/management/commands/migrate_schemas.py", line 53, in handle
    self.run_migrations(self.schema_name, settings.TENANT_APPS, options)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django_tenants/management/commands/migrate_schemas.py", line 64, in run_migrations
    command.execute(*self.args, **options)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/core/management/base.py", line 441, in execute
    output = self.handle(*args, **options)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/core/management/commands/migrate.py", line 221, in handle
    executor.migrate(targets, plan, fake=fake, fake_initial=fake_initial)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/migrations/executor.py", line 110, in migrate
    self.apply_migration(states[migration], migration, fake=fake, fake_initial=fake_initial)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/migrations/executor.py", line 147, in apply_migration
    state = migration.apply(state, schema_editor)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/migrations/migration.py", line 115, in apply
    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/migrations/operations/models.py", line 59, in database_forwards
    schema_editor.create_model(model)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/schema.py", line 81, in create_model
    super(PostGISSchemaEditor, self).create_model(model)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/backends/base/schema.py", line 286, in create_model
    self.execute(sql, params or None)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/backends/base/schema.py", line 111, in execute
    cursor.execute(sql, params)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/backends/utils.py", line 79, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/utils.py", line 97, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/backends/utils.py", line 62, in execute
    return self.cursor.execute(sql)
ProgrammingError: column "proceeding_track_id" specified more than once

branching from one state

Hello.
I'm like this project, because it's easy to understand. But, i misunderstud needs of Proceeding model.
Anyway, i got some issue, when try to realize backward workflow:

inprogress -> resolved
resolved -> inprogress
resolved -> done

When we try to proceed back from "resolved" to "inprogress" it's found "resolved -> done" transition and determines it's as required, and not change object state to "inprogress".
Workaround leads me to line in ProceedingService. There is issue begins.
What logics covered under this lines?
Also, i can't get what for we need to initialize all Proceeding on objects creation, not on demand. Does Proceeding is not can be interpreted as "log" - what transition was happen?

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.