For the Odoo warehouse module I have to do a check if all required fields have been filled when quality control wants to transfer the products to stock.
At this moment everything works but the location for quality control is currently hardcoded. This means that when someone is using another stock location for quality control they would have to change the code.
I have searched trough the Odoo documentation and as far as I can see for the new api I have to use self.env instead of self.pool.get. (I added the old code as a comment.)
When debugging it seems that stock.warehouse is in self.pool and not in self.env (But I guess this might just be one of those "Odoo" things).
Second thing is that I have hardcoded the current company_id "1".
I think it would be best if that could also be best if that would be a variable.
I hope someone can help me solve this.
Thanks in advance
class stock_transfer_details(models.TransientModel):
_inherit = "stock.transfer_details"
#api.one
def do_detailed_transfer(self):
res = super(stock_transfer_details, self).do_detailed_transfer()
# Check if all the required lot additional fields have been filled.
# Else raise warning.
# TODO Replace hardcoded Quality location by database reference
warehouse = self.env("stock.warehouse").search([("company_id", "=", "1")])
# self.pool.get("stock.warehouse").browse(cr, uid, item["wh_qc_stock_loc_id"], context=context)
qc_location = warehouse.wh_qc_stock_loc_id
missing_mandatory_fields = []
if self.picking_source_location_id.id == 14:
item_ids = self.mapped("item_ids")
for item in item_ids:
additional_fields = item.lot_id.mapped("lot_lot_additional_fields")
for field in additional_fields:
if field.lot_additional_fields.mandatory and not field.value:
if item.lot_id.name not in missing_mandatory_fields:
missing_mandatory_fields.append(item.lot_id.name)
if missing_mandatory_fields:
error_message = "All required fields for the serial numbers must be filled! \n"
error_message += "Serial numbers: \n"
for item in missing_mandatory_fields:
error_message += item + "\n"
raise exceptions.Warning(error_message)
return res
Try the next code:
for item in self.pack_move_items:
warehouse = self.env['stock.warehouse'].browse(item.wh_qc_stock_loc_id.mapped('id'))
After a lot of research I have been able to figure out how this works.
This is the code I have used to get the current warehouse quality location:
wh_qa_location = (self.env["stock.warehouse"].search([("partner_id.id", "=", self.create_uid.company_id.id)])).wh_qc_stock_loc_id
First I will look for the current warehouse the employee is in. Once that is found I will simply get the value of the wh_qc_stock_loc_id
Related
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.
I'm trying to get a field from openERPs mail_message model using python code which is executed in a server action (so its not a module where I can debug! I cannot even print in this state) (when a new eMail is being fetched) but I am unable to get anything useful from it.
Basicly when someone is throwing me a email, a new Task is created by openERP. But the newely created ticket is not connected to the user which send me the mail.
When a new email is fetched, this server action gets executed.
In a table called mail_message you can then find the email (+ author_id, + email, + res_id (which is the id of the created Task), therefore I'd like to fetch the author_id from that table.
(A query would look like this:
SELECT author_id FROM mail_message WHERE type = 'email' AND res_id = '<Task.id>')
This is my current code
#Initialize object. That one points to the mail_message model.
mailMessage_obj = self.pool.get('mail.message')
#Created Id in project_task
myId = object.id
#browse whole object with that id
#message = mailMessage_obj.browse(cr,uid,[myId])
#Select field where
messageIds = mailMessage_obj.search(cr,uid,[('type','=','email'),('res_id','=',myId)],context=context)
if messageIds:
#messageRecord = mailMessage_obj.browse(cr,uid,[myId],context=context)
#object.write({'partner_id':messageRecord.author_id.id})
res = mailMessage_obj.read(messageIds, ['author_id'])
partnerId = res[0]
#Author id
#partnerId = message[0]['author_id']
#partnerId = message.author_id
#res = [(r['id'], r['author_id']) for r in messageRecord]
#partnerId = res
#partnerId = 259866
object.write({'partner_id':partnerId})
I dont know how to get my hands on the author_id properly. If I hardcode a ID and let it write to the database (last two lines) It'll work just fine, but I cant hardcode a users id. ;)
Could someone explain to me how its done correctly?
I dont know whether I should use .browse or .read or something else..
I think you have an error on you python method.
you wrote :
res = mailMessage_obj.read(messageIds, ['author_id'])
partnerId = res[0]
But read() method returns here a list of dict (because messageIds is a list). Then, you have not specified the field you wanted to retrieve from res variable, and finally, as author_id is a many2one, it returns something like this : (2, 'myusers').
You should have done :
res = mailMessage_obj.read(cr, uid, messageIds, ['author_id'])
partnerId = res[0]['author_id'][0]
Hope i helped you,
Greatings
My experience is with HTML/CSS/PHP but I have recently started working with python. I find myself stuck and hope someone can spot what I am doing incorrectly. The program I am working on is stalling while retrieving some variables from an xml document. I have narrowed it down to this section:
(Made up the variables)
def add_ingredients_for_selected_recipes(self, root):
recipes = ["Beef Stew", "Tuna Casserole", "Spaghetti", "Chocolate Cake"]
guest = self.settings.get('guest')
allergies = {'nuts': ["guest1", "guest2", "guest3"], 'seafood': ["guest5", "guest6"]}
for recipe in recipes:
# Add necessary ingredients for user desired recipes to our list
if self.settings.get("recipe_" + recipe):
self.queue_event('info', 'Selecting ingredients for "%s" recipe.' % recipe)
for child in root.iter(recipe):
for ing in child.iter('ingredient'):
contains = ing.attrib.get('allergy')
if contains is None:
self.queue_event('debug', 'Adding ingredient:%s' % (ing.text))
self.ingredients.append(ing.text)
elif contains in allergies and guest in allergies[contains]:
self.queue_event('debug', 'Adding ingredient:%s contains %s' % (ing.text, contains))
self.ingredients.append(ing.text)
else:
self.queue_event('debug', 'Skipping ingredient:%s contains %s' % (ing.text, contains))
Here is what I grep'd from the trace: https://dl.dropboxusercontent.com/u/60521097/fromtrace
One more thing that's relevant is if I remove the portion that checks for the attribute it works fine. So I know where the problem is but I am not seeing it. Thanks in advance for any suggestions.
Ok I figured out the problem. I previously defined which "recipes" were available as option for each of the "guests". When the main loop (for recipe in recipes:) gets to a recipe that is not defined as an available option to the "allergy" in "allergies" it shows False and the loop can't continue. I fixed this by defining the "recipes" variable based on selected "guest" like this:
self.recipes_by_guest = {"guest1": ["Beef Stew", "Tuna Casserole", "Spaghetti"],
"guest2": ["Beef Stew", "Spaghetti"],...etc}
guest = self.settings.get("guest")
recipes = self.recipes_by_guest[guest]
allergies = {'nuts': ["guest1", "guest2", "guest3"], 'seafood': ["guest5", "guest6"]}
for recipe in recipes: etc...
Thanks to everyone who had a look and commented suggestions!
I have a class that is taking in an Id and trying to update the variable current_account but when I print out the details of the current_account it hasn't updated.
Anyone got any ideas for this? New to python so might be doing something stupid that I can't see.
class UserData:
def __init__(self, db_conn=None):
if None == db_conn:
raise Exception("DB Connection Required.")
self.db = db_conn
self.set_my_account()
self.set_accounts()
self.set_current_account()
def set_current_account(self, account_id=None):
print account_id
if None == account_id:
self.current_account = self.my_account
else:
if len(self.accounts) > 0:
for account in self.accounts:
if account['_id'] == account_id:
self.current_account = account
print self.current_account['_id']
else:
raise Exception("No accounts available.")
Assume that set_my_account() gets a dictionary of account data and that set_accounts() get a list of dictionaries of account data.
So when I do the following:
user_data = UserData(db_conn=db_conn)
user_data.set_current_account(account_id=account_id)
Where db_conn is a valid database connection and account_id is a valid account id.
I get the following out of the above two lines.
None
518a310356c02c0756764b4e
512754cfc1f3d16c25c350b7
So the None value is from the declaration of the class and then the next two are from the call to set_current_account(). The first id value is what I'm trying to set. The second id value is what was already set from the class __init__() method.
There were a lot of redundancies an un-Pythonic constructions. I cleaned up the code to help me understand what you trying to do.
class UserData(object):
def __init__(self, db_conn):
self.db = db_conn
self.set_my_account()
self.set_accounts()
self.set_current_account()
def set_current_account(self, account_id=None):
print account_id
if account_id is None:
self.current_account = self.my_account
else:
if not self.accounts:
raise Exception("No accounts available.")
for account in self.accounts:
if account['_id'] == account_id:
self.current_account = account
print self.current_account['_id']
user_data = UserData(db_conn)
user_data.set_current_account(account_id)
You used default arguments (db_conn=None) when a call without an explicit argument is invalid. Yes, you can now call __init__(None) but you could also call __init__('Nalum'); you can't protect against everything.
By moving the "No accounts" exception the block fast-fails and you save one level of indention.
The call UserData(db_conn=db_conn) is valid but unecessarily repetitive.
Unfortunately, I still can't figure out what you are trying to accomplish and this is perhaps the largest flaw. Variable names are terribly important for help the reader (which may be the future you) make sense of code. current_account, my_account, account_id and current_account['_id'] so obscure the intention that you should really consider more distinct, informative names.
Figured out what it was.
The data was being changed else where in the code base. It is now working as expected.
Thanks guys for pointing out the Python centric things that I was doing wrong, good to get it.
I am creating a list from some of the model data but I am not doing it correctly, it works but when I refresh the page in the broswer reportResults just gets added to. I hoped it would get garbage collected between requests but obviously I am doing something wrong, any ideas anyone??
Thanks,
Ewan
reportResults = [] #the list that doesn't get collected
def addReportResult(fix,description):
fix.description = description
reportResults.append(fix)
def unitHistory(request,unitid, syear, smonth, sday, shour, fyear, fmonth, fday, fhour, type=None):
waypoints = Fixes.objects.filter(name=(unitid))
waypoints = waypoints.filter(gpstime__range=(awareStartTime, awareEndTime)).order_by('gpstime')[:1000]
if waypoints:
for index in range(len(waypoints)):
...do stuff here selecting some waypoints and generating "description" text
addReportResult(waypointsindex,description) ##append the list with this, adding a text description
return render_to_response('unitHistory.html', {'fixes': reportResults})
You are reusing the same list each time, to fix it you need to restructure your code to create a new list on every request. This can be done in multiple ways and this is one such way:
def addReportResult(reportResults, fix,description):
fix.description = description
reportResults.append(fix)
def unitHistory(request,unitid, syear, smonth, sday, shour, fyear, fmonth, fday, fhour, type=None):
reportResults = [] # Here we create our local list that is recreated each request.
waypoints = Fixes.objects.filter(name=(unitid))
waypoints = waypoints.filter(gpstime__range=(awareStartTime, awareEndTime)).order_by('gpstime')[:1000]
if waypoints:
for index in range(len(waypoints)):
# Do processing
addReportResult(reportResults, waypointsindex, description)
# We pass the list to the function so it can use it.
return render_to_response('unitHistory.html', {'fixes': reportResults})
If the addReportResult stays small you could also inline the description attribute set by removing the call to addReportResult altogether and doing the waypointsindex.description = description at the same position.
Just so you're aware of the life cycle of requests, mod_wsgi will keep a process open to service multiple requests. That process gets recycled every so often but it is definitely not bound to a single request as you've assumed.
That means you need a local list. I would suggest moving the addReportResult function contents directly in-line, but that's not a great idea if it needs to be reusable or if the function is too long. Instead I'd make that function return the item, and you can collect the results locally.
def create_report(fix, description): # I've changed the name to snake_casing
fix.description = description
return fix
def unit_history(request,unitid, syear, smonth, sday, shour, fyear, fmonth, fday, fhour, type=None):
reports = []
waypoints = Fixes.objects.filter(name=(unitid))
waypoints = waypoints.filter(gpstime__range=(awareStartTime, awareEndTime)).order_by('gpstime')[:1000]
if waypoints:
for index in range(len(waypoints)):
report = create_report(waypointsindex, description)
reports.append(report)
return render_to_response('unitHistory.html', {'fixes': reportResults})