I have two binary fields. In the sale order line tree view, I have a button to open my wizard. I chose and save file and it's perfectly saved in my label_file field of the selected sale order line.
The problem is that when I open the wizard I want to see it as a saved file, but it's not generated and only bytes are in the path.
first
Class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
label_file = fields.Binary('Saved Label')
Saved in SaleOrderLine
second
class OrderLineLabel(models.TransientModel):
_name = 'order.line.label'
label_file_show = fields.Binary('Label file')
#api.multi
def write_label_vals(self):
self.ensure_one()
sale_order_line = self.env['sale.order.line'].browse(self.env.context.get('active_ids'))
vals = {
'label_file': self.label_file,
}
sale_order_line.write(vals)
#api.model
def default_get(self, fields):
res = super(OrderLineLabel, self).default_get(fields)
order_line_id = self.env['sale.order.line'].browse(self.env.context.get('active_ids'))
status, headers, content = binary_content(model='sale.order.line', field='label_file', id=order_line_id.id,filename='test',download=True)
#tried like this
res['label_file_show'] = content
#or just like this
res['label_file_show'] = order_line_id.label_file
return res
this is how it looks when I open the wizard.
You need to add filename to the binary fields.
Declare a char field to hold the name:
label_file_name = fields.Char()
And use filename attribute to specify the file name to the binary field:
<field name="label_file_name" invisible="True"/>
<field name="label_file_show" filename="label_file_name"/>
In write_label_vals add one more line to save also the file name.
order_line_id.label_file_name = self.label_file_name
# vals = {'label_file': self.label_file, 'label_file_name': self.label_file_name}
Set the value of the filename in the wizard default_get method :
res['label_file_name'] = order_line_id.label_file_name
Related
I'm trying to create an Odoo module which override Sales order confirm button. I followed the information I found, and I created the following code, but it doesn't work.
from odoo import models, fields, api
import logging
_logger = logging.getLogger(__name__)
class saleoverride(models.Model):
_name = 'saleoverride.saleoverride'
_description = 'saleoverride'
_inherit = 'sale.order'
name = fields.Char()
# value = fields.Integer()
# value2 = fields.Float(compute="_value_pc", store=True)
description = fields.Text()
transaction_ids = fields.Many2many('payment.transaction', 'saleoverride_transaction_rel', 'saleoverride_id', 'transaction_id',
string='Transactions', copy=False, readonly=True)
tag_ids = fields.Many2many('crm.tag', 'saleoverride_tag_rel', 'saleoverride_id', 'tag_id', string='Tags')
#api.model
def action_confirm(self):
res = super(SaleOrder, self).action_confirm()
_logger.info("saleoverride_action_confirm")
_logger.info(self)
return res
I tried to search the message in the log file, but can't find anything. Can someone help me?
Thanks!
The problem is that you are not extending the sale.order model, but creating a new one (saleoverride.saleoverride) based on sale.order.
Check odoo docs: Inheritance and extension
from odoo import models, fields, api
import logging
_logger = logging.getLogger(__name__)
class saleoverride(models.Model):
_description = 'saleoverride'
_inherit = 'sale.order'
name = fields.Char()
# value = fields.Integer()
# value2 = fields.Float(compute="_value_pc", store=True)
description = fields.Text()
transaction_ids = fields.Many2many('payment.transaction', 'saleoverride_transaction_rel', 'saleoverride_id', 'transaction_id',
string='Transactions', copy=False, readonly=True)
tag_ids = fields.Many2many('crm.tag', 'saleoverride_tag_rel', 'saleoverride_id', 'tag_id', string='Tags')
def action_confirm(self):
res = super(SaleOrder, self).action_confirm()
_logger.info("saleoverride_action_confirm")
_logger.info(self)
return res
Removing _name = 'saleoverride.saleoverride' from your class adds new features to sale.order.
Also remove the decorator since the original function doesn't have one.
When you want to override the code in addons, all you can do is just inheriting it. So, you can't define name while overriding the existing code.
Remove:
_name ='saleoverride.saleoverride'
in account.analytic.lines i have field number. every project have the numer field.
So y goal is to auto fill project_id field that is in lines when i type in number field in lines.
class AccountAnalyticLine(models.Model):
_inherit = 'account.analytic.line'
number = fields.Integer(related='project_id.number',string='Project Number')
#api.onchange('number')
def get_project_id(self):
v={}
if self.number:
project = self.env['project.project']
if project.project_id.id:
v['project'] = project.project_id and project.project_id.id or False
return {'value': v}
Try this:
#api.onchange('number')
def get_project_id(self):
# in new api no need for return and you can
# affect change directly to self
project = false
if self.number:
project_obj = self.env['project.project']
# now search for project that have the same number
porject = project_obj.search([('number', '=', self.number)], limit=1)
self.project_id = project
# if you want to show a warning when user fillup
# the number field and there is no project found
if not self.project_id and self.number:
# number is not empty but there is no project with this number
return {'warning': {
'title': _("Project Warning"),
'message': _('No project found with this number : %s ') % self.number
}}
don't forget to import the translation toos:
# 10.0
from odoo.tools.translate import _
Is it possible to have two classes
class SimulationDigitizer(HasTraits):
width = Int(1920)
height = Int(1080)
name = 'Simulation'
class FileDigitizer(HasTraits):
Filename = File
name = 'File'
and another class 'Digitizer' having an attribute (or trait) UserDigitizer whose edition dialog will propose a drop-down list with 'Simulation' and 'File' and, depending on the choice, the instance edition of either FileDigitizer or SimulationDigitizer and get the result in UserDigitizer ?
Thanks
Thanks Jonathan, you are right, the problem is to select the appropriate subcomponent among many possible.
I ended with the following code, I guess it can be improved in many ways. I am new both to Python and Traits.
# -*- coding: utf-8 -*-
from traits.api import HasTraits, Int, File, Enum, Instance
from traitsui.api import View, Item, Handler, HGroup, InstanceEditor
class UserComponent ( HasTraits ):
""" An empty class from which all user component classes are
derived.
"""
pass
class SimulationDigitizer(UserComponent):
width = Int(1920)
height = Int(1080)
nature = 'Simulation'
class FileDigitizer(UserComponent):
filename = File
nature = 'Reading a file'
UserDigitizers = [FileDigitizer, SimulationDigitizer]
class UserComponentHandler(Handler):
def __init__(self,_user_components_dict):
Handler.__init__(self)
self._user_components_dict = _user_components_dict
def object_user_component_nature_changed ( self, info ):
# Find new UserComponent class from string in user_component_nature
new_user_component = self._user_components_dict[info.object.user_component_nature]
# If different, change user_component value
if info.object.user_component is not new_user_component:
info.object.user_component = new_user_component
class Digitizer(HasTraits):
user_component_nature = Enum([x().nature for x in UserDigitizers])
_user_file_digitizer = FileDigitizer()
_user_simulation_digitizer = SimulationDigitizer()
# Dictionary with keys = nature and values = user digitizers
_user_digitizers_dict = {x.nature: x for x in [_user_file_digitizer,_user_simulation_digitizer]}
user_component = Enum(_user_file_digitizer,_user_simulation_digitizer)
view = View(HGroup(Item('user_component_nature',
label = 'Nature'),
Item('user_component',
show_label = False,
editor = InstanceEditor(label = 'Edit',
kind = 'modal'))),
handler = UserComponentHandler(_user_digitizers_dict))
d = Digitizer()
if __name__ == '__main__':
d.configure_traits()
I would like to customize reports filename.
For instance, when I download an invoice, I will have something like 'Invoice.pdf' as filename. What I would like is something like 'invoice_number.pdf' but I don't know how to use dynamic file name ?
I found a way for 7.0 and current trunk:
For a quick fix, take a look at the Reports class in:
openerp-7.0\web\addons\web\controllers\main.py
The report name is set in a variable named file_name:
file_name = '%s.%s' % (file_name, report_struct['format'])
Find this line and insert this part before it:
'''
YP: Added proc. to create reports with real object names (the object must have a field named "name"):
eg: "PO00006.pdf" instead of "Request For Quotation.pdf" for a single object report
"PO00006-PO00002.pdf" instead of "Request For Quotation.pdf" for a multiple objects report
'''
# Try to get current object model and their ids from context
if action.has_key('context'):
action_context = action.get('context',{})
if action_context.has_key('active_model') and action_context.has_key('active_id'):
action_active_model = action_context.get('active_model','')
action_active_ids = action_context.get('active_ids', [])
if action_active_model and action_active_ids:
# Use built-in ORM method to get data from DB
m = req.session.model(action_active_model)
r = m.read(action_active_ids, False, context)
# Parse result to create a better filename
for i, item in enumerate(r):
if item.has_key('name'):
if i == 0:
file_name = ('%s') % (item['name'])
else:
file_name = ('%s-%s') % (file_name, item['name'])
For a quick fix, take a look at the Reports class in:
openerp-6.1\web\addons\web\controllers\main.py
The report name is set in a variable named header.
For my needs, I only want the report to be named by the object name (that's suitable in 99% of cases) so I added a new variable named report_name:
report_name = action['report_name']
if action.has_key('context'):
action_context = action.get('context',{})
if action_context.has_key('name'):
report_name = action_context['name']
return req.make_response(report,
headers=[
('Content-Disposition', 'attachment; filename="%s.%s"' % (report_name, report_struct['format'])),
('Content-Type', report_mimetype),
('Content-Length', len(report))],
cookies={'fileToken': int(token)})
I found a solution to set the default filename like the attach file name, using the expression in the attachment attribute in your xml report definition.
Found and modify this file: openerp7/openerp-web/addons/web/controllers/main.py.
Search this sentence:
file_name = '%s.%s' % (file_name, report_struct['format'])
Then add this code before:
'''Code added by Raul Paz to set the default file_name like the attach_name
The attach_name mach with the attachment expresion from your report_xml
<report
id="report_webkit.YourModule_report_id"
model="YourModule.model"
name="Your_report_name"
file="YOUR_MODULE/report/YOUR_MAKO_FILE.mako"
string="Your Text in the print button"
auto="False"
report_type="webkit"
attachment="'Valid_filename_expresion"
usage="default"
/>
And
to modify existing report sale_report.xml to manage the name:
create in your module a file : sale_report.xml
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="sale.report_sale_order" model="ir.actions.report.xml">
<field name="attachment">(object.name or 'Sale Order')</field>
<field name="attachment_use" eval="True"/>
</record>
</data>
</openerp>
'''
try:
if action.get('attachment_use',False) and action['attachment']:
model = context['active_model']
cr = openerp.pooler.get_db(req.session._db).cursor()
uid = context['uid']
ids = context['active_ids']
objects=openerp.pooler.get_pool(req.session._db).get(model).browse(cr,uid,ids,context=context)
file_name=[eval(action['attachment'],{'object':x, 'time':time}) for x in objects][0]
except:
pass
#end code added
Good luky
I defined some content types based on Plone Dexterity, I want any content type has his own ID. So I used the zope.schema.Id .
class IArticle(form.Schema, IImageScaleTraversable):
"""
Article
"""
form.model("models/article.xml")
id = schema.Id(
title=_(u"Identifier"),
description=_(u"The unique identifier of object."),
required=False
)
When I create a new article object, it works fine, but when I want to modify the Id of an existed article, It always show me error:
{'args': (<MultiContentTreeWidget 'form.widgets.IRelatedItems.relatedItems'>,),
'context': <Article at /dev/articles/this-is-another-articles>,
'default': <object object at 0x100266b20>,
'loop': {},
'nothing': None,
'options': {},
'repeat': {},
'request': <HTTPRequest, URL=http://localhost:8083/dev/articles/this-is-another-article/##edit>,
'template': <zope.browserpage.viewpagetemplatefile.ViewPageTemplateFile object at 0x10588ecd0>,
'view': <MultiContentTreeWidget 'form.widgets.IRelatedItems.relatedItems'>,
'views': <zope.browserpage.viewpagetemplatefile.ViewMapper object at 0x10b309f50>}
.......
.......
.......
Module zope.tales.expressions, line 217, in __call__
Module zope.tales.expressions, line 211, in _eval
Module plone.formwidget.contenttree.widget, line 147, in render_tree
Module plone.app.layout.navigation.navtree, line 186, in buildFolderTree
Module Products.CMFPlone.CatalogTool, line 427, in searchResults
Module Products.ZCatalog.ZCatalog, line 604, in searchResults
Module Products.ZCatalog.Catalog, line 907, in searchResults
Module Products.ZCatalog.Catalog, line 656, in search
Module Products.ZCatalog.Catalog, line 676, in sortResults
Module plone.app.folder.nogopip, line 104, in documentToKeyMap
Module plone.folder.ordered, line 102, in getObjectPosition
Module plone.folder.default, line 130, in getObjectPosition
ValueError: No object with id "this-is-another-articles" exists.
I don't think this is a use-case supported in the stock Edit form. You can rename items from the folder contents tab of the folder containing the item. Should you want to do this from the edit form:
You may need to use a custom edit form (subclass the existing form);
name your id field something other than 'id', perhaps 'target_id' or 'shortname'
have the __call__() method of the form update / save the form, and only on success rename the item within its container.
__call__() should redirect to the new item's URL after edit that triggers a name change.
Maybe something like (completely untested):
from plone.dexterity.browser.edit import DefaultEditForm as BaseForm
from Products.statusmessages.interfaces import IStatusMessages
class RenameEnabledEditForm(BaseForm):
def __call__(self, *args, **kwargs):
self.update(*args, **kwargs)
data, errors = self.extractData()
existing, newid = self.context.getId(), data.get('target_id')
if not errors and existing != newid:
parent_folder = self.context.__parent__
if newid in parent_folder.objectIds():
raise RuntimeError('Duplicate object id') # collision!
parent_folder.manage_renameObject(existing, newid)
newurl = parent_folder[newid].absolute_url()
IStatusMessages(self.context).addStatusMessage(
u'Renamed item from "%s" to "%s" in folder' % (existing, newid),
type='info',
)
self.request.response.redirect(newurl)
else:
return self.index(*args, **kwargs)
I've achieved this with a custom add form so that only the add form includes id-field and the edit form doesn't (renaming is done in the old way through the rename-action or from the folder contents view).
Some boilerblate:
from five import grok
from zope import schema
from plone.directives import form
from plone.directives import dexterity
class IMyContent(form.Schema):
"""Schema of MyContent"""
class NotValidId(schema.ValidationError):
"""Id is not valid."""
def isValidId(value):
"""Validates id for new MyContent objects"""
# raise NotValidId(value)
return True
class IMyContentAddForm(IMyContent):
"""Schema of MyContent Add Form"""
form.order_before(id="*")
id = schema.ASCIILine(
title=u"Id",
constraint=isValidId,
required=True
)
class MyContentAddForm(dexterity.AddForm):
"""Add Form for new MyContent objects"""
grok.name("my.app.mycontent")
label = u"Add New"
schema = IMyContentAddForm
def create(self, data):
"""Creates a new object with the given (validated) id"""
id = data.pop("id")
obj = super(MyContentAddForm, self).create(data)
obj.id = id
return obj
There's also an open issue and preliminary work for it to make Dexterity-types support the standard "Show 'Short Name' on content?"-feature.
for use with dexterity content types, i adapted #spdupton's answer (correcting a typo in the import) and changing self.context to self.request
from Products.statusmessages.interfaces import IStatusMessage
class EditForm(dexterity.EditForm):
grok.context(IPics)
def applyChanges(self, data):
existing = self.context.getId()
data, errors = self.extractData()
super(EditForm, self).applyChanges(data)
newid = str(data.get('picName'))
print 'existing, newid =', existing, newid
if not errors and existing != newid:
parent_folder = self.context.__parent__
if newid in parent_folder.objectIds():
raise RuntimeError('Duplicate object id') # collision!
parent_folder.manage_renameObject(existing, newid)
newurl = parent_folder[newid].absolute_url()
IStatusMessage(self.request).addStatusMessage(
u'Renamed item from "%s" to "%s"' % (existing, newid),
type='info',
)
self.request.response.redirect(newurl)
#