nacer kraa
nacer kraa

Reputation: 9

How to upload multiple files in website, and add it as an attachment to a new record in Odoo 15?

I'm trying to upload multiple files from the Odoo website, and then call a controller with method post to create a record and save the attachment.

this is my module student.py

   ref = fields.Char(string="Ref")
   first_name = fields.Char(string="Firstname")
   last_name = fields.Char(string="Lastname")
   task_attachment = fields.Many2many(comodel_name="ir.attachment",
                        relation="m2m_ir_identity_card_rel",
                        column1="m2m_id",
                        column2="attachment_id",
                        string="Identity Card")

And this is my form and input on web_form.xml

 <form role="form" action="/create/webstudent" enctype="multipart/form-data" method="POST">
   <div class="form-group">
       <label for="student_file" class="control-label">File Of Student :</label>
       <input type="file" name="task_attachment" multiple="true"/>
    </div>

    <div class="clearfix oe_login_buttons">
        <button type="submit" class="btn btn-primary pull-left">Submit</button>
    </div>
  </form>

And this is my controller

@http.route('/create/webstudent', type="http", auth="user", website=True)
  def create_webpatient(self, **kw):
   if request.httprequest.method == 'POST':
    new_task = request.env['university.student'].sudo().create(kw)
    if 'task_attachment' in request.params:
        attached_files = request.httprequest.files.getlist('task_attachment')
        for attachment in attached_files:
            attached_file = attachment.read()
            request.env['ir.attachment'].sudo().create({
                'name': attachment.filename,
                'res_model': 'university.student',
                'res_id': new_task.id,
                'type': 'binary',
                'datas_fname': attachment.filename,
                'datas': attached_file.encode('base64'),
            })

   return request.render("om_university.student_thanks", {})

When I click the submit button this error shows up :

    500: Internal Server Error
      Traceback
    Traceback (most recent call last):
     File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
   15\odoo\odoo\addons\base\models\ir_http.py", line 237, in _dispatch
    result = request.dispatch()
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\http.py", line 811, in dispatch
    r = self._call_function(**self.params)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\http.py", line 360, in _call_function
    return checked_call(self.db, *args, **kwargs)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\service\model.py", line 94, in wrapper
    return f(dbname, *args, **kwargs)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\http.py", line 349, in checked_call
    result = self.endpoint(*a, **kw)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\http.py", line 917, in __call__
    return self.method(*args, **kw)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\http.py", line 536, in response_wrap
    response = f(*args, **kw)
  File "c:\users\nacer\desktop\learning_odoo\odooworkspace\odoo- 
  15\custom_addons\om_university\controllers\controllers.py", line 29, 
  in create_webpatient
    new_task = request.env['university.student'].sudo().create(kw)
  File "<decorator-gen-131>", line 2, in create
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\api.py", line 412, in _model_create_multi
    return create(self, [arg])
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\addons\mail\models\mail_thread.py", line 265, in create
    threads = super(MailThread, self).create(vals_list)
  File "<decorator-gen-67>", line 2, in create
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\api.py", line 413, in _model_create_multi
    return create(self, arg)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\addons\base\models\ir_fields.py", line 613, in create
    recs = super().create(vals_list)
  File "<decorator-gen-13>", line 2, in create
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\api.py", line 413, in _model_create_multi
    return create(self, arg)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\models.py", line 4070, in create
    records = self._create(data_list)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\models.py", line 4220, in _create
    field.create([
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\fields.py", line 3283, in create
    self.write_batch(record_values, True)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\fields.py", line 3304, in write_batch
    raise ValueError("Wrong value for %s: %s" % (self, value))
   Exception
  The above exception was the direct cause of the following exception:
   Traceback (most recent call last):
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\addons\base\models\ir_http.py", line 237, in _dispatch
    result = request.dispatch()
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\http.py", line 811, in dispatch
    r = self._call_function(**self.params)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\http.py", line 360, in _call_function
    return checked_call(self.db, *args, **kwargs)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\service\model.py", line 94, in wrapper
    return f(dbname, *args, **kwargs)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\http.py", line 349, in checked_call
    result = self.endpoint(*a, **kw)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\http.py", line 917, in __call__
    return self.method(*args, **kw)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\http.py", line 536, in response_wrap
    response = f(*args, **kw)
  File "c:\users\nacer\desktop\learning_odoo\odooworkspace\odoo- 
  15\custom_addons\om_university\controllers\controllers.py", line 29, 
   in create_webpatient
    new_task = request.env['university.student'].sudo().create(kw)
  File "<decorator-gen-131>", line 2, in create
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\api.py", line 412, in _model_create_multi
    return create(self, [arg])
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\addons\mail\models\mail_thread.py", line 265, in create
    threads = super(MailThread, self).create(vals_list)
  File "<decorator-gen-67>", line 2, in create
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
   15\odoo\odoo\api.py", line 413, in _model_create_multi
    return create(self, arg)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\addons\base\models\ir_fields.py", line 613, in create
    recs = super().create(vals_list)
  File "<decorator-gen-13>", line 2, in create
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\api.py", line 413, in _model_create_multi
    return create(self, arg)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\models.py", line 4070, in create
    records = self._create(data_list)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\models.py", line 4220, in _create
    field.create([
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\fields.py", line 3283, in create
    self.write_batch(record_values, True)
  File "C:\Users\nacer\Desktop\learning_odoo\odooWorkspace\odoo- 
  15\odoo\odoo\fields.py", line 3304, in write_batch
    raise ValueError("Wrong value for %s: %s" % (self, value))
  ValueError: Wrong value for university.student.image_1920: 
   <FileStorage: '888j06068.jpg' ('image/jpeg')>

can anybody help me, please!

Upvotes: 0

Views: 1513

Answers (1)

Ahrimann Steiner
Ahrimann Steiner

Reputation: 1314

1st EXAMPLE : multiple files uploads

web/static/src/xml/base.xml

<t t-name="web.CustomFileInput" owl="1">
    <span class="o_file_input" aria-atomic="true">
        <span class="o_file_input_trigger" t-on-click.prevent="_onTriggerClicked">
            <t t-slot="default">
                <button class="btn btn-primary">Choose File</button>
            </t>
        </span>
        <input type="file" name="ufile" class="o_input_file d-none"
            t-att="{multiple: props.multi_upload, accept: props.accepted_file_extensions}"
            t-ref="file-input"
            t-on-change="_onFileInputChange"
        />
    </span>
</t>

async _uploadFiles(files, params={}) {
        if (!files || !files.length) { return; }

        await new Promise(resolve => {
            const xhr = this._createXhr();
            xhr.open('POST', this._getFileUploadRoute());
      //...
      //...
_getFileUploadRoute() {
        return '/documents/upload_attachment';
    },

controllers/main.py

@http.route('/documents/upload_attachment', type='http', methods=['POST'], auth="user")
    def upload_document(self, folder_id, ufile, document_id=False, partner_id=False, owner_id=False):
        files = request.httprequest.files.getlist('ufile')
        result = {'success': _("All files uploaded")}
        tag_ids = request.params.pop('tag_ids', None)
        tag_ids = tag_ids.split(',') if tag_ids else []
        if document_id:
            document = request.env['documents.document'].browse(int(document_id))
            ufile = files[0]
            try:
                data = base64.encodebytes(ufile.read())
                mimetype = ufile.content_type
                document.write({
                    'name': ufile.filename,
                    'datas': data,
                    'mimetype': mimetype,
                })
            except Exception as e:
                logger.exception("Fail to upload document %s" % ufile.filename)
                result = {'error': str(e)}
        else:
            vals_list = []
            for ufile in files:
                try:
                    mimetype = ufile.content_type
                    datas = base64.encodebytes(ufile.read())
                    vals = {
                        'name': ufile.filename,
                        'mimetype': mimetype,
                        'datas': datas,
                        'folder_id': int(folder_id),
                        'tag_ids': tag_ids,
                        'partner_id': int(partner_id)
                    }
                    if owner_id:
                        vals['owner_id'] = int(owner_id)
                    vals_list.append(vals)
                except Exception as e:
                    logger.exception("Fail to upload document %s" % ufile.filename)
                    result = {'error': str(e)}
            documents = request.env['documents.document'].create(vals_list)
            result['ids'] = documents.ids

        return json.dumps(result)

models/document.py

class Document(models.Model):
    _name = 'documents.document'
    @api.model
    def create(self, vals):
        keys = [key for key in vals if
                self._fields[key].related and self._fields[key].related[0] == 'attachment_id']
        attachment_dict = {key: vals.pop(key) for key in keys if key in vals}
        attachment = self.env['ir.attachment'].browse(vals.get('attachment_id'))

        if attachment and attachment_dict:
            attachment.write(attachment_dict)
        elif attachment_dict:
            attachment_dict.setdefault('name', vals.get('name', 'unnamed'))
            attachment = self.env['ir.attachment'].create(attachment_dict)
            vals['attachment_id'] = attachment.id
        new_record = super(Document, self).create(vals)

        # this condition takes precedence during forward-port.
        if (attachment and not attachment.res_id and (not attachment.res_model or attachment.res_model == 'documents.document')):
            attachment.with_context(no_document=True).write({'res_model': 'documents.document', 'res_id': new_record.id})
        return new_record

2nd EXAMPLE: in web_editor/static/src/xml/wysiwyg.xml

<input type="file" class="d-none o_file_input" name="upload" t-att-accept="widget.options.accept" t-att-multiple="widget.options.multiImages &amp;&amp; 'multiple'"/>

in web_editor/static/src/js/wysiwyg/widgets/media.js

/**
 * Let users choose a file, including uploading a new file in odoo.
 */
var FileWidget = SearchableMediaWidget.extend({
    events: _.extend({}, SearchableMediaWidget.prototype.events || {}, {
        'click .o_upload_media_button': '_onUploadButtonClick',
        'change .o_file_input': '_onFileInputChange',
/**
     * Handles change of the file input: create attachments with the new files
     * and open the Preview dialog for each of them. Locks the save button until
     * all new files have been processed.
     *
     * @private
     * @returns {Promise}
     */
    _onFileInputChange: function () {
        return this._mutex.exec(this._addData.bind(this));
    },
/**
     * Uploads the files that are currently selected on the file input, which
     * creates new attachments. Then inserts them on the media dialog and
     * selects them. If multiImages is not set, also triggers up the
     * save_request event to insert the attachment in the DOM.
     *
     * @private
     * @returns {Promise}
     */
    async _addData() {
        let files = this.$fileInput[0].files;
        if (!files.length) {
            // Case if the input is emptied, return resolved promise
            return;
        }

        var self = this;
        var uploadMutex = new concurrency.Mutex();

        // Upload the smallest file first to block the user the least possible.
        files = _.sortBy(files, 'size');
        _.each(files, function (file) {
            // Upload one file at a time: no need to parallel as upload is
            // limited by bandwidth.
            uploadMutex.exec(function () {
                return utils.getDataURLFromFile(file).then(function (result) {
                    return self._rpc({
                        route: '/web_editor/attachment/add_data',
                        params: {
                            'name': file.name,
                            'data': result.split(',')[1],
                            'res_id': self.options.res_id,
                            'res_model': self.options.res_model,
                            'width': 0,
                            'quality': 0,
                        },
                    }).then(function (attachment) {
                        self._handleNewAttachment(attachment);
                    });
                });
            });
        });

in /web_editor/main.py

    @http.route('/web_editor/attachment/add_data', type='json', auth='user', methods=['POST'], website=True)
    def add_data(self, name, data, quality=0, width=0, height=0, res_id=False, res_model='ir.ui.view', **kwargs):
        try:
            data = tools.image_process(data, size=(width, height), quality=quality, verify_resolution=True)
        except UserError:
            pass  # not an image
        self._clean_context()
        attachment = self._attachment_create(name=name, data=data, res_id=res_id, res_model=res_model)
        return attachment._get_media_info()


    def _attachment_create(self, name='', data=False, url=False, res_id=False, res_model='ir.ui.view'):
        """Create and return a new attachment."""
        if name.lower().endswith('.bmp'):
            # Avoid mismatch between content type and mimetype, see commit msg
            name = name[:-4]

        if not name and url:
            name = url.split("/").pop()

        if res_model != 'ir.ui.view' and res_id:
            res_id = int(res_id)
        else:
            res_id = False

        attachment_data = {
            'name': name,
            'public': res_model == 'ir.ui.view',
            'res_id': res_id,
            'res_model': res_model,
        }

        if data:
            attachment_data['datas'] = data
        elif url:
            attachment_data.update({
                'type': 'url',
                'url': url,
            })
        else:
            raise UserError(_("You need to specify either data or url to create an attachment."))

        attachment = request.env['ir.attachment'].create(attachment_data)
        return attachment

Upvotes: 1

Related Questions