media ok + test
This commit is contained in:
parent
0100653643
commit
2933a217e2
13 changed files with 565 additions and 33 deletions
3
.hgignore
Normal file
3
.hgignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
syntax: glob
|
||||
__pycache__/*
|
||||
locale/convert_de2en.py
|
34
__init__.py
34
__init__.py
|
@ -4,39 +4,9 @@
|
|||
# full copyright notices and license terms.
|
||||
|
||||
from trytond.pool import Pool
|
||||
from .book import Book
|
||||
from .types import Type
|
||||
from .line import Line, LineContext
|
||||
from .splitline import SplitLine
|
||||
from .wizard_openline import OpenCashBook, OpenCashBookStart, OpenCashBookTree
|
||||
from .wizard_runreport import RunCbReport, RunCbReportStart
|
||||
from .wizard_booking import EnterBookingWizard, EnterBookingStart
|
||||
from .configuration import Configuration, UserConfiguration
|
||||
from .category import Category
|
||||
from .reconciliation import Reconciliation
|
||||
from .cbreport import ReconciliationReport
|
||||
from .line import Line
|
||||
|
||||
def register():
|
||||
Pool.register(
|
||||
Configuration,
|
||||
UserConfiguration,
|
||||
Type,
|
||||
Category,
|
||||
Book,
|
||||
LineContext,
|
||||
Line,
|
||||
SplitLine,
|
||||
Reconciliation,
|
||||
OpenCashBookStart,
|
||||
RunCbReportStart,
|
||||
EnterBookingStart,
|
||||
module='cashbook', type_='model')
|
||||
Pool.register(
|
||||
ReconciliationReport,
|
||||
module='cashbook', type_='report')
|
||||
Pool.register(
|
||||
OpenCashBook,
|
||||
OpenCashBookTree,
|
||||
RunCbReport,
|
||||
EnterBookingWizard,
|
||||
module='cashbook', type_='wizard')
|
||||
module='cashbook_media', type_='model')
|
||||
|
|
165
line.py
Normal file
165
line.py
Normal file
|
@ -0,0 +1,165 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the cashbook-module from m-ds for Tryton.
|
||||
# The COPYRIGHT file at the top level of this repository contains the
|
||||
# full copyright notices and license terms.
|
||||
|
||||
import mimetypes, magic
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
from trytond.model import fields
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.config import config
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.exceptions import UserError
|
||||
from trytond.i18n import gettext
|
||||
from trytond.pyson import Eval, Bool
|
||||
from trytond.modules.cashbook.line import STATES, DEPENDS
|
||||
|
||||
|
||||
store_prefix = config.get('cashbook', 'store_prefix', default='cashbook')
|
||||
image_limit = config.get('cashbook', 'image_max_pixel', default='2000')
|
||||
try :
|
||||
image_limit = int(image_limit)
|
||||
if image_limit < 100:
|
||||
image_limit = 100
|
||||
if image_limit > 10000:
|
||||
image_limit = 10000
|
||||
except :
|
||||
image_limit = 2000
|
||||
|
||||
|
||||
class Line(metaclass=PoolMeta):
|
||||
__name__ = 'cashbook.line'
|
||||
|
||||
media = fields.Binary(string='Image of PDF', filename='media_name',
|
||||
file_id='media_id', store_prefix=store_prefix,
|
||||
states=STATES, depends=DEPENDS)
|
||||
media_name = fields.Char(string='File name',
|
||||
states={
|
||||
'required': Bool(Eval('media')),
|
||||
'readonly': STATES['readonly'],
|
||||
}, depends=DEPENDS)
|
||||
media_id = fields.Char(string='File ID', readonly=True)
|
||||
media_mime = fields.Char(string='MIME', readonly=True)
|
||||
media_size = fields.Integer(string='File size', readonly=True)
|
||||
|
||||
@classmethod
|
||||
def _identify_file(cls, data, mime=True):
|
||||
""" get file-type
|
||||
"""
|
||||
return magic.from_buffer(data, mime=mime)
|
||||
|
||||
@classmethod
|
||||
def _hr_file_size(cls, num, suffix="B"):
|
||||
"""
|
||||
"""
|
||||
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
|
||||
if abs(num) < 1024.0:
|
||||
return f"{num:3.1f}{unit}{suffix}"
|
||||
num /= 1024.0
|
||||
return f"{num:.1f}Yi{suffix}"
|
||||
|
||||
@classmethod
|
||||
def resize_image_file(cls, image_data):
|
||||
""" shrink image 'image_limit' pixel if its bigger
|
||||
"""
|
||||
image_data2 = None
|
||||
with BytesIO(image_data) as fhdl:
|
||||
image = Image.open(fhdl, 'r')
|
||||
|
||||
(width, height) = image.size
|
||||
if (width > image_limit) or (height > image_limit):
|
||||
|
||||
if width > height:
|
||||
new_size = (image_limit, int(height * image_limit / width))
|
||||
else :
|
||||
new_size = (int(width * image_limit / height), image_limit)
|
||||
|
||||
# resize - fit in (image_limit x image_limit)
|
||||
img2 = image.resize(new_size, Image.LANCZOS)
|
||||
with BytesIO() as fhdl2:
|
||||
img2.save(fhdl2, 'JPEG', optimize=True, quality=80)
|
||||
fhdl2.seek(0)
|
||||
image_data2 = fhdl2.read()
|
||||
del img2
|
||||
del image
|
||||
|
||||
return image_data2
|
||||
|
||||
@classmethod
|
||||
def get_media_info(cls, values):
|
||||
""" get mime-type, update file-name
|
||||
"""
|
||||
if len(values['media'] or '') < 100:
|
||||
values['media'] = None
|
||||
values['media_mime'] = None
|
||||
values['media_size'] = None
|
||||
values['media_name'] = None
|
||||
else :
|
||||
values['media_mime'] = cls._identify_file(values['media'][:1024])
|
||||
|
||||
# if its a image, resize it to fit in (image_limit x image_limit) pixel
|
||||
if values['media_mime'].startswith('image'):
|
||||
new_image = cls.resize_image_file(values['media'])
|
||||
if new_image is not None:
|
||||
values['media'] = new_image
|
||||
values['media_mime'] = cls._identify_file(values['media'][:1024])
|
||||
|
||||
values['media_size'] = len(values['media'])
|
||||
file_ext = mimetypes.guess_extension(values['media_mime'])
|
||||
if 'media_name' in values.keys():
|
||||
if not values['media_name'].endswith(file_ext):
|
||||
# cut extension
|
||||
if values['media_name'][-4] == '.':
|
||||
values['media_name'] = values['media_name'][:-4]
|
||||
values['media_name'] = values['media_name'] + file_ext
|
||||
return values
|
||||
|
||||
@classmethod
|
||||
def validate(cls, lines):
|
||||
""" deny invalid mime-types, file-sizes etc.
|
||||
"""
|
||||
super(Line, cls).validate(lines)
|
||||
|
||||
for line in lines:
|
||||
if line.media is not None:
|
||||
if line.media_size > 1024*1024*5:
|
||||
raise UserError(gettext(
|
||||
'cashbook_media.msg_file_too_big',
|
||||
recname = line.rec_name,
|
||||
))
|
||||
if not line.media_mime in ['application/pdf',
|
||||
'image/png', 'image/jpg', 'image/jpeg']:
|
||||
raise UserError(gettext(
|
||||
'cashbook_media.msg_file_invalid_mime',
|
||||
recname = line.rec_name,
|
||||
fmime = line.media_mime,
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
""" add media-info
|
||||
"""
|
||||
vlist = [x.copy() for x in vlist]
|
||||
for values in vlist:
|
||||
if 'media' in values.keys():
|
||||
values.update(cls.get_media_info(values))
|
||||
return super(Line, cls).create(vlist)
|
||||
|
||||
@classmethod
|
||||
def write(cls, *args):
|
||||
""" update media-info
|
||||
"""
|
||||
actions = iter(args)
|
||||
to_write = []
|
||||
for records, values in zip(actions, actions):
|
||||
if 'media' in values.keys():
|
||||
values.update(cls.get_media_info(values))
|
||||
|
||||
to_write.extend([
|
||||
records,
|
||||
values,
|
||||
])
|
||||
super(Line, cls).write(*to_write)
|
||||
|
||||
# end Line
|
15
line.xml
Normal file
15
line.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the cashbook-module from m-ds for Tryton.
|
||||
The COPYRIGHT file at the top level of this repository contains the
|
||||
full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="line_view_form">
|
||||
<field name="model">cashbook.line</field>
|
||||
<field name="inherit" ref="cashbook.line_view_form"/>
|
||||
<field name="name">line_form</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
44
locale/de.po
Normal file
44
locale/de.po
Normal file
|
@ -0,0 +1,44 @@
|
|||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
|
||||
##############
|
||||
# ir.message #
|
||||
##############
|
||||
msgctxt "model:ir.message,text:msg_file_too_big"
|
||||
msgid "The file size of the record '%(recname)s' exceeded the maximum value of 5MB."
|
||||
msgstr "Die Dateigröße des Datensatzes '%(recname)s' überschreitete den maximalen Wert von 5MB."
|
||||
|
||||
msgctxt "model:ir.message,text:msg_file_invalid_mime"
|
||||
msgid "The file type '%(fmime)s' of the record '%(recname)s' is not allowed. (allowed: PNG, JPG, PDF)"
|
||||
msgstr "Der Dateityp '%(fmime)s' des Datensatzes '%(recname)s' ist nicht zugelassen. (erlaubt: PNG, JPG, PDF)"
|
||||
|
||||
|
||||
#################
|
||||
# cashbook.line #
|
||||
#################
|
||||
msgctxt "view:cashbook.line:"
|
||||
msgid "Image/PDF"
|
||||
msgstr "Bild/PDF"
|
||||
|
||||
msgctxt "field:cashbook.line,media:"
|
||||
msgid "Image of PDF"
|
||||
msgstr "Bild oder PDF"
|
||||
|
||||
msgctxt "field:cashbook.line,media_name:"
|
||||
msgid "File name"
|
||||
msgstr "Dateiname"
|
||||
|
||||
msgctxt "field:cashbook.line,media_id:"
|
||||
msgid "File ID"
|
||||
msgstr "Datei-ID"
|
||||
|
||||
msgctxt "field:cashbook.line,media_mime:"
|
||||
msgid "MIME"
|
||||
msgstr "MIME"
|
||||
|
||||
msgctxt "field:cashbook.line,media_size:"
|
||||
msgid "File size"
|
||||
msgstr "Dateigröße"
|
||||
|
36
locale/en.po
Normal file
36
locale/en.po
Normal file
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "model:ir.message,text:msg_file_too_big"
|
||||
msgid "The file size of the record '%(recname)s' exceeded the maximum value of 5MB."
|
||||
msgstr "The file size of the record '%(recname)s' exceeded the maximum value of 5MB."
|
||||
|
||||
msgctxt "model:ir.message,text:msg_file_invalid_mime"
|
||||
msgid "The file type '%(fmime)s' of the record '%(recname)s' is not allowed. (allowed: PNG, JPG, PDF)"
|
||||
msgstr "The file type '%(fmime)s' of the record '%(recname)s' is not allowed. (allowed: PNG, JPG, PDF)"
|
||||
|
||||
msgctxt "view:cashbook.line:"
|
||||
msgid "Image/PDF"
|
||||
msgstr "Image/PDF"
|
||||
|
||||
msgctxt "field:cashbook.line,media:"
|
||||
msgid "Image of PDF"
|
||||
msgstr "Image of PDF"
|
||||
|
||||
msgctxt "field:cashbook.line,media_name:"
|
||||
msgid "File name"
|
||||
msgstr "File name"
|
||||
|
||||
msgctxt "field:cashbook.line,media_id:"
|
||||
msgid "File ID"
|
||||
msgstr "File ID"
|
||||
|
||||
msgctxt "field:cashbook.line,media_mime:"
|
||||
msgid "MIME"
|
||||
msgstr "MIME"
|
||||
|
||||
msgctxt "field:cashbook.line,media_size:"
|
||||
msgid "File size"
|
||||
msgstr "File size"
|
||||
|
16
message.xml
Normal file
16
message.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the cashbook-module from m-ds for Tryton.
|
||||
The COPYRIGHT file at the top level of this repository contains the
|
||||
full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<record model="ir.message" id="msg_file_too_big">
|
||||
<field name="text">The file size of the record '%(recname)s' exceeded the maximum value of 5MB.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_file_invalid_mime">
|
||||
<field name="text">The file type '%(fmime)s' of the record '%(recname)s' is not allowed. (allowed: PNG, JPG, PDF)</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
2
setup.py
2
setup.py
|
@ -42,7 +42,7 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f:
|
|||
major_version = 6
|
||||
minor_version = 0
|
||||
|
||||
requires = []
|
||||
requires = ['python-magic>=0.4.24', 'Pillow']
|
||||
for dep in info.get('depends', []):
|
||||
if not re.match(r'(ir|res|webdav)(\W|$)', dep):
|
||||
if dep in modversion.keys():
|
||||
|
|
24
tests/__init__.py
Normal file
24
tests/__init__.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
import trytond.tests.test_tryton
|
||||
import unittest
|
||||
|
||||
from trytond.modules.cashbook_media.tests.test_line import LineTestCase
|
||||
|
||||
|
||||
__all__ = ['suite']
|
||||
|
||||
|
||||
class CashbookTestCase(\
|
||||
LineTestCase,
|
||||
):
|
||||
'Test cashbook module'
|
||||
module = 'cashbook_media'
|
||||
|
||||
# end CashbookTestCase
|
||||
|
||||
def suite():
|
||||
suite = trytond.tests.test_tryton.suite()
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(CashbookTestCase))
|
||||
return suite
|
5
tests/img_data.py
Normal file
5
tests/img_data.py
Normal file
File diff suppressed because one or more lines are too long
228
tests/test_line.py
Normal file
228
tests/test_line.py
Normal file
|
@ -0,0 +1,228 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the cashbook-module from m-ds for Tryton.
|
||||
# The COPYRIGHT file at the top level of this repository contains the
|
||||
# full copyright notices and license terms.
|
||||
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
from trytond.pool import Pool
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.exceptions import UserError
|
||||
from trytond.modules.cashbook.tests import CashbookTestCase
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
from .img_data import img_data_png, dok_data_pdf, text_data
|
||||
|
||||
|
||||
class LineTestCase(CashbookTestCase):
|
||||
'Test cashbook line module'
|
||||
module = 'cashbook_media'
|
||||
|
||||
@with_transaction()
|
||||
def test_media_add_image(self):
|
||||
""" create cook/line, add png-file
|
||||
"""
|
||||
pool = Pool()
|
||||
Book = pool.get('cashbook.book')
|
||||
Lines = pool.get('cashbook.line')
|
||||
|
||||
types = self.prep_type()
|
||||
category = self.prep_category(cattype='in')
|
||||
company = self.prep_company()
|
||||
party = self.prep_party()
|
||||
book, = Book.create([{
|
||||
'name': 'Book 1',
|
||||
'btype': types.id,
|
||||
'company': company.id,
|
||||
'currency': company.currency.id,
|
||||
'number_sequ': self.prep_sequence().id,
|
||||
'start_date': date(2022, 5, 1),
|
||||
'lines': [('create', [{
|
||||
'date': date(2022, 5, 1),
|
||||
'description': 'Text 1',
|
||||
'category': category.id,
|
||||
'bookingtype': 'in',
|
||||
'amount': Decimal('1.0'),
|
||||
'party': party.id,
|
||||
},])],
|
||||
}])
|
||||
self.assertEqual(book.name, 'Book 1')
|
||||
self.assertEqual(len(book.lines), 1)
|
||||
self.assertEqual(book.state, 'open')
|
||||
|
||||
# add image to line-1
|
||||
Lines.write(*[
|
||||
[book.lines[0]],
|
||||
{
|
||||
'media': img_data_png,
|
||||
'media_name': 'image.png',
|
||||
}])
|
||||
self.assertEqual(book.lines[0].media_size, 18428)
|
||||
self.assertEqual(book.lines[0].media_mime, 'image/png')
|
||||
self.assertEqual(book.lines[0].media_name, 'image.png')
|
||||
|
||||
# replace image at line-1 by pdf
|
||||
Lines.write(*[
|
||||
[book.lines[0]],
|
||||
{
|
||||
'media': dok_data_pdf,
|
||||
}])
|
||||
self.assertEqual(book.lines[0].media_size, 8724)
|
||||
self.assertEqual(book.lines[0].media_mime, 'application/pdf')
|
||||
self.assertEqual(book.lines[0].media_name, 'image.png')
|
||||
|
||||
# create line with pdf
|
||||
Book.write(*[
|
||||
[book],
|
||||
{
|
||||
'lines': [('create', [{
|
||||
'date': date(2022, 5, 2),
|
||||
'description': 'Text 2',
|
||||
'category': category.id,
|
||||
'bookingtype': 'in',
|
||||
'amount': Decimal('1.0'),
|
||||
'party': party.id,
|
||||
'media': dok_data_pdf,
|
||||
'media_name': 'data.pdf',
|
||||
}])],
|
||||
}
|
||||
])
|
||||
self.assertEqual(len(book.lines), 2)
|
||||
self.assertEqual(book.lines[1].media_size, 8724)
|
||||
self.assertEqual(book.lines[1].media_mime, 'application/pdf')
|
||||
self.assertEqual(book.lines[1].media_name, 'data.pdf')
|
||||
|
||||
@with_transaction()
|
||||
def test_media_add_invalid_file(self):
|
||||
""" create cook/line, add txt-file
|
||||
"""
|
||||
pool = Pool()
|
||||
Book = pool.get('cashbook.book')
|
||||
Lines = pool.get('cashbook.line')
|
||||
|
||||
types = self.prep_type()
|
||||
category = self.prep_category(cattype='in')
|
||||
company = self.prep_company()
|
||||
party = self.prep_party()
|
||||
book, = Book.create([{
|
||||
'name': 'Book 1',
|
||||
'btype': types.id,
|
||||
'company': company.id,
|
||||
'currency': company.currency.id,
|
||||
'number_sequ': self.prep_sequence().id,
|
||||
'start_date': date(2022, 5, 1),
|
||||
'lines': [('create', [{
|
||||
'date': date(2022, 5, 1),
|
||||
'description': 'Text 1',
|
||||
'category': category.id,
|
||||
'bookingtype': 'in',
|
||||
'amount': Decimal('1.0'),
|
||||
'party': party.id,
|
||||
},])],
|
||||
}])
|
||||
self.assertEqual(book.name, 'Book 1')
|
||||
self.assertEqual(len(book.lines), 1)
|
||||
self.assertEqual(book.state, 'open')
|
||||
|
||||
# add invalid file
|
||||
self.assertRaisesRegex(UserError,
|
||||
"The file type 'text/plain' of the record '05/02/2022|Rev|1.00 usd|Text 2 [Cat1]' is not allowed. (allowed: PNG, JPG, PDF)",
|
||||
Book.write,
|
||||
*[
|
||||
[book],
|
||||
{
|
||||
'lines': [('create', [{
|
||||
'date': date(2022, 5, 2),
|
||||
'description': 'Text 2',
|
||||
'category': category.id,
|
||||
'bookingtype': 'in',
|
||||
'amount': Decimal('1.0'),
|
||||
'party': party.id,
|
||||
'media': text_data,
|
||||
'media_name': 'text.txt',
|
||||
}])],
|
||||
}
|
||||
])
|
||||
|
||||
# replace image at line-1 by invalid file
|
||||
self.assertRaisesRegex(UserError,
|
||||
"The file type 'text/plain' of the record '05/02/2022|Rev|1.00 usd|Text 2 [Cat1]' is not allowed. (allowed: PNG, JPG, PDF)",
|
||||
Lines.write,
|
||||
*[
|
||||
[book.lines[0]],
|
||||
{
|
||||
'media': text_data,
|
||||
'media_name': 'text.txt',
|
||||
},
|
||||
])
|
||||
|
||||
@with_transaction()
|
||||
def test_media_add_big_file(self):
|
||||
""" create cook/line, add big png-file
|
||||
"""
|
||||
pool = Pool()
|
||||
Book = pool.get('cashbook.book')
|
||||
Lines = pool.get('cashbook.line')
|
||||
|
||||
types = self.prep_type()
|
||||
category = self.prep_category(cattype='in')
|
||||
company = self.prep_company()
|
||||
party = self.prep_party()
|
||||
|
||||
book, = Book.create([{
|
||||
'name': 'Book 1',
|
||||
'btype': types.id,
|
||||
'company': company.id,
|
||||
'currency': company.currency.id,
|
||||
'number_sequ': self.prep_sequence().id,
|
||||
'start_date': date(2022, 5, 1),
|
||||
'lines': [('create', [{
|
||||
'date': date(2022, 5, 1),
|
||||
'description': 'Text 1',
|
||||
'category': category.id,
|
||||
'bookingtype': 'in',
|
||||
'amount': Decimal('1.0'),
|
||||
'party': party.id,
|
||||
},])],
|
||||
}])
|
||||
self.assertEqual(book.name, 'Book 1')
|
||||
self.assertEqual(len(book.lines), 1)
|
||||
self.assertEqual(book.state, 'open')
|
||||
|
||||
# construct image
|
||||
with BytesIO() as fhdl:
|
||||
img1 = Image.new('RGB', (3200, 1340))
|
||||
img1.save(fhdl, 'PNG', optimize=True)
|
||||
del img1
|
||||
|
||||
fhdl.seek(0)
|
||||
img_big_data = fhdl.read()
|
||||
|
||||
# create line with png, should be resized
|
||||
Book.write(*[
|
||||
[book],
|
||||
{
|
||||
'lines': [('create', [{
|
||||
'date': date(2022, 5, 2),
|
||||
'description': 'Text 2',
|
||||
'category': category.id,
|
||||
'bookingtype': 'in',
|
||||
'amount': Decimal('1.0'),
|
||||
'party': party.id,
|
||||
'media': img_big_data,
|
||||
'media_name': 'big.png',
|
||||
}])],
|
||||
}
|
||||
])
|
||||
self.assertEqual(len(book.lines), 2)
|
||||
self.assertEqual(book.lines[1].media_mime, 'image/jpeg')
|
||||
self.assertEqual(book.lines[1].media_size, 10221)
|
||||
self.assertEqual(book.lines[1].media_name, 'big.jpg')
|
||||
|
||||
# check image size
|
||||
with BytesIO(book.lines[1].media) as fhdl:
|
||||
img2 = Image.open(fhdl, 'r')
|
||||
self.assertEqual(img2.size, (2000, 837))
|
||||
|
||||
# end LineTestCase
|
|
@ -3,3 +3,5 @@ version=6.0.0
|
|||
depends:
|
||||
cashbook
|
||||
xml:
|
||||
message.xml
|
||||
line.xml
|
||||
|
|
24
view/line_form.xml
Normal file
24
view/line_form.xml
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the cashbook-module from m-ds for Tryton.
|
||||
The COPYRIGHT file at the top level of this repository contains the
|
||||
full copyright notices and license terms. -->
|
||||
<data>
|
||||
|
||||
<xpath expr="/form/notebook/page[@name='references']" position="after">
|
||||
|
||||
<page name="media" col="4" string="Image/PDF">
|
||||
<label name="media"/>
|
||||
<field name="media"/>
|
||||
<newline/>
|
||||
|
||||
<label name="media_name"/>
|
||||
<field name="media_name"/>
|
||||
|
||||
<label name="media_mime"/>
|
||||
<field name="media_mime"/>
|
||||
|
||||
</page>
|
||||
|
||||
</xpath>
|
||||
|
||||
</data>
|
Loading…
Reference in a new issue