Attachments in form

Hi I am creating my own package, I need to create App.ControllForm in a modal window that will have options to add attachments, what api should I use and form tags.
currently I have it done like this

class AssetModal extends App.ControllerModal
  head: __('Model')
  shown: true
  buttonSubmit: true
  buttonCancel: true
  large: true

  content: ->

  render: ->
    @form_id = App.ControllerForm.formId()

    content = $(App.view('asset/form')())

    @form = new App.ControllerForm(
      el: content.find('.js-form')
        configure_attributes: [
          { name: 'name', display: __('Nazwa'), tag: 'input', null: false, },
          { name: 'serial_number', display: __('Numer seryjny'), tag: 'input',null: false },
          { name: 'organization_id', display: __('Organization'), tag: 'autocompletion_ajax_customer_organization', multiple: false, null: false, relation: 'Organization', },
          { name: 'user_ids', display: __('User'), tag: 'autocompletion_ajax', relation: 'User', null: true, },
          { name: 'invoice', display: __('Faktura'), tag: 'file_upload',form_id: @form_id, class: 'input', null: true,  },
          { name: 'attachments', display: __('Załączniki'), tag: 'file_upload', form_id: @form_id, class: 'input', null: true,  },
          { name: 'asset_model_id', display: __('Aktywo'), tag: 'autocompletion_ajax', relation: 'AssetModel', null: true, },
          { name: 'seller_model_id', display: __('Sprzedawca'), tag: 'autocompletion_ajax', relation: 'SellerModel', null: true,},
          { name: 'purchase_date', display: __('Data kupna'), tag: 'date', class: 'input', null: true },
          { name: 'purchase_cost', display: __('Koszt kupna'), tag: 'decimal', null: true},
          { name: 'description', display: __('Opis'), tag: 'textarea',upload:true, class: 'input', null: true},
      params: @data

    callback = (data) =>
      @objects = data.items
      return if data.items.length >= data.total_count

  onSubmit: (e) =>
    params = @formParam(
    error = @form.validate(params)

    if error
      @formValidate(form:, errors: error)
      return false

    isEditing = Boolean(@data)
    if isEditing
      id: 'assets_edit',
      type: 'PUT',
      url: "#{@apiPath}/assets/#{}",
      data: JSON.stringify(params),
      processData: true,
      success: (data) =>
      id: 'assets_add',
      type: 'POST',
      url: "#{@apiPath}/assets/",
      data: JSON.stringify(params),
      processData: true,
      success: (data) =>

# coffeelint: disable=camel_case_classes
class App.UiElement.file_upload
  @render: (attributeConfig, params, form) ->
    console.log form
    attribute = $.extend(true, {}, attributeConfig)
    attachments = []
    item = $( App.view('generic/file_upload')(attribute: attribute) )
    @form_id = attribute.form_id

    renderFile = (file) ->
      attachments.push file

    item.find('.attachments').on('click', '.js-delete', (e) ->
      id = $(e.currentTarget).data('id')
      attachments = _.filter(
        (item) ->
          return if is id.toString()

      # delete attachment from storage
        type:        'DELETE'
        url:         "#{App.Config.get('api_path')}/asset_attachments/#{@form_id}"
        processData: false

      element = $(e.currentTarget).closest('.attachments')
      if element.find('.attachment').length == 0

    new App.Html5Upload(
      uploadUrl:              "#{App.Config.get('api_path')}/asset_attachments/"
      dropContainer:          item.find("[name=#{}]")
      cancelContainer:        item.find('.js-cancel')
      inputField:             item.find('input')
        form_id: @form_id
      onFileCompletedCallback: (response) ->
      attachmentPlaceholder: item.find('.attachmentPlaceholder')
      attachmentUpload:      item.find('.attachmentUpload')
      progressBar:           item.find('.attachmentUpload-progressBar')
      progressText:          item.find('.js-percentage')
class AssetAttachmentsController < ApplicationController
  prepend_before_action :authentication_check, except: %i[show destroy]
  prepend_before_action :authentication_check_only, only: %i[show destroy]

  def show

    view_type = params[:preview] ? 'preview' : nil
      filename:    download_file.filename,
      type:        download_file.content_type,
      disposition: download_file.disposition

  def create
    file = params[:File]
    content_type = file.content_type

    if !content_type || content_type == 'application/octet-stream'
      content_type = if MIME::Types.type_for(file.original_filename).first

    headers_store = {
      'Content-Type' => content_type

    store = Store.create!(
      object:      'Asset::Attachemnt',
      o_id:        params[:form_id],
      filename:    file.original_filename,
      preferences: headers_store

    render json: {
      success: true,
      data:    {
        filename:    file.original_filename,
        size:        store.size,
        contentType: store.preferences['Content-Type']

  def destroy

    render json: {
      success: true,

  def destroy_form
      object: 'Asset::Attachemnt',
      o_id:   params[:form_id],

    render json: {
      success: true,


  def user_not_authorized(e)

Hi @Caufland ,

I think the issue with your questions are that you are posting very big code snippets which are incomplete and not testable. If you need help you should try to break down your code in a small working code snippet which other developers can implement and test. Even if they are willing to help you, I don’t think that with your code examples this is even possible. And so is my answer, but I will try to give you some infos about the handling of attachments in zammad.

If you want to use attachments zammad-like I think they are working via form_id.

Normally a form has a form_id. It is used to connect attachments to the form you are using to create an object. Zammad is storing the attachments temporarily over this controller:

zammad/app/controllers/upload_caches_controller.rb at 45c9b70f98e952ec8d9dc93009c3ff4fac5468d3 · zammad/zammad · GitHub

With that it is possible to save the current temp state of the creation or the update related to a form_id. On creation of the object then the form_id is used to read the temp data from the upload cache. An example can be found here:
zammad/app/controllers/concerns/creates_ticket_articles.rb at 45c9b70f98e952ec8d9dc93009c3ff4fac5468d3 · zammad/zammad · GitHub

For the model itself AFAIK there is a concern which is helping for the handling of the attachments:
zammad/app/models/application_model/has_attachments.rb at 45c9b70f98e952ec8d9dc93009c3ff4fac5468d3 · zammad/zammad · GitHub

Hope this helps a bit.

Thanks for the answer, next time I will try to improve as to the construction of my questions. I have a question about the uploadCache controller because I don’t see in the code that it has anywhere where it adds items to postgres and yet this happens is it normal behavior or not?

The upload cache controller uses the UploadCache:

zammad/app/controllers/upload_caches_controller.rb at 45c9b70f98e952ec8d9dc93009c3ff4fac5468d3 · zammad/zammad · GitHub

zammad/app/controllers/upload_caches_controller.rb at 45c9b70f98e952ec8d9dc93009c3ff4fac5468d3 · zammad/zammad · GitHub

This is a lib:
zammad/lib/upload_cache.rb at 45c9b70f98e952ec8d9dc93009c3ff4fac5468d3 · zammad/zammad · GitHub

And the foundation of that are the Store & Store::File.

You should find this also in your Postgres, e.g. If you upload a file in the ticket creation mask:


Thanks, everything is correct. And I have one more problem I’m trying to add a preview as the user has in the left menu and I was able to do it only after unviewing the page the preview keeps loading and when I enter the page and change the tab it loads without any problem I get an ERROR, cant find

organization_id App.Organization.find(2) for ‘Asset’ TEest123

error in the console I don’t know what’s going on because I have the relationship done in the asset class but I don’t have it done in the organization class and is there any other option than editing the base organization file because I suspect that’s the problem

Preview works without a problem when I enter it right away:

And this is what it looks like after refreshing the page (and I get that error what I stated above):

class App.AssetOverview extends App.Controller
  @requiredPermission: ['ticket.agent', 'admin.user']

  constructor: (params) ->
    App.Asset.full(@asset_id, @render)

  meta: =>
    meta =
      url: @url()
      id:  @asset_id

    if App.Asset.exists(@asset_id)
      asset = App.Asset.find(@asset_id)
      icon = asset.icon()

      meta.head       = asset.displayName()
      meta.title      = asset.displayName()
      meta.iconClass  = icon     = true

  url: =>
    '#orders/assets/overview/' + @asset_id

  show: =>
    App.OnlineNotification.seen('AssetOverview', @asset_id)
    @navupdate(url: '#', type: 'menu')

  changed: ->

  render: (asset) =>
    if !@doNotLog
      @doNotLog = 1
      @recentView('AssetOverview', @asset_id)

    elLocal = $(App.view('asset_overview/index')())

    new App.AssetOverviewTabs(
      el = elLocal

    @html elLocal

    new App.UpdateTastbar(
      genericObject: asset

  setPosition: (position) =>

  currentPosition: =>

class Router extends App.ControllerPermanent
  @requiredPermission: ['admin.assets']

  constructor: (params) ->

    # check authentication

    # cleanup params
    clean_params = 
      asset_id: params.asset_id

      key: "AssetOverview-#{@asset_id}"
      controller: 'AssetOverview'
      params: clean_params
      show: true

App.Config.set('orders/assets/overview/:asset_id', Router, 'Routes')