Allowing users to select which flow to roll back to django-viewflow - django

Hey all i have been using viewflow as the workflow engine in my django project. I wanted to know if it was possible to allow users to 'select' which flow they wish to roll back to if lets say an approval was rejected.
Here , the director chooses 'Reject' , however it doesn't make sense to end the flow here , instead it should be a be a selectable 'roll back' , so that the the people down the line do not need to restart the entire process again.
Here's what i have done so far :
flows.py
#director will approve or dont approve
approve_by_director = flow.View(
UpdateProcessView,
form_class=DirectorApproveForm,
task_title="Approval By Director"
).Permission("cash.director"
).Next(this.check_director)
check_director = flow.If(
cond=lambda act: act.process.director,
task_title="Processing"
).Then(this.send).Else(this.justification)
#justifications for the roll back.
justification = flow.View(
JustificationView,
task_title="Justifications for Roll Back"
).Assign(lambda act: self.request.user
).Permission(auto_create=True
).Next(this.roll_back)
roll_back = flow.Handler(this.roll_back_call).Next(this.approve_by_preparer) ##<---- here , i am just sending it back to the 'preparer' , however it would be great if this could be dynamic!
end = flow.End()
def roll_back_call(self, activation):
esig = ESignatures.objects.filter(paymentVoucherProcess = activation.process).filter(voided = False)
docu = Attachment.objects.filter(paymentVoucherProcess = activation.process).filter(voided = False)
if len(esig) > 0 :
for sig in esig:
sig.voided = True
sig.save()
if len(docu) > 0 :
for doc in docu:
doc.voided = True
doc.save()
activation.process.preparer = False
activation.process.verifier = False
activation.process.treasury = False
activation.process.director = False
The problem here is that since the .next() node , i hard coded the phase in which i wish to roll back to, however this is not ideal as it would be optimal for the user to be able to 'select' which phase they were to send it back...
Therefore , I have two questions :
1. Is my method of the roll back correct? (maybe there is a better way to do the roll back instead of calling the process and hard code refreshing the fields)
2. Is there a way to select which part of the flow the user wish to roll back to?
Thanks and i would greatly appreciate anybody's advise

Actually, the good BPMN practice pattern is to finish flow as soon as possible, and do not involve complex cycles. That makes code simple, clean and easy to support. Simplifies reporting and keeps all revisions of data inside different process instances
With Viewflow flow-restart decision could be implemented as flow.View that records user decision and following flow.Handler that starts new flow instance using one of additional flow.StartFunction
Another option, is to use flow.View to record user decision and flow.Switch to step into another task.
Generally, BPMN separates user tasks and gateways, that lead to all decisions been recorded, before used.

Related

Concurrency and django scaling horizontal

Hello I would like to ask you for advice for an application, I currently use Django cookie cutter for the project, I use as a library, Django MPTT in this model it must respect a certain hierarchy and that some objects do not find themselves in the same place as another to do this I need to isolate the objects to then perform my calculation and create my new objects. First of all since it could take a while I used Cellery to make my additions to the database I tried several concurrent accesses and I never had a rollback, I thought instead of using Cellery since I know how to scale the docker into several Django, I would integrate this creation into my view and use the #atomic decorator. The problem is that when we create objects at the same time on each different server (django1,django2,django3) I have a lot of rollback while with celeriac I have no problem. I don't understand why competition is more difficult in Django than in celery? Do you have any advice for me? For a better management of different Django servers at the same time, thank you in advance example :
Work in celery norollback :
#celery_app.task()
def create_user_in_matrix(user_id):
.....
new_human = HumanFaction.objects.select_for_update().filter(id=int(user_id))
with transaction.atomic():
new_human = new_human.first()
alderol = HumanFaction.objects.get(user_id=alderol_user_id)
print('selection utilisateur', new_human)
level = 1
level_max = HumanFaction.objects.aggregate(Max('level'))['level__max']
if level_max == 0:
level_max = 1
print('level maximum', level_max)
while level <= level_max and user_whole:
users = HumanFaction.objects.select_for_update().filter(level=level)
HumanFaction.objects.create(user=new_human, parent=aledol)
.....
Operates every third time in a django view during a colision test frequent rollback (django,1,2,3)
#transaction.atomic
def human_group(request):
......
new_human = HumanFaction.objects.select_for_update().filter(id=int(user_id))
with transaction.atomic():
new_human = new_human.first()
alderol = HumanFaction.objects.get(user_id=alderol_user_id)
print('selection utilisateur', new_human)
level = 1
level_max = HumanFaction.objects.aggregate(Max('level'))['level__max']
if level_max == 0:
level_max = 1
print('level maximum', level_max)
while level <= level_max and user_whole:
users = HumanFaction.objects.select_for_update().filter(level=level)
HumanFaction.objects.create(user=new_human, parent=aledol)
.....

How to Assign a Task to a user in Django-Vieflow

class HelloWorldFlow(Flow):
process_class = HelloWorldProcess
######### Here I start the process where i enter some text
start = (
flow.Start(CreateProcessView, fields=["text"])
.Permission(auto_create=True)
.Next(this.approve)
)
######### Here i update the process view with the user name which i obtained from "asignedto"- which consist of all the user in a drop down
approve = (
flow.View(UpdateProcessView, fields=["asignedto"])
.Assign(lambda act: act.process.created_by)
.Permission(auto_create=True)
.Next(this.task_assign)
)
######### Below is the code where i am facing difficulty i am not able to know how to pass the user name in the .Assign()
######### This one works .Assign(username="Debasish"), however i want to assign the user based on previous workflow which is approve, where i selected from the drop down
######### This one do not work .Assign(username=this.approve.owner)
task_assign = (
flow.View(AssignTaskView)
.Assign(this.approve.owner)
.Next(this.check_approve)
)
######### Below this its working
check_approve = (
flow.If(lambda activation: activation.process.approved)
.Then(this.send)
.Else(this.end)
)
send = flow.Handler(this.send_hello_world_request).Next(this.end)
end = flow.End()
def send_hello_world_request(self, activation):
print(activation.process.text)
One of the advantages of the BPMN is the process traceability. You can't implement a process that would not persist the process decisions. That's very handy for the process performance analytics.
Each task in the BPMN is independent of each other and could perform decisions on a process data only. That gives flexibility to a flow diagram and allows to rearrange flow nodes since there is only data storage dependency between them.
So, the short answer - store a user in the Process model and use .Assign(lambda act: act.process.granted_user_for_a_task)

python code for directory api to batch retrieve all users from domain

Currently I have a method that retrieves all ~119,000 gmail accounts and writes them to a csv file using python code below and the enabled admin.sdk + auth 2.0:
def get_accounts(self):
students = []
page_token = None
params = {'customer': 'my_customer'}
while True:
try:
if page_token:
params['pageToken'] = page_token
current_page = self.dir_api.users().list(**params).execute()
students.extend(current_page['users'])
# write each page of data to a file
csv_file = CSVWriter(students, self.output_file)
csv_file.write_file()
# clear the list for the next page of data
del students[:]
page_token = current_page.get('nextPageToken')
if not page_token:
break
except errors.HttpError as error:
break
I would like to retrieve all 119,000 as a lump sum, that is, without having to loop or as a batch call. Is this possible and if so, can you provide example python code? I have run into communication issues and have to rerun the process multiple times to obtain the ~119,000 accts successfully (takes about 10 minutes to download). Would like to minimize communication errors. Please advise if better method exists or non-looping method also is possible.
There's no way to do this as a batch because you need to know each pageToken and those are only given as the page is retrieved. However, you can increase your performance somewhat by getting larger pages:
params = {'customer': 'my_customer', 'maxResults': 500}
since the default page size when maxResults is not set is 100, adding maxResults: 500 will reduce the number of API calls by an order of 5. While each call may take slightly longer, you should notice performance increases because you're making far fewer API calls and HTTP round trips.
You should also look at using the fields parameter to only specify user attributes you need to read in the list. That way you're not wasting time and bandwidth retrieving details about your users that your app never uses. Try something like:
my_fields = 'nextPageToken,users(primaryEmail,name,suspended)'
params = {
'customer': 'my_customer',
maxResults': 500,
fields: my_fields
}
Last of all, if your app retrieves the list of users fairly frequently, turning on caching may help.

Rails - Tracking non-logged in user activity

Say an application has many Products and searching capabilities.
1. How can I track page views on each product(a simple counter) and
2. how can I track search queries as well?
Most importantly, I need to be able to find/order products by number of page views.
The (simpler / best performance / least outside dependency) the better!
Why this question? The solutions I've seen so far are either out of date gems or don't work well with searching.
Here's a simple way to do it. In the model:
def increment(by = 1)
self.views ||= 0 #Prevents error if views can be nil
self.views += by
self.save
end
Then in the controller:
#model.increment
Faster if made into an asynchronous job which I still need to do.
To prevent users spamming refresh or something I made it a bit more complicated:
def show
product_itemcode = params[:itemcode]
#product = Product.find_by(itemcode: product_itemcode)
unless session[product_itemcode.to_s]
#product.increment
session[product_itemcode.to_s] = true
end
end
Any thoughts?
EDIT:
An increment method already exists:
http://apidock.com/rails/ActiveRecord/Persistence/increment
You still have to call object.save though

FK validation within Django

Good afternoon,
I have my django server running with a REST api on top to serve my mobile devices. Now, at some point, the mobile device will communicate with Django.
Let's say the device is asking Django to add an object in the database, and within that object, I need to set a FK like this:
objectA = ObjectA.objects.create(title=title,
category_id = c_id, order = order, equipment_id = e_id,
info_maintenance = info_m, info_security = info_s,
info_general = info_g, alphabetical_notation = alphabetical_notation,
allow_comments = allow_comments,
added_by_id = user_id,
last_modified_by_id = user_id)
If the e_id and c_id is received from my mobile devices, should I check before calling this creation if they actually still exists in the DB? That is two extra queries... but if they can avoid any problems, I don't mind!
Thanks a lot!
It think that Django creates constraint on Foreign Key by default ( might depend on database though ). This means that if your foreign keys point to something that does not exist, then saving will fail ( resulting in Exception on Python side ).
You can reduce it to a single query (it should be a single query at least, warning I haven't tested the code):
if MyObject.objects.filter(id__in=[e_id, c_id]).distinct().count() == 2:
# create the object
ObjectA.objects.create(...)
else:
# objects corresponding e_id and c_id do not exist, do NOT create ObjectA
You should always validate any information that's coming from a user or that can be altered by a determined user. It wouldn't be difficult for someone to sniff the traffic and start constructing their own REST requests to your server. Always clean and validate external data that's being added to the system.