Data Management

Let’s talk about data management in workflow applications. In a nutshell, a business process is a journey that starts with an event and moves toward a measurable result. Viewflow is your tool to clearly separate the workflow layer from the CRUD logic, helping to decouple workflow data from core business data.

Business data, the valuable stuff, is designed carefully, rarely changes, and sits comfortably in a third normal form database schema. Workflow-related data, on the other hand, is dynamic and evolves with the process.

Utilizing Built-in Fields

Viewflow’s Task and Process models come equipped with two generic foreign key fields: seed` and artifact. These fields are your go-to tools for linking workflow data to your core business data.

Think of each task or process as a journey. It starts with an event and ends with a result. The seed` field is where you plant the initial event, and the artifact` field is where you harvest the outcome. Simple as that.

Here’s how you can use these fields in practice:

class SelectSeedForm(forms.ModelForm):
    seed = forms.ModelChoiceField(
        queryset=Source.objects.filter(...)
    )

    def save(self, commit=True):
        self.instance.seed = self.cleaned_data["seed"]
        return super().save(commit=commit)

    class Meta:
        model = Process  # yuor process model
        fields = []

In your flow definition, it’s straightforward to integrate this form:

class SampleFlow(flow.Flow):
    start = flow.Start(
        CreateProcessView.as_view(
            form_class=SelectSourceForm,
        ),
    ).Next(...)

Build-in data field

In Viewflow, both Process and Task models come with a data JSONField. This field is perfect for storing workflow-related data. Unlike your core business data, workflow data is only needed within the context of the process. It could be anything from intermediate objects to decisions that don’t need to stick around.

The beauty of Viewflow is that it provides an API to treat the contents of this JSONField like regular Django model fields. This means you can use these virtual fields in model forms, making your code cleaner and more intuitive. You can define proxy models to enhance them with these virtual fields.

Here’s how you can do it:

class ApprovmentProcess(Process):
    # Avaible as process.data['approved'] as process.approved
    approved = jsonstore.BooleanField(default=False)

    class Meta:
        proxy = True

class AprovementFlow(flows.Flow):
    ...

    approve = (
        flow.View(
            views.UpdateProcessView.as_view(fields=["approved"])
        )
        .Next(...)
    )

Model inheritance

When you need that extra level of customization, Viewflow supports model inheritance for both Process and Task models.

You can dive deep with inheritance from AbstractTask or AbstractProcess. Alternatively, you can inherit directly from the Process model, though this approach involves additional joins during model selection.

Here’s an example:

class ShipmentProcess(Process):
    carrier = models.ForeingKey(Carrier, on_delete=models.CASCADE)

Pass seeds and data over task

In a workflow, to keep tasks independent from one another, you can initialize tasks with seed and data fields from previous tasks. This method ensures a clean handoff of information between tasks, maintaining the independence and modularity of each task.

Here’s how you can achieve this:

class MyFlow(flow.Flow):
    # Copy start task artifact link to next_task.seed
    start = (
        flow.Start(SeedSelectionView.as_view())
        .Next(this.next_task, task_seed=lambda activation: activation.task.artifact)
    )

    # Create a task with task.data field preinitialized
    next_task = (
        flow.If(
            cond=lambda activation: activation.task.seed.fresh
        )
        .Then(
            this.plant,
            task_data=lambda activation: {'seed': activation.task.seed}
        )
        .Else(
            this.eat,
            task_data=lambda activation: {'grain': activation.task.seed}
        )
    )

By passing seeds and data this way, you ensure each task has all the information it needs to function independently while maintaining a clear and logical workflow progression. This approach not only simplifies debugging and maintenance but also enhances the modularity and reusability of your tasks.

Advanced Branching and Data Handling

Preinitializing task data or seeds can be incredibly useful, especially when you have multiple similar tasks within a flow. This method ensures each task performs the work it requires independently.

Here’s an example:

split = (
    flow.Split()
    .Next(
        this.process_post,
        task_data_source=lambda activation: {'post': post} for post in activation.process.data['posts'],
    )
    .Next(this.join)
)

In this example, the split task distributes posts to the process_post task, initializing each with specific post data. This way, each process_post task works independently on its assigned post.

Similarly, you can create subprocesses with preinitialized seed/data fields:

public post = flow.Subprocess(
    PublishFlow.as_subprocess,
    process_seed=lambda activation: activation.process.artifact,
).Next(this.end)

In this case, the public_post subprocess is initialized with the artifact from the main process. This approach ensures that the subprocess has all the necessary context to execute its tasks effectively.

Using these techniques, you maintain a clean and organized workflow, where each task and subprocess is independent yet informed by the necessary context. This not only makes your workflow more robust but also simplifies maintenance and debugging, ensuring each part of the process works seamlessly.

Final notes

Viewflow provides a powerful set of tools for managing workflow data, from built-in fields and JSONField handling to advanced branching and model inheritance. By understanding and leveraging these features, you can create workflows that are both flexible and maintainable. This not only enhances your application’s functionality but also ensures a clear separation of concerns, making your codebase easier to manage and extend.