first push message
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
'name': "attachment_size",
|
||||
|
||||
'summary': "Short (1 phrase/line) summary of the module's purpose",
|
||||
|
||||
'description': """
|
||||
Long description of module's purpose
|
||||
""",
|
||||
|
||||
'author': "Copy by Chay Kroem",
|
||||
'website': "https://www.yourcompany.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'tools',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base','web','base_setup'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
# 'security/ir.model.access.csv',
|
||||
# 'views/views.xml',
|
||||
# 'views/templates.xml',
|
||||
'views/res_config_settings_view.xml',
|
||||
'views/res_users_view.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
'demo/demo.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'attachment_size/static/src/js/file_uploader.js',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import controllers
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
# from odoo import http
|
||||
|
||||
|
||||
# class AttachmentSize(http.Controller):
|
||||
# @http.route('/attachment_size/attachment_size', auth='public')
|
||||
# def index(self, **kw):
|
||||
# return "Hello, world"
|
||||
|
||||
# @http.route('/attachment_size/attachment_size/objects', auth='public')
|
||||
# def list(self, **kw):
|
||||
# return http.request.render('attachment_size.listing', {
|
||||
# 'root': '/attachment_size/attachment_size',
|
||||
# 'objects': http.request.env['attachment_size.attachment_size'].search([]),
|
||||
# })
|
||||
|
||||
# @http.route('/attachment_size/attachment_size/objects/<model("attachment_size.attachment_size"):obj>', auth='public')
|
||||
# def object(self, obj, **kw):
|
||||
# return http.request.render('attachment_size.object', {
|
||||
# 'object': obj
|
||||
# })
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<record id="object0" model="attachment_size.attachment_size">
|
||||
<field name="name">Object 0</field>
|
||||
<field name="value">0</field>
|
||||
</record>
|
||||
|
||||
<record id="object1" model="attachment_size.attachment_size">
|
||||
<field name="name">Object 1</field>
|
||||
<field name="value">10</field>
|
||||
</record>
|
||||
|
||||
<record id="object2" model="attachment_size.attachment_size">
|
||||
<field name="name">Object 2</field>
|
||||
<field name="value">20</field>
|
||||
</record>
|
||||
|
||||
<record id="object3" model="attachment_size.attachment_size">
|
||||
<field name="name">Object 3</field>
|
||||
<field name="value">30</field>
|
||||
</record>
|
||||
|
||||
<record id="object4" model="attachment_size.attachment_size">
|
||||
<field name="name">Object 4</field>
|
||||
<field name="value">40</field>
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# from . import models
|
||||
from . import res_config_settings
|
||||
from . import ir_attachment
|
||||
from . import res_users
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,102 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ir_attachment.py
|
||||
from odoo import models, _
|
||||
from odoo.exceptions import UserError
|
||||
import base64
|
||||
|
||||
|
||||
def human_file_size(size_bytes):
|
||||
"""Convert bytes to human readable format"""
|
||||
if size_bytes == 0:
|
||||
return "0 B"
|
||||
size_name = ("B", "KB", "MB", "GB", "TB")
|
||||
import math
|
||||
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||
p = math.pow(1024, i)
|
||||
s = round(size_bytes / p, 2)
|
||||
return "%s %s" % (s, size_name[i])
|
||||
|
||||
|
||||
class IrHttp(models.AbstractModel):
|
||||
_inherit = "ir.http"
|
||||
|
||||
def session_info(self):
|
||||
print("session_info called ✅")
|
||||
|
||||
session_info = super().session_info()
|
||||
|
||||
user = self.env.user
|
||||
current_user_id = user.id
|
||||
|
||||
user_id = self.env['res.users'].browse(current_user_id)
|
||||
|
||||
max_attachment_size_bytes = None
|
||||
|
||||
if user_id and user_id.user_select_attachment_size_unit and user_id.users_general_size_limit_attachment > 0:
|
||||
unit = user_id.user_select_attachment_size_unit
|
||||
size = user_id.users_general_size_limit_attachment
|
||||
|
||||
if unit == 'kb':
|
||||
max_attachment_size_bytes = size * 1024
|
||||
elif unit == 'mb':
|
||||
max_attachment_size_bytes = size * 1024 * 1024
|
||||
else:
|
||||
max_attachment_size_bytes = size * 1024 * 1024
|
||||
|
||||
else:
|
||||
enable_general_limit = self.env['ir.config_parameter'].sudo().get_param(
|
||||
'pys_attachment_size_limitation.enable_general_size_limit_attachment',
|
||||
False
|
||||
)
|
||||
|
||||
if enable_general_limit:
|
||||
unit = self.env["ir.config_parameter"].sudo().get_param(
|
||||
"pys_attachment_size_limitation.select_attachment_size_unit",
|
||||
)
|
||||
|
||||
size = int(
|
||||
self.env["ir.config_parameter"].sudo().get_param(
|
||||
"pys_attachment_size_limitation.general_size_limit_attachment",
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
if unit and size > 0:
|
||||
if unit == 'kb':
|
||||
max_attachment_size_bytes = size * 1024
|
||||
elif unit == 'mb':
|
||||
max_attachment_size_bytes = size * 1024 * 1024
|
||||
else:
|
||||
max_attachment_size_bytes = size * 1024 * 1024
|
||||
|
||||
# Ensure it's an integer
|
||||
session_info["max_attachment_size"] = int(max_attachment_size_bytes) if max_attachment_size_bytes else None
|
||||
|
||||
print("max_attachment_size (in bytes):", session_info["max_attachment_size"])
|
||||
return session_info
|
||||
|
||||
|
||||
class IrAttachment(models.Model):
|
||||
_inherit = 'ir.attachment'
|
||||
|
||||
def create(self, vals_list):
|
||||
# Check size before creation
|
||||
for vals in vals_list:
|
||||
if vals.get('datas'):
|
||||
try:
|
||||
decoded_data = base64.b64decode(vals['datas'])
|
||||
max_size = self.env['ir.http'].session_info().get('max_attachment_size')
|
||||
|
||||
if max_size and max_size > 0 and len(decoded_data) > max_size:
|
||||
raise UserError(_(
|
||||
"Attachment size (%s) exceeds the maximum allowed size (%s)."
|
||||
) % (
|
||||
human_file_size(len(decoded_data)),
|
||||
human_file_size(max_size)
|
||||
))
|
||||
except Exception as e:
|
||||
if isinstance(e, UserError):
|
||||
raise
|
||||
# If base64 decoding fails, continue
|
||||
|
||||
return super().create(vals_list)
|
||||
@@ -0,0 +1,62 @@
|
||||
from odoo import models, _, api, fields
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class IrAttachment(models.Model):
|
||||
_inherit = 'ir.attachment'
|
||||
|
||||
def create(self, vals_list):
|
||||
# 1. Get the limit from system parameters (set via res.config.settings)
|
||||
# Default to 2048 MB (2 GB) if not set in settings
|
||||
max_size_mb = float(self.env['ir.config_parameter'].sudo().get_param(
|
||||
'attachment_size.max_attachment_size', default=2048.0
|
||||
))
|
||||
|
||||
# 2. Convert MB to bytes
|
||||
max_size_bytes = max_size_mb * 1024 * 1024
|
||||
|
||||
# 3. Validate file size
|
||||
for vals in vals_list:
|
||||
if vals.get('datas'):
|
||||
# Base64 increases size by ~33%, so we check against max_size_bytes * 4 / 3
|
||||
if len(vals['datas']) > (max_size_bytes * 4 / 3):
|
||||
raise UserError(_(
|
||||
"File size exceeds the maximum allowed limit of %(max_size)s MB.",
|
||||
max_size=max_size_mb
|
||||
))
|
||||
|
||||
return super().create(vals_list)
|
||||
|
||||
|
||||
class FileSizeConfigSetting(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
max_attachment_size = fields.Float(
|
||||
string="Maximum Attachment Size (MB)",
|
||||
config_parameter='attachment_size.max_attachment_size',
|
||||
help="Set the maximum size for attachments in MB. Default is 2048 MB (2 GB)."
|
||||
)
|
||||
|
||||
|
||||
class Http(models.AbstractModel):
|
||||
_inherit = "ir.http"
|
||||
|
||||
@api.model
|
||||
def session_info(self):
|
||||
session_info = super().session_info()
|
||||
|
||||
# Get the limit from system parameters, default to 2048 MB (2 GB)
|
||||
max_size_mb = float(self.env['ir.config_parameter'].sudo().get_param(
|
||||
'attachment_size.max_attachment_size', default=2048.0
|
||||
))
|
||||
|
||||
max_attachment_size_bytes = max_size_mb * 1024 * 1024 # Convert MB to Bytes
|
||||
|
||||
# Always update session_info so JS always has a value (no more undefined)
|
||||
session_info.update({
|
||||
"max_attachment_size": max_attachment_size_bytes,
|
||||
"max_size": max_size_mb,
|
||||
"max_file_upload_size": max_attachment_size_bytes,
|
||||
})
|
||||
|
||||
return session_info
|
||||
@@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#res_config_settings.py
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
enable_general_size_limit_attachment = fields.Boolean(string="Enable Attachment Size Limitation",
|
||||
help='Enable Attachment Size Limitation',
|
||||
store=True,
|
||||
config_parameter="pys_attachment_size_limitation.enable_general_size_limit_attachment"
|
||||
)
|
||||
|
||||
general_size_limit_attachment = fields.Integer(string="Attachment Size Limit",
|
||||
help="Maximum allowed attachment size",
|
||||
store=True,
|
||||
config_parameter="pys_attachment_size_limitation.general_size_limit_attachment"
|
||||
)
|
||||
|
||||
select_attachment_size_unit = fields.Selection([('mb', 'MB'), ('kb', 'KB')],
|
||||
string="Attachment Size Unit",
|
||||
help="Select the unit used for attachment size limitation",
|
||||
config_parameter="pys_attachment_size_limitation.select_attachment_size_unit",
|
||||
store=True, default='kb'
|
||||
)
|
||||
|
||||
# In res_config_settings.py
|
||||
@api.constrains('general_size_limit_attachment')
|
||||
def _check_positive_attachment_size(self):
|
||||
for rec in self:
|
||||
if rec.general_size_limit_attachment is not None:
|
||||
if rec.general_size_limit_attachment < 0:
|
||||
raise ValidationError(
|
||||
_("Attachment size limit must be a positive value.")
|
||||
)
|
||||
# Optional: Add a reasonable maximum limit (e.g., 10GB)
|
||||
if rec.general_size_limit_attachment > 10485760: # 10GB in KB
|
||||
raise ValidationError(
|
||||
_("Attachment size limit cannot exceed 10 GB.")
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#res_users.py
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
users_general_size_limit_attachment = fields.Integer(string="Attachment Size Limit",
|
||||
help="Maximum allowed attachment size",
|
||||
store=True,
|
||||
config_parameter="pys_attachment_size_limitation.users_general_size_limit_attachment"
|
||||
)
|
||||
|
||||
user_select_attachment_size_unit = fields.Selection([('mb', 'MB'), ('kb', 'KB')],
|
||||
string="Attachment Size Unit", store=True,
|
||||
help="Select the unit used for attachment size limitation",
|
||||
default='kb')
|
||||
|
||||
@api.constrains('users_general_size_limit_attachment')
|
||||
def _check_positive_attachment_size(self):
|
||||
for rec in self:
|
||||
if rec.users_general_size_limit_attachment is not None and rec.users_general_size_limit_attachment < 0:
|
||||
raise ValidationError(
|
||||
_("Attachment size limit must be a positive value.")
|
||||
)
|
||||
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_attachment_size_attachment_size,attachment_size.attachment_size,model_attachment_size_attachment_size,base.group_user,1,1,1,1
|
||||
|
@@ -0,0 +1,70 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { FileUploader } from "@web/views/fields/file_handler";
|
||||
import { humanSize } from "@web/core/utils/binary";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { session } from "@web/session";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
patch(FileUploader.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
// max_attachment_size comes from ir.http session_info (in BYTES)
|
||||
this.max_attachment_size = session.max_attachment_size;
|
||||
|
||||
if (this.max_attachment_size && this.max_attachment_size > 0) {
|
||||
console.log(
|
||||
"[Attachment Limit] Max size:",
|
||||
humanSize(this.max_attachment_size)
|
||||
);
|
||||
} else {
|
||||
console.log("[Attachment Limit] Unlimited attachments");
|
||||
}
|
||||
|
||||
this.dialogService = useService("dialog");
|
||||
},
|
||||
|
||||
async onFileChange(ev) {
|
||||
// If no limit is set, proceed normally
|
||||
if (!this.max_attachment_size || this.max_attachment_size <= 0) {
|
||||
return super.onFileChange(ev);
|
||||
}
|
||||
|
||||
const files = ev.target.files;
|
||||
|
||||
// Check all files for size violations
|
||||
const oversizedFiles = [];
|
||||
for (const file of files) {
|
||||
if (file.size > this.max_attachment_size) {
|
||||
oversizedFiles.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
// If any file exceeds the limit, show error and STOP processing
|
||||
if (oversizedFiles.length > 0) {
|
||||
const file = oversizedFiles[0]; // Show first oversized file
|
||||
|
||||
this.dialogService.add(ConfirmationDialog, {
|
||||
title: _t("Validation Error"),
|
||||
body: _t(
|
||||
"The selected file \"%s\" is %s, which exceeds the maximum allowed size of %s.",
|
||||
file.name,
|
||||
humanSize(file.size),
|
||||
humanSize(this.max_attachment_size)
|
||||
),
|
||||
});
|
||||
|
||||
// IMPORTANT: Clear the file input to prevent further processing
|
||||
ev.target.value = '';
|
||||
|
||||
// Return early - DO NOT call super.onFileChange()
|
||||
return;
|
||||
}
|
||||
|
||||
// All files are within limit, proceed normally
|
||||
return super.onFileChange(ev);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.attachment</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="priority" eval="40"/>
|
||||
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//block[@id='user_default_rights']" position="before">
|
||||
<block title="Attachment" id="attachment_size_limit">
|
||||
|
||||
<div class="row mt16 o_settings_container">
|
||||
<div class="col-12 o_setting_box">
|
||||
|
||||
<!-- LEFT PANE -->
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="enable_general_size_limit_attachment"/>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT PANE -->
|
||||
<div class="o_setting_right_pane"
|
||||
invisible="not enable_general_size_limit_attachment">
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-6 ">
|
||||
<label for="select_attachment_size_unit" class="me-3"/>
|
||||
<field name="select_attachment_size_unit"
|
||||
required="enable_general_size_limit_attachment"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Size (Right side) -->
|
||||
<div class="col-6">
|
||||
<label for="general_size_limit_attachment" class="me-3"/>
|
||||
<field name="general_size_limit_attachment" required="enable_general_size_limit_attachment"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="text-muted mt8">
|
||||
Define the maximum allowed attachment size.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</block>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="inherit_res_user_attachment" model="ir.ui.view">
|
||||
<field name="name">inherit.res.user.attachment</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="base.view_users_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook/page[@name='preferences']" position="inside">
|
||||
<group>
|
||||
<group>
|
||||
<field name="user_select_attachment_size_unit"
|
||||
required="users_general_size_limit_attachment"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="users_general_size_limit_attachment"
|
||||
modifiers="{'required': ['users_general_size_limit_attachment', '>', 0]}"/>
|
||||
</group>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,24 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<template id="listing">
|
||||
<ul>
|
||||
<li t-foreach="objects" t-as="object">
|
||||
<a t-attf-href="#{ root }/objects/#{ object.id }">
|
||||
<t t-esc="object.display_name"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template id="object">
|
||||
<h1><t t-esc="object.display_name"/></h1>
|
||||
<dl>
|
||||
<t t-foreach="object._fields" t-as="field">
|
||||
<dt><t t-esc="field"/></dt>
|
||||
<dd><t t-esc="object[field]"/></dd>
|
||||
</t>
|
||||
</dl>
|
||||
</template>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,19 @@
|
||||
<odoo>
|
||||
<record id="res_config_settings_attachment_size" model="ir.ui.view">
|
||||
<field name="name">base.inherited.setting.view.form</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="priority" eval="10" />
|
||||
<field name="inherit_id" ref="base_setup.res_config_settings_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@id='invite_users']" position="after">
|
||||
<div name="attachment_size" >
|
||||
<block title="Attachment File Size" id="attachment_file_size_id" >
|
||||
<setting id="max_attachment_size_id" string="File Size" help="Define the maximum upload size of a file in MB(s)." >
|
||||
<field name="max_attachment_size" /><div class="d-inline-block">MB(s)</div>
|
||||
</setting>
|
||||
</block>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user