SQLAlchemy event how to get delete items info after db session commit - python

I am using SQLAlchemy event which is very very good, I can void a lots of repeated work.
Now I need to delete some items, and do some post work in after_delete listener after delete action, but, meanwhile in after_delete listener I need to using those deleted items info which cannot be accessed while the session had been committed, here's a piece of code .
def shop_delete_category(category_id):
item_category = ItemCategory.query.get(category_id)
if item_category is None:
result['code'] = 100
else:
deleted_category_id_items = ItemCategory.query.filter(ItemCategory.id == category_id).with_entities(ItemCategory.id).all()
# some other business logic
db.session.delete(item_category)
db.session.commit()
My event listener code:
#event.listens_for(ItemCategory, 'after_delete')
def after_delete_shop_category(mapper, connect, target):
# business logic
shop_category_delete_signal.send(target)
# here will need deleted_category_id_items
# how can I pass in ?
# logic will be like this
redis_cache_delete_category_ids(deleted_category_id_items)
I want to do some post work after delete action , and in delete action I will use deleted_category_id_items, my question is
how can I pass deleted_category_id_items the event? Or is there some better way to slove the problem ?
Edit: My own answer
found : before_delete listener can solve the problem
Edit:
Another question how can I pass my own parameter to a listener, like this
#event.listens_for(ItemCategory, 'after_delete')
def after_delete_shop_category(mapper, connect, target, *my_own_paramters):
# business logic
shop_category_delete_signal.send(target)
# here will need deleted_category_id_items
# how can I pass in ?
# logic will be like this
redis_cache_delete_category_ids(deleted_category_id_items)
# can use my_own_paramters do some work
Thanks very much !

Related

Django - update query doesn't work in pre_delete signal

I tried to update a Transaction model queryset in the below pre_delete signal that connected to the Wallet model, but it didn't work.
The signal triggers, and all lines work correctly, except the last line :(
#receiver(pre_delete, sender=Wallet)
def delete_wallet(sender, instance, **kwargs):
if instance.is_default:
raise ValidationError({"is_default": "You can't delete the default wallet"})
q = {"company": instance.company} if instance.company else {"user": instance.user}
try:
default_wallet = Wallet.objects.get(**q, is_default=True)
except Wallet.DoesNotExist:
default_wallet = Wallet.objects.filter(**q).exclude(pk=instance.pk).first()
if not default_wallet:
raise ValidationError({"is_default": "You can't delete the last wallet"})
instance.transactions.all().update(wallet=default_wallet)
I moved the logic to the delete() function of the Wallet model instead of using the pre_delete() signal, and it's fixed now.
Maybe it cant find default wallet so it tries go get the first one which is the wallet you want to delete (because it's before delete so it still exist).
Try to exclude pk=instance.pk in your default_wallet query.

Locust/Python: Splitting a tasks array with if conditions in a SequentialTaskSet

I'm new to Locust, and Python in general. I've been using JMeter for several years, and I'm trying to implement similar logic to what I've used there for handling failures in login.
I want to run a simple scenario: Login, Enter a folder, Logout
Where I'm having trouble is implementing this logic:
If any login call fails, I don't want to attempt the folder enter (avoid a cascading failure after login), but I still want to run the logout to ensure there is no active session.
My current SequentialTaskSet reads like this, which works for executing the child tasksets:
class folder_enter_scalability_taskset(SequentialTaskSet):
def on_start(self):
self.tm = TransactionManager()
#task
def seedata_config(self):
seeddata = next(seeddata_reader)
self.client.username = seeddata['username']
self.client.password = seeddata['password']
tasks = [login_taskset, folder_enter_taskset, logout_taskset]
Is there a way to split up the tasks array into multiple steps within the same SequentialTaskSet?
Always Login
if login passed, enter folder
Always Logout
Thanks to phil in the comments for pointing me in the right direction, I now have a working code with try-finally and if-else instead of a tasks array. Hope this can help someone else:
class folder_enter_scalability_taskset(SequentialTaskSet):
def on_start(self):
self.tm = TransactionManager()
#task
def seedata_config(self):
seeddata = next(seeddata_reader)
self.client.username = seeddata['username']
self.client.password = seeddata['password']
self.client.userrole = seeddata['userrole']
#task
def folder_enter_scalability(self):
try:
login_taskset.login(self)
if self.client.loginFail is False:
folder_enter_taskset.folder_enter(self)
else:
logging.error("Login failed, skipping to logout")
finally:
logout_taskset.logout(self)
I wasn't having luck with getting it to skip folder enter if I placed it in a try-else-finally condition, so I went with adding this self.client.loginFail in the login taskset to support if-else within try.
It's initiated as 'False', and flipped to 'True' in case of failure in the taskset.

How to store user data and display them according to their corresponding callbacks

Here's my code:
from telegram import *
from telegram.ext import *
import telegram, telegram.ext
u = Updater('TOKEN', use_context=True)
j=u.job_queue
def set(update: Update, context: CallbackContext):
user_says=' '.join(context.args)
user_says=user_says.split(' ')
global item, chat_id
item=user_says[1:]
chat_id=update.effective_chat.id
j.run_once(callback, int(user_says[0]))
def callback(context: telegram.ext.CallbackContext):
context.bot.send_message(chat_id=chat_id, text=f'Great you won {item}')
if __name__ == '__main__':
dp = u.dispatcher
dp.add_handler(CommandHandler('set', set))
u.start_polling()
u.idle()
So the problem is if someone start the timer and in the mean time when the timer is running someone else (or the same person) start different timer the value of item get replaced with the newest one, it will display wrong item value for all the timers which are already running in that time (except for the latest one) how can I store items and show them with their corresponding timer's callback
Hope you understood my question :)
You may simply use a dict to store the items and the chat_id with an UUID as a key, then, pass the UUID to the callback function and retrieve the items from the dict.
from uuid import uuid4
items = {}
def set(update: Update, context: CallbackContext):
user_says=' '.join(context.args)
user_says=user_says.split(' ')
chat_id=update.effective_chat.id
uid = uuid4()
items[uid] = {"id": chat_id, "item": user_says[1:]}
j.run_once(callback, int(user_says[0]), context=uid)
def callback(context: telegram.ext.CallbackContext):
uid = context.job.context
chat_id = items[uid]["id"]
context.bot.send_message(chat_id=chat_id, text=f'Great you won {items[uid]["item"]}')
del items[uid]
Read more about multi-users handling in How to manage more users in a telegram bot?
IMHO Knugis answer is unnecessarily complicated. Instead of storing some data in a global dict and then passing the key to context, you might as well just pass the data directly, e.g. as tuple:
j.run_once(callback, int(user_says[0]), context=(chat_id, user_says[1:]))
and then
def callback(context: telegram.ext.CallbackContext):
uid, user_says = context.job.context
...
On a somewhat related note I'd like to point out that PTB already comes with a built-in mechanism to store user/chat-related data. See this wiki page for details.
Disclaimer: I'm currently the maintainer of python-telegram-bot.

web2py: How to execute instructions before delete using SQLFORM.smartgrid

I use SQLFORM.smartgrid to show a list of records from a table (service_types). In each row of the smartgrid there is a delete link/button to delete the record. I want to executive some code before smartgrid/web2py actually deletes the record, for example I want to know if there are child records (services table) referencing this record, and if any, flash a message telling user that record cannot be deleted. How is this done?
db.py
db.define_table('service_types',
Field('type_name', requires=[IS_NOT_EMPTY(), IS_ALPHANUMERIC()]),
format='%(type_name)s',
)
db.define_table('services',
Field('service_name',requires=[IS_NOT_EMPTY(),IS_NOT_IN_DB(db,'services.service_name')]),
Field('service_type','reference service_types',requires=IS_IN_DB(db,db.service_types.id,
'%(type_name)s',
error_message='not in table',
zero=None),
ondelete='RESTRICT',
),
Field('interest_rate','decimal(15,2)',requires=IS_DECIMAL_IN_RANGE(0,100)),
Field('max_term','integer'),
auth.signature,
format='%(service_name)s',
)
db.services._plural='Services'
db.services._singular='Service'
if db(db.service_types).count() < 1:
db.service_types.insert(type_name='Loan')
db.service_types.insert(type_name='Contribution')
db.service_types.insert(type_name='Other')
controller
def list_services():
grid = SQLFORM.smartgrid(db.services
, fields = [db.services.service_name,db.services.service_type]
)
return locals()
view
{{extend 'layout.html'}}
{{=grid}}
There are two options. First, the deletable argument can be a function that takes the Row object of a given record and returns True or False to indicate whether the record is deletable. If it returns False, the "Delete" button will not be shown for that record, nor the delete operation be allowed on the server.
def can_delete(row):
return True if [some condition involving row] else False
grid = SQLFORM.smartgrid(..., deletable=can_delete)
Second, there is an ondelete argument that takes the db Table object and the record ID. It is called right before the delete operation, so to prevent the delete, you can do a redirect within that function:
def ondelete(table, record_id):
record = table(record_id)
if [some condition]:
session.flash = 'Cannot delete this record'
redirect(URL())
grid = SQLFORM.smartgrid(..., ondelete=ondelete)
Note, if the grid is loaded via an Ajax component and its actions are therefore performed via Ajax, using redirect within the ondelete method as shown above will not work well, as the redirect will have no effect and the table row will still be deleted from the grid in the browser (even though the database record was not deleted). In that case, an alternative approach is to return a non-200 HTTP response to the browser, which will prevent the client-side Javascript from deleting the row from the table (the delete happens only on success of the Ajax request). We should also set response.flash instead of session.flash (because we are not redirecting/reloading the whole page):
def ondelete(table, record_id):
record = table(record_id)
if [some condition]:
response.flash = 'Cannot delete this record'
raise HTTP(403)
Note, both the deletable and ondelete arguments can be dictionaries with table names as keys, so you can specify different values for different tables that might be linked from the smartgrid.
Finally, notice the delete URLs look like /appname/list_services/services/delete/services/[record ID]. So, in the controller, you can determine if a delete is being requested by checking if 'delete' in request.args. In that case, request.args[-2:] represents the table name and record ID, which you can use to do any checks.
From Anthony's answer I chose the second option and came up with the following:
def ondelete_service_type(service_type_table, service_type_id):
count = db(db.services.service_type == service_type_id).count()
if count > 0:
session.flash = T("Cant delete")
#redirect(URL('default','list_service_types#'))
else:
pass
return locals()
def list_service_types():
grid = SQLFORM.smartgrid(db.service_types
, fields = [db.service_types.type_name, db.services.service_name]
, ondelete = ondelete_service_type
)
return locals()
But, if I do this...
if count > 0:
session.flash = T("Cant delete")
else:
pass
return locals()
I get this error:
And if I do this:
if count > 0:
session.flash = T("Cant delete")
redirect(URL('default','list_service_types#')) <== please take note
else:
pass
return locals()
I get the flash error message Cant delete but the record appears deleted from the list, and reappears after a page refresh with F5 (apparently because the delete was not allowed in the database, which is intended).
Which one should I fix and how?
Note
If any of these issue is resolved I can accept Anthony's answer.

In odoo 10, using the UI I cannot get my "Server action +Automated action" to work

I have the "reviewer" field available in my task, and I want to switch the reviewer with the task assignee automatically when the task is moved from the 'In progress' stage to the 'Review' stage. I have the following Python code in my server action:
picture of the code in context
def assignrev(self):
for record in self:
if record['project.task.type.stage_id.name']=='Review':
a=self.res.users.reviewer_id.name
b=self.res.users.user_id.name
record['res.users.user_id.name']=a
record['res.users.reviewer_id.name']=b
and below are links to pictures of my automated action settings:
Server action to run
"When to run" settings
Unfortunately, changing the task stage to 'Review' does not give the expected results. Any suggestion please?
Kazu
Ok I finally got the answer to this. below is a picture of the code in context for Odoo 10:
No "def" of "for record" needed: the code will not run.
I just hope this will be helpful to someone else...
Kazu
My guess is that you are incorrectly calling the fields you're trying to get.
# Instead of this
a = self.res.users.reviewer_id.name
b = self.res.users.user_id.name
record['res.users.user_id.name']=a
record['res.users.reviewer_id.name']=b
# Try this
# You don't need to update the name, you need to update the database ID reference
record['user_id'] = record.reviewer_id.id
record['reviewer_id'] = record.user_id.id
Furthermore, why don't you try using an onchange method instead?
#api.multi
def onchange_state(self):
for record in self:
if record.stage_id.name == 'Review':
record.update({
'user_id': record.reviewer_id.id,
'reviewer_id': record.user_id.id,
})
If you're still having problems, you can use ipdb to debug your code more easily by triggering set_trace in your method.
def assignrev(self):
# Triggers a break in code so that you can debug
import ipdb; ipdb.set_trace()
for record in self:
# Test line by line with the terminal to see where your problem is

Categories

Resources