sequelize-admin-panel

Admin panel for sequelize ORM


Keywords
admin, sequelize, orm, django
License
GPL-3.0
Install
npm install sequelize-admin-panel@1.0.3

Documentation

Sequelize admin panel

TL;DR

ЗапуститС

git clone https://github.com/eliseevmikhail/sequelize-admin-panel.git
cd sequelize-admin-panel
yarn install && cd demo && yarn install && yarn initdb && yarn start

ΠΈ ΠΎΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ localhost:3000

ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ возмоТности

ΠŸΡ€ΠΎΡΡ‚Π°Ρ настройка

  • ΠŸΡ€ΠΎΡΡ‚ΠΎ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅ sequelizeAdmin ΠΊΠ°ΠΊ middleware ΠΏΠΎ Ρ‚Ρ€Π΅Π±ΡƒΠ΅ΠΌΠΎΠΌΡƒ ΠΏΡƒΡ‚ΠΈ
  • Π’ минимальной ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ достаточно ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‚ΡŒ экзСмпляр sequelize для получСния Ρ€Π°Π±ΠΎΡ‡Π΅ΠΉ Π°Π΄ΠΌΠΈΠ½-ΠΏΠ°Π½Π΅Π»ΠΈ
  • Π˜ΡΡ…ΠΎΠ΄Π½Ρ‹Π΅ ΠΌΠΎΠ΄Π΅Π»ΠΈ Π½Π΅ Ρ‚Ρ€Π΅Π±ΡƒΡŽΡ‚ ΠΌΠΎΠ΄ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ. ВсС Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ находятся Π² наслСдниках класса ModelAdmin

Настройка повСдСния ΠΏΠΎΠ»Π΅ΠΉ

Π’ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ Ρ€Π΅Π½Π΄Π΅Ρ€ΠΈΠ½Π³Π° ΠΏΠΎΠ»Π΅ΠΉ ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ Π² спискС записСй ΠΈ Π½Π° экранС рСдактирования записи

ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° Ρ‚ΠΈΠΏΠΎΠ² ΠΈ ΠΎΡ‚Π½ΠΎΡˆΠ΅Π½ΠΈΠΉ

ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ΡΡ Π±ΠΎΠ»ΡŒΡˆΠΈΠ½ΡΡ‚Π²ΠΎ ΠΏΡ€ΠΈΠΌΠΈΡ‚ΠΈΠ²Π½Ρ‹Ρ… Ρ‚ΠΈΠΏΠΎΠ² Sequelize ΠΈ всС ΠΎΡ‚Π½ΠΎΡˆΠ΅Π½ΠΈΡ (associations)

ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΠΈ ΠΈ ΠΏΡ€Π°Π²Π°

ВстроСнноС ΡƒΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡΠΌΠΈ ΠΈ ΠΏΡ€Π°Π²Π°ΠΌΠΈ доступа Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹

Локализация

ВсС ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄Ρ‹, Π²ΠΊΠ»ΡŽΡ‡Π°Ρ сообщСния, названия ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ, ΠΏΠΎΠ»Π΅ΠΉ ΠΈ дСйствий ΠΏΠ΅Ρ€Π΅Π΄Π°ΡŽΡ‚ΡΡ Π² Π΅Π΄ΠΈΠ½ΠΎΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π΅, Ρ€Π°Π·Π±ΠΈΡ‚ΠΎΠΌ ΠΏΠΎ локалям. БистСма опрСдСляСт Π½ΡƒΠΆΠ½ΡƒΡŽ локаль ΠΏΠΎ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ°ΠΌ Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π°. Π’ случаС отсутствия ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄Π° ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ встроСнный английский для сообщСний ΠΈΠ»ΠΈ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΌΠΎΠ΄Π΅Π»ΠΈ ΠΈΠ»ΠΈ поля для ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ ΠΈ ΠΏΠΎΠ»Π΅ΠΉ соотвСтствСнно.

Быстрый старт

Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚

mkdir sequelize-admin-demo; cd sequelize-admin-demo
yarn init
yarn add express sequelize sequelize-cli sequelize-admin-panel
yarn add mysql2 # ΠΈΠ»ΠΈ Π΄Ρ€ΡƒΠ³ΠΎΠΉ Π°Π΄Π°ΠΏΡ‚Π΅Ρ€
node node_modules/.bin/sequelize init

ΠΎΡ‚Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΡƒΠΉΡ‚Π΅ config/config.json для ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ ΠΊ вашСй Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…, рСкомСндуСтся сразу Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ "define": {"charset": "utf8", "collate": "utf8_general_ci"}, создайтС Π‘Π”

node node_modules/.bin/sequelize db:create

Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ Π³Π»Π°Π²Π½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° index.js

const express = require('express')
const db = require('./models')
const { sequelizeAdmin } = require('sequelize-admin-panel')
const app = express()
app.use('/admin', sequelizeAdmin(express, db.sequelize))
app.listen(process.env.PORT || 3000, () => console.log('Server started'))

создайтС Ρ„Π°ΠΉΠ» cli.js

const db = require('./models')
require('sequelize-admin-panel').cli(db.sequelize)

ΠΎΠ±Ρ€Π°Ρ‚ΠΈΡ‚Π΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, такая Ρ„ΠΎΡ€ΠΌΠ° запуска Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ созданный ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ Ρ„Π°ΠΉΠ» ./models ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ всС ΠΌΠΎΠ΄Π΅Π»ΠΈ. Если Π²Ρ‹ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚Π΅ sequelize-cli, ΡƒΠ±Π΅Π΄ΠΈΡ‚Π΅ΡΡŒ Ρ‡Ρ‚ΠΎ Π½ΡƒΠΆΠ½Ρ‹Π΅ ΠΌΠΎΠ΄Π΅Π»ΠΈ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½Ρ‹ (Π²Ρ‹Π·Π²Π°Π½ Sequelize.define).

Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΈ синхронизируйтС ΠΌΠΎΠ΄Π΅Π»ΠΈ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ sequelize-cli

node node_modules/.bin/sequelize model:generate --name MyModel --attributes name:STRING,count:INTEGER
node node_modules/.bin/sequelize db:migrate
node ./cli init # инициализация Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ

Π»ΠΈΠ±ΠΎ, Ссли Π²Ρ‹ Π½Π΅ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒΡΡ sequelize migration, Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ создайтС Π² ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³Π΅ models Ρ„Π°ΠΉΠ»Ρ‹ ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ Π² соотвСтствии с шаблоном:

module.exports = (sequelize, DataTypes) => {
  const MyModel = sequelize.define(
    'MyModel',
    {
      // <--- имя модСли
      name: DataTypes.STRING,
      count: DataTypes.INTEGER
    },
    {}
  )
  MyModel.associate = function(models) {
    // associations can be defined here
    // like this: MyModel.belongsTo(models.OtherModel)
  }
  return MyModel
}

ΠΈ синхронизируйтС ΠΌΠΎΠ΄Π΅Π»ΠΈ Π²Ρ‹Π·ΠΎΠ²ΠΎΠΌ

node ./cli init --all # очистит всС Π΄Π°Π½Π½Ρ‹Π΅!

НСдостатком ΠΏΠ΅Ρ€Π²ΠΎΠ³ΠΎ способа являСтся Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎΡΡ‚ΡŒ Ρ€ΡƒΡ‡Π½ΠΎΠ³ΠΎ описания ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ схСм Ρ‚Π°Π±Π»ΠΈΡ† Π² Ρ„Π°ΠΉΠ»Π°Ρ… ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΈ, Π²Ρ‚ΠΎΡ€ΠΎΠ³ΠΎ -- сброс содСрТимого Π±Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ… ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π΅ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ. ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½Π΅Π΅ ΠΎ Ρ€Π°Π±ΠΎΡ‚Π΅ с sequelizejs ΠΏΠΎ ссылкС.

ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅: ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Sequelize.sync({alter: true}) ΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΡ‚ΡŒ ΠΊ Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ†ΠΈΠΈ constraints

ЗапуститС сСрвСр node . ΠΈ ΠΎΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Π² Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π΅ http://localhost:3000/admin. Π“ΠΎΡ‚ΠΎΠ²ΠΎ!

Настройка прСдставлСний

Для настройки прСдставлСния ΠΏΠΎΠ»Π΅ΠΉ ΠΌΠΎΠ΄Π΅Π»ΠΈ создайтС наслСдника класса ModelAdmin, Π² Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ init Π·Π°Π΄Π°ΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π±ΡƒΠ΅ΠΌΡ‹Π΅ значСния, Π° Π·Π°Ρ‚Π΅ΠΌ ΠΏΠ΅Ρ€Π΅Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ°Ρ€Ρƒ [MyModel,MyModelAdmin] Π² Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ sequelizeAdmin свойством models Ρ‚Ρ€Π΅Ρ‚ΡŒΠ΅Π³ΠΎ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Π°.

MyModelAdmin.js:

const { ModelAdmin } = require('sequelize-admin-panel')

class MyModelAdmin extends ModelAdmin {
  repr(req, entry) {
    return entry.name
  }

  init() {
    super.init()
    this.list_fields = ['id', 'name', 'count', 'nonExistedField']
    this.list_links = ['id']
    this.search_fields = ['name', 'count']
    this.ordering = ['count', '-name']
    this.list_per_page = 20
    this.editor_fields = ['id', 'name', 'count']
    this.readonly_fields = ['id']
    this.icon = '<span class="oi oi-media-play"></span>'

    this.setFieldDescription('name', {
      view: (req, entry, fieldName) => {
        return 'I love ' + entry.name + '!'
      },
      html: false
    })

    this.setFieldDescription('count', {
      // ΠΌΠΎΠΆΠ½ΠΎ ΠΈ Promise
      view: (req, entry, fieldName) => {
        const model = req.SA.modelAdminInstance.model,
          Sequelize = req.SA.Sequelize
        // Π΄Ρ€ΡƒΠ³ΠΈΠ΅ ΠΌΠΎΠ΄Π΅Π»ΠΈ доступны Ρ‡Π΅Ρ€Π΅Π· req.SA.modelAdminManager.getModelAdminByModelName('model_name').model
        return (
          model
            .find({
              // ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ максимальноС
              attributes: [
                [Sequelize.fn('max', Sequelize.col('count')), 'max_count']
              ]
            })
            // простоС экранированиС
            .then(entry => parseInt(entry.get('max_count'), 10))
            .then(max => `<progress value="${entry.count}" max="${max}">`)
        )
      },
      html: true // остороТно -- Π²Ρ‹Π²ΠΎΠ΄ Π½Π΅ экранируСтся
    })

    // ΠΌΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ сколько ΡƒΠ³ΠΎΠ΄Π½ΠΎ псСвдо-ΠΏΠΎΠ»Π΅ΠΉ
    this.setFieldDescription('nonExistedField', {
      view: (req, entry, fieldName) => {
        return entry.name.toUpperCase() + ' is great!'
      }
    })
  }
}
module.exports = MyModelAdmin

index.js:

...
app.use('/admin', sequelizeAdmin(express, db.sequelize, {
  models: [ [db.MyModel, require('./MyModelAdmin')] ]
}))
...

Π’ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π΅ ΠΏΠΎΠ»Π΅ name Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΈΠ·Π½Π°Π²Π°Ρ‚ΡŒΡΡ Π² любви, count ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ progressbar ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ наибольшСго Π² Ρ‚Π°Π±Π»ΠΈΡ†Π΅ значСния, Π° Ρ‚Ρ€Π΅Ρ‚ΡŒΠ΅ ΠΏΠΎΠ»Π΅, ΠΎΡ‚ΡΡƒΡ‚ΡΠ²ΡƒΡŽΡ‰Π΅Π΅ Π² Ρ‚Π°Π±Π»ΠΈΡ†Π΅, Π·Π°Π½ΠΈΠΌΠ°Ρ‚ΡŒΡΡ восхвалСниСм.

Рассмотрим ΠΏΠΎ ΠΏΡƒΠ½ΠΊΡ‚Π°ΠΌ:

  • Ѐункция repr создаёт прСдставлСниС записи ΠΌΠΎΠ΄Π΅Π»ΠΈ (строки Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹). Оно ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π² Ρ‚Π°Π±Π»ΠΈΡ†Π΅ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² Ссли Π½Π΅ Π·Π°Π΄Π°Π½Ρ‹ поля Π² list_fields, Π° Ρ‚Π°ΠΊΠΆΠ΅ для создания списков ΠΎΡ‚Π½ΠΎΡˆΠ΅Π½ΠΈΠΉ. ΠžΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ΡΡ ΠΊΠ°ΠΊ plain-text, ΠΌΠΎΠΆΠ½ΠΎ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Ρ‚ΡŒ Promise.
  • Бвойства list_fields, list_links, search_fields, ordering Π·Π°Π΄Π°ΡŽΡ‚ соотвСтствСнно ΠΊΠ°ΠΊΠΈΠ΅ поля Π±ΡƒΠ΄ΡƒΡ‚ Π²ΠΈΠ΄Π½Ρ‹ Π² Ρ‚Π°Π±Π»ΠΈΡ†Π΅ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ², ΠΊΠ°ΠΊΠΈΠ΅ ΠΈΠ· Π½ΠΈΡ… ΡΠ²Π»ΡΡŽΡ‚ΡΡ ссылками, ΠΏΠΎ ΠΊΠ°ΠΊΠΈΠΌ осущСствляСтся поиск ΠΈ сортировка ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ (Π² Ρ‚ΠΎΠΌ числС Π² списках ΠΎΡ‚Π½ΠΎΡˆΠ΅Π½ΠΈΠΉ). Надо ΠΎΡ‚ΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ поиск ΠΈ сортировка Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Π° Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠΎ ΠΏΡ€ΠΈΠΌΠΈΡ‚ΠΈΠ²Π½Ρ‹Ρ… Ρ‚ΠΈΠΏΠ°ΠΌ, ΠΏΡ€ΠΈΡΡƒΡ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΌ Π² Π±Π°Π·Π΅ (Ρ‚.Π΅. Π½Π΅ ΠΏΠΎ associations ΠΈ Π½Π΅ ΠΏΠΎ искусствСнным полям).
  • Если Π½ΡƒΠΆΠ½ΠΎ вывСсти всС поля ΠΊΡ€ΠΎΠΌΠ΅ ΡƒΠΊΠ°Π·Π°Π½Π½Ρ‹Ρ…, пСрСчислитС ΠΈΡ… Π² list_exclude. Если Π½ΡƒΠΆΠ½ΠΎ вывСсти Π²ΠΎΠΎΠ±Ρ‰Π΅ всС поля, ΡƒΠΊΠ°ΠΆΠΈΡ‚Π΅ Π² list_exclude Π½Π΅ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π΅ ΠΏΠΎΠ»Π΅. list_fields ΠΏΡ€ΠΈ этом Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ пустым.
  • Π’ list_fields ΠΌΠΎΠΆΠ½ΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Π½Π΅ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π΅ ΠΏΠΎΠ»Π΅, Π° Π·Π°Ρ‚Π΅ΠΌ ΠΎΠΏΠΈΡΠ°Ρ‚ΡŒ Π΅Π³ΠΎ прСдставлСниС.
  • Π’ icon ΠΌΠΎΠΆΠ½ΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ»ΡŒΠ½Ρ‹ΠΉ html, рСкомСндуСтся ΠΈΠΊΠΎΠ½ΠΎΡ‡Π½Ρ‹ΠΉ ΡˆΡ€ΠΈΡ„Ρ‚.

Π”Π°Π»Π΅Π΅, Π²Ρ‹Π·ΠΎΠ²ΠΎΠΌ setFieldDescription ΠΌΡ‹ пСрСопрСдСляСм Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ Ρ€Π΅Π½Π΄Π΅Ρ€ΠΈΠ½Π³Π° прСдставлСния поля строки Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ ΠΈ ΡƒΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ, слСдуСт Π»ΠΈ Π΅Ρ‘ Π²Ρ‹Π²ΠΎΠ΄ ΠΈΠ½Ρ‚Π΅Ρ€ΠΏΡ€Π΅Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΊΠ°ΠΊ html. НС Π·Π°Π±Ρ‹Π²Π°ΠΉΡ‚Π΅ ΠΎΠ± экранировании.

Π›Π΅Π³ΠΊΠΎ Π·Π°ΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ Π²Ρ‹Π·ΠΎΠ² view Π΄Π΅Π»Π°Π΅Ρ‚ ΠΎΠ΄Π½Ρƒ ΠΈ Ρ‚Ρƒ ΠΆΠ΅ Ρ€Π°Π±ΠΎΡ‚Ρƒ ΠΏΠΎ Π²Ρ‹Ρ‡ΠΈΡΠ»Π΅Π½ΠΈΡŽ максимального значСния. ΠŸΡ€Π°Π²ΠΈΠ»ΡŒΠ½Ρ‹ΠΌ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ΠΌ Π±ΡƒΠ΄Π΅Ρ‚ вынСсти Π΅Ρ‘ Π² ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ коллбэк beforeListRender. ΠžΠ±Ρ€Π°Ρ‚ΠΈΡ‚Π΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ пСрСдаётся ΠΊΠ°ΠΊ свойство ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° req:

class MyModelAdmin extends ModelAdmin {
...
  beforeListRender(req, count, entries) {
    const model = req.SA.modelAdminInstance.model,
      Sequelize = req.SA.Sequelize
    return model.find({
        // ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ максимальноС
        attributes: [
          [Sequelize.fn('max', Sequelize.col('count')), 'max_count']
        ]
      })
      // простоС экранированиС ΠΈ ΠΊΡΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅
      .then(entry => req.MAX = parseInt(entry.get('max_count'), 10))
  }
...
  init() {
    ...
    this.setFieldDescription('count', {
      view: (req, entry, fieldName) =>
        `<progress value="${entry.count}" max="${req.MAX}">`,
      html: true
    })
    ...
  }

Настройка Π²ΠΈΠ΄ΠΆΠ΅Ρ‚ΠΎΠ²

Π€ΡƒΠ½ΠΊΡ†ΠΈΠΈ setFieldDescription ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‚ΡŒ свойство widget, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ Ρ€Π΅Π½Π΄Π΅Ρ€ΠΈΠ½Π³Π° Π²ΠΈΠ΄ΠΆΠ΅Ρ‚Π° с сигнатурой (req, entry, fieldName, value, options) ΠΈ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°ΡŽΡ‰Π΅ΠΉ html-ΠΊΠΎΠ΄ Π²ΠΈΠ΄ΠΆΠ΅Ρ‚Π°.

НапримСр, напишСм Π²ΠΈΠΆΠ΄Π΅Ρ‚ для поля count Π² Ρ„ΠΎΡ€ΠΌΠ΅ ΠΏΠΎΠ»Π·ΡƒΠ½ΠΊΠ°

    this.setFieldDescription('count', {
      ...
      widget: (req, entry, fieldName, value, options) =>
      `<input type="range" min="0" max="100" step="1"
        class="form-control"
        ${options.readOnly ? 'disabled' : ''}
        name=${fieldName}
        value=${value} />`
    })

ΠžΠ±Ρ€Π°Ρ‚ΠΈΡ‚Π΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, Π½ΡƒΠΆΠ½ΠΎ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ name=fieldName, ΠΆΠ΅Π»Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ value ΠΈ Ρ„Π»Π°Π³ readonly. Класс form-control ΠΈΠ· bootstrap растягиваСт Π²ΠΈΠ΄ΠΆΠ΅Ρ‚ ΠΏΠΎ ΡˆΠΈΡ€ΠΈΠ½Π΅ ΠΈ Π² ΠΎΠ±Ρ‰Π΅ΠΌ случаС Π½Π΅ обязатСлСн.

Если Π²ΠΈΠ΄ΠΆΠ΅Ρ‚ слоТнСС поля Π²Π²ΠΎΠ΄Π°, слСдуСт ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ скрытый input ΠΈ ΠΏΠΎΠΌΠ΅Ρ‰Π°Ρ‚ΡŒ Π² Π½Π΅Π³ΠΎ Π°ΠΊΡ‚ΡƒΠ°Π»ΡŒΠ½ΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Π½Π° клиСнтской сторонС. НапримСр, ΠΏΡƒΡΡ‚ΡŒ ΠΏΠΎΠ»Π΅ point ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΎ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ:

point: {
  type: DataTypes.STRING,
  allowNull: false,
  defaultValue: '55.75027920193085,37.622483042183035',
}

Ρ‚ΠΎΠ³Π΄Π° Π²ΠΈΠ΄ΠΆΠ΅Ρ‚ с яндСкс-ΠΊΠ°Ρ€Ρ‚ΠΎΠΉ ΠΌΠΎΠΆΠ½ΠΎ ΠΎΠΏΠΈΡΠ°Ρ‚ΡŒ Ρ‚Π°ΠΊ:

widget: (req, entry, fieldName, value) => {
  return `
      <!-- hidden form field -->
      <input type=hidden name="${fieldName}" value=${value} />
      <div style='width: 100%; height: 240px; border: solid black 1px' id="${fieldName}_mapid"></div>
      <script>
        function ${fieldName}_map() {
          var coord = '${value}'.split(',')
          var map = new ymaps.Map("${fieldName}_mapid", {
            center: coord, 
            zoom: 7
          });
          var placemark = new ymaps.Placemark(coord);
          map.events.add('click', function (e) {
            var coords = e.get('coords');
            placemark.geometry.setCoordinates(coords);
            // setup hidden form field
            document.getElementsByName("${fieldName}")[0].value=coords.join(',')
          });
          map.geoObjects.add(placemark);
        }
        ymaps.ready(${fieldName}_map);
      </script>`
}

Π§Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΡƒ яндСкс ΠΊΠ°Ρ€Ρ‚ Π² ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ init Π²Ρ‹Π·ΠΎΠ²ΠΈΡ‚Π΅

this.addExtraResource(
  '<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script>'
)

Π»ΠΈΠ±ΠΎ

// js Π² ΠΊΠΎΠ½Ρ†Π΅ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌ парсСру
this.addExtraResource('https://api-maps.yandex.ru/2.1/?lang=ru_RU#js')

Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΏΡ€ΠΈ любом Π½Π°ΠΆΠ°Ρ‚ΠΈΠΈ Π½Π° ΠΊΠ°Ρ€Ρ‚Ρƒ сСриализованныС ΠΊΠΎΠΎΡ€Π΄ΠΈΠ½Π°Ρ‚Ρ‹ ΠΏΠΎΠΏΠ°Π΄Π°ΡŽΡ‚ Π² скрытый input, ΠΈ ΠΏΠ΅Ρ€Π΅Π΄Π°ΡŽΡ‚ΡΡ ΠΏΡ€ΠΈ сохранСнии Π½Π° сСрвСр с ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½Ρ‹ΠΌ ΠΈΠΌΠ΅Π½Π΅ΠΌ поля. ΠžΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎ стоит ΠΎΡ‚ΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ ΠΏΡ€ΠΈ создании Π»ΡŽΠ±Ρ‹Ρ… id Π»ΡƒΡ‡ΡˆΠ΅ Π³Π΅Π½Π΅Ρ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π΅Π³ΠΎ с участиСм ΠΈΠΌΠ΅Π½ΠΈ поля (${fieldName}_mapid) Π²ΠΎ ΠΈΠ·Π±Π΅ΠΆΠ°Π½ΠΈΠ΅ ΠΊΠΎΠ»Π»ΠΈΠ·ΠΈΠΉ.

Настройка сСттСров

ΠŸΡ€ΠΈ сохранСнии записи ΠΌΠΎΠ΄Π΅Π»ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠ΅ Π΅Ρ‘ ΠΏΠΎΠ»Π΅ сохраняСтся сСттСром Π² соотвСтствии с Π΅Ρ‘ Ρ‚ΠΈΠΏΠΎΠΌ. ΠŸΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ сохранСния ΠΌΠΎΠΆΠ½ΠΎ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ свойтства setter ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² addFieldsDescriptions ΠΈ setFieldDescription. Π’ случаС ΠΏΡ€ΠΈΠΌΠΈΡ‚ΠΈΠ²Π½ΠΎΠ³ΠΎ поля, сСттСр ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ выглядит Ρ‚Π°ΠΊ:

function defaultSetter(req, entry, fieldName, transaction) {
  entry[fieldName] = req.body[fieldName]
}

Π’Π°ΠΊ ΠΆΠ΅ ΠΌΠΎΠΆΠ΅Ρ‚ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Ρ‚ΡŒ Promise. Π’ сСттСрС ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠΌΠ΅ΡΡ‚ΠΈΡ‚ΡŒ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΡƒΡŽ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΡƒ:

function setter(req, entry, fieldName, transaction) {
  if (req.body[fieldName] % 10 === 0) entry[fieldName] = req.body[fieldName]
  else throw req.SA.getSequelizeError('multiples of ten', fieldName)
}

Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹Π΅ пСрСопрСдСлСния

МоТно ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ ΠΏΡ€ΠΎΠΌΠ΅ΠΆΡƒΡ‚ΠΎΡ‡Π½ΠΎΠ³ΠΎ наслСдника класса ModelAdmin, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Ρ€Π΅Π½Π΄Π΅Ρ€ΠΈΠ½Π³Π° ΠΈ сСттСр для Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ‚ΠΈΠΏΠΎΠ², Π² Ρ‚ΠΎΠΌ числС Π½Π΅ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΡ…. А Π·Π°Ρ‚Π΅ΠΌ ΡΡΡ‹Π»Π°Ρ‚ΡŒΡΡ Π½Π° ΠΈΠΌΠ΅Π½Π° этих Ρ‚ΠΈΠΏΠΎΠ² вмСсто опрСдСлСния Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ Π² свойствах view, widget ΠΈ setter ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² addFieldsDescriptions ΠΈ setFieldDescription:

class CustomTypes extends ModelAdmin {
  init() {
    super.init()
    this.overrideTypeView('X_FILEBLOB', (req, entry, fieldName) => {
      return entry[fieldName]
        ? req.SA.tr('File size:') + entry[fieldName].length
        : req.SA.tr('No file')
    })
    this.overrideTypeWidget(
      'X_FILEBLOB',
      (req, entry, fieldName, value, options) => {
        return `<input type="file" class="form-control" ${
          options.readOnly ? 'disabled' : ''
        } name="${fieldName}" />`
      }
    )
    this.overrideTypeSetter(
      'X_FILEBLOB',
      (req, entry, fieldName, transaction) => {
        return new Promise((resolve, reject) => {
          const file = req.files[fieldName]
          if (file.size > 1048576)
            reject(req.SA.getSequelizeError('file length', fieldName))
          fs.readFile(file.path, (err, buf) => {
            if (err) reject(err)
            else {
              entry[fieldName] = buf
              resolve()
            }
          })
        })
      }
    )
  }
}

class OverridedAdmin extends CustomTypes {
  init() {
    super.init()
    this.addFieldDescriptions({
      file: {
        view: 'X_FILEBLOB',
        widget: 'X_FILEBLOB',
        setter: 'X_FILEBLOB'
      }
    })
  }
}

ДСйствия (actions)

Для массовой ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² Π² Ρ‚Π°Π±Π»ΠΈΡ†Π΅ записСй Π²Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ Π·Π°Π΄Π°Ρ‚ΡŒ action.

Для этого ΠΏΠ΅Ρ€Π΅Π΄Π°ΠΉΡ‚Π΅ массив дСйствий Π² свойство ModelAdmin.actions. Допустим, ΠΌΡ‹ Ρ…ΠΎΡ‚ΠΈΠΌ ΠΈΠΌΠ΅Ρ‚ΡŒ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ ΠΎΠ±Π½ΡƒΠ»ΡΡ‚ΡŒ ΠΏΠΎΠ»Π΅ count

this.actions = [
  {
    name: 'zerofy',
    renderer: (req, res, modelAdmin, ids, exit) => {
      let transaction
      return req.SA.sequelizeInstance
        .transaction()
        .then(_transaction => (transaction = _transaction))
        .then(() =>
          modelAdmin.model.update(
            { count: 0 },
            { where: { [modelAdmin.pkName]: ids }, transaction }
          )
        )
        .then(() => transaction.commit())
        .catch(() => transaction.rollback())
        .then(() => exit())
    }
  }
]

Π’Π΅ΠΏΠ΅Ρ€ΡŒ достаточно ΠΎΡ‚ΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ чСкбоксы ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΡ… записСй ΠΈ Π²Ρ‹Π±Ρ€Π°Ρ‚ΡŒ ΠΏΡƒΠ½ΠΊΡ‚ zerofy ΠΈΠ· Π²Ρ‹ΠΏΠ°Π΄Π°ΡŽΡ‰Π΅Π³ΠΎ списка Π² Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ΅ ΠΊΠΎΠ»ΠΎΠ½ΠΊΠΈ с чСкбоксами. По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ Ρ‚Π°ΠΌ присутствуСт Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΡƒΠ½ΠΊΡ‚ delete.

Коллбэк renderer позволяСт ΠΏΠΎΠ»Π½ΠΎΡΡ‚ΡŒΡŽ ΡƒΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ΠΌ ΠΈ ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄Π°ΠΌΠΈ ΠΌΠ΅ΠΆΠ΄Ρƒ страницами, ΠΊΠ°ΠΊ это сдСлано Π² дСйствии delete, Π½ΠΎ Π² Π΄Π°Π½Π½ΠΎΠΌ случаС явно ΠΈΠ·Π±Ρ‹Ρ‚ΠΎΡ‡Π΅Π½. ВмСсто Π½Π΅Π³ΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π² свойствС changer ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ простой коллбэк, ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°ΡŽΡ‰ΠΈΠΉ записи ΠΏΠΎ ΠΎΠ΄Π½ΠΎΠΉ:

this.actions = [
  {
    name: 'zerofy',
    changer: entry => (entry.count = 0)
  }
]

НС всСгда ΠΎΠΏΡ‚ΠΈΠΌΠ°Π»ΡŒΠ½ΠΎ, Π½ΠΎ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Ρ‘Π½Π½ΠΎ Π½Π°ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΡ€ΠΎΡ‰Π΅.

TODO: Π²ΠΎΠ·Π²Ρ€Π°Ρ‚ ошибок ΠΈ affected rows

ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² запроса ΠΈ сквозныС ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹

ПослС рСдактирования записи ΠΌΠΎΠ΄Π΅Π»ΠΈ ΠΆΠ΅Π»Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ Π²Π΅Ρ€Π½ΡƒΡ‚ΡŒΡΡ ΠΊ просмотру списка записСй Π² Ρ‚ΠΎΠΌ ΠΆΠ΅ состоянии, Π²ΠΊΠ»ΡŽΡ‡Π°Ρ ΠΏΠ°Π³ΠΈΠ½Π°Ρ†ΠΈΡŽ, поиск ΠΈ Ρ‚.Π΄. Для упрощСния этого, Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ автоматичСски ΠΏΠ΅Ρ€Π΅Π΄Π°Π²Π°Ρ‚ΡŒΡΡ ΠΏΡ€ΠΈ Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ. Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½ΠΎ это ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ. Π•ΡΡ‚ΡŒ ΠΏΡ€Π΅Π΄ΠΎΠΏΡ€Π΅Π΄Π΅Π»Ρ‘Π½Π½Ρ‹ΠΉ список ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² ΠΈ функция, которая создаёт URL ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄Π° Π²ΠΊΠ»ΡŽΡ‡Π°ΡŽΡ‰Π΅Π³ΠΎ всС эти ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ Π² сСриализованном Π²ΠΈΠ΄Π΅ Π² свойствС params. НапримСр, Ссли ΠΌΡ‹ находся Π½Π° страницС /admin/model/modelName/?search=тСст, ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄Π΅ Π½Π° Π²Ρ‚ΠΎΡ€ΡƒΡŽ страницу Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² поиска, URL ссылки ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄Π° Π±ΡƒΠ΄Π΅Ρ‚ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ /admin/model/modelName?params={"search":"тСст","page":1}.

ВсС URL ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ΠΎΠ² Π²Π½ΡƒΡ‚Ρ€ΠΈ Π°Π΄ΠΌΠΈΠ½-ΠΏΠ°Π½Π΅Π»ΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡΠΎΠ·Π΄Π°Π²Π°Ρ‚ΡŒΡΡ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠ΅ΠΉ req.SA.queryExtender(), ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°ΡŽΡ‰Π΅ΠΉ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹:

  • массив частСй ΠΏΡƒΡ‚ΠΈ ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ корня Π°Π΄ΠΌΠΈΠ½-ΠΏΠ°Π½Π΅Π»ΠΈ ['model', 'modelName']
  • ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ с Π½ΠΎΠ²Ρ‹ΠΌΠΈ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°ΠΌΠΈ {page: 1}
  • массив ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ², ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½Π΅ слСдуСт Π²ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ Π² запрос ΠΈΠ»ΠΈ false для ΠΈΡΠΊΠ»ΡŽΡ‡Π½ΠΈΡ всСх.

Π’ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π΅ Π²Ρ‹ΡˆΠ΅ΡƒΠΏΠΎΠΌΡΠ½ΡƒΡ‚Π°Ρ ссылка ΠΏΠ°Π³ΠΈΠ½Π°Ρ†ΠΈΠΈ выглядит Ρ‚Π°ΠΊ (ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ pug):

a(href=req.SA.queryExtender(["model", req.SA.modelName], {page: data.page+1}, []) >>

Аналогично, ΠΏΡ€ΠΈ использовании Ρ„ΠΎΡ€ΠΌ, Π² Ρ„ΠΎΡ€ΠΌΡƒ слСдуСт Π²ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ ΠΏΠΎΠ»Π΅ сгСнСрированноС req.SA.formExtender с Ρ‚Π΅ΠΌΠΈ ΠΆΠ΅ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Π°ΠΌΠΈ ΠΊΡ€ΠΎΠΌΠ΅ ΠΏΠ΅Ρ€Π²ΠΎΠ³ΠΎ

//- action Ρ„ΠΎΡ€ΠΌΡ‹ создаётся Π±Π΅Π· ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² запроса
form(action=req.SA.queryExtender(["model", req.SA.modelName], {}, false), enctype="multipart/form-data")
  //- ΠΏΠΎΠ»Π΅ Ρ„ΠΎΡ€ΠΌΡ‹
  input(type="text", name="search", value=req.SA.params.search)
  //- ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΊΡ€ΠΎΠΌΠ΅ search ΠΈ page вставлСны Π² Ρ„ΠΎΡ€ΠΌΡƒ
  | !{req.SA.formExtender({}, ["search", "page"])}

На Ρ‡Ρ‚ΠΎ слСдуСт ΠΎΠ±Ρ€Π°Ρ‚ΠΈΡ‚ΡŒ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅:

  • action Ρ„ΠΎΡ€ΠΌΡ‹ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ²
  • Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ поля Ρ„ΠΎΡ€ΠΌΡ‹ бСрётся ΠΈΠ· req.SA.params.search
  • ΠΏΡ€ΠΈ Π²Ρ‹Π·ΠΎΠ²Π΅ req.SA.formExtender Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ явно ΠΈΡΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ свойство search, ΠΈΠ½Π°Ρ‡Π΅ послС очистки ΠΏΠΎΠ»Π΅ поиска Π±ΡƒΠ΄Π΅Ρ‚ использовано староС Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅
  • page очищаСтся ΠΈΠ· Ρ‚Π΅Ρ… сообраТСний, Ρ‡Ρ‚ΠΎ Π½ΠΎΠΌΠ΅Ρ€ Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΉ страницы послС измСнСния Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² Π²Ρ‹Π²ΠΎΠ΄Π° Π½Π΅ ΠΈΠΌΠ΅Π΅Ρ‚ смысла

ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½Π΅Π΅ ΠΎ req.SA.params.search: Π² Π½Π΅Π³ΠΎ ΠΏΠΎΠΏΠ°Π΄Π°ΡŽΡ‚ содСрТимоС дСсСриализованного поля params, ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ запроса, ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ Ρ„ΠΎΡ€ΠΌ Π² порядкС пСрСкрытия.

Бписок сквозных ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ²: ['sort', 'page', 'search', 'subwindow', 'action', 'entryIds', 'backurl']

Валидация

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ коррСктности Π²Π²ΠΎΠ΄Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ… Π»Π΅ΠΆΠ°Ρ‚ Π½Π° sequelize, поэтому ΠΆΠ΅Π»Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ ΠΎΠΏΠΈΡΡ‹Π²Π°Ρ‚ΡŒ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ ΠΏΡ€ΠΈ объявлСнии поля. ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½Π΅Π΅ ΠΏΠΎ ссылкС

БобствСнныС ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠΌΠ΅ΡΡ‚ΠΈΡ‚ΡŒ Π² сСттСр.

Локализация

ЗапуститС созданный Ρ€Π°Π½Π΅Π΅ cli.js для получСния ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄ΠΎΠ²

node cli dumptranslation [--empty] [--hints] > translations/[локаль].json

ΠžΡ‚Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΡƒΠΉΡ‚Π΅ локаль, ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄Ρ‹ сообщСний, ΠΈΠΌΠ΅Π½Π° ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ, ΠΏΠΎΠ»Π΅ΠΉ ΠΈ дСйствий. ΠŸΠΎΠ²Ρ‚ΠΎΡ€ΠΈΡ‚ΡŒ ΠΏΠΎ количСству Π»ΠΎΠΊΠ°Π»Π΅ΠΉ. Π’Π°ΠΊ ΠΆΠ΅ Π²Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ Π·Π°Π΄Π°Ρ‚ΡŒ подсказки для ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ ΠΈ ΠΏΠΎΠ»Π΅ΠΉ, Π² ΠΏΠ΅Ρ€Π²ΠΎΠΌ случаС Ρ‡Π΅Ρ€Π΅Π· свойство hint, Π²ΠΎ Π²Ρ‚ΠΎΡ€ΠΎΠΌ Ρ‡Π΅Ρ€Π΅Π· ΠΎΠ΄Π½ΠΎΠΈΠΌΠ΅Π½Π½ΠΎΠ΅ с ΠΈΠΌΠ΅Π½Π΅ΠΌ поля свойство с Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π½Ρ‹ΠΌ суффиксом _hint

  "sequelize_admin_user": {
    "label": "",
    "plural": "",
    "hint": "подсказка модСли",
    "fields": {
      "username": "",
      "username_hint": "подсказка поля",
  ...

Π‘Π³Ρ€ΡƒΠΏΠΏΠΈΡ€ΡƒΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄Ρ‹ ΠΈ ΠΏΠ΅Ρ€Π΅Π΄Π°ΠΉΡ‚Π΅ свойство translation Π² Ρ‚Ρ€Π΅Ρ‚ΠΈΠΉ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ sequelizeAdmin:

app.use(
  '/admin',
  sequelizeAdmin(express, db.sequelize, {
    translation: Object.assign(
      {},
      require('./translations/ru'),
      require('./translations/de')
    )
  })
)

Автоматизация ΠΈΠΌΠΏΠΎΡ€Ρ‚Π° содСрТимого ΠΏΠ°ΠΏΠΊΠΈ translations Π½Π° вашС усмотрСниС.

TODO: ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄ ошибок

Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡΠΌΠΈ ΠΈ ΠΏΡ€Π°Π²Π°ΠΌΠΈ

Для создания ΡΡƒΠΏΠ΅Ρ€ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚Π΅

node cli createsuperuser Π»ΠΎΠ³ΠΈΠ½ ΠΏΠ°Ρ€ΠΎΠ»ΡŒ

Π’ Ρ€Π΅ΠΆΠΈΠΌΠ΅ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ (process.env.NODE_ENV !== 'production') это Π½Π΅ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ, ΠΏΡ€ΠΈ отстутствии ΡΡƒΠΏΠ΅Ρ€ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ осущСствляСтся автоматичСский Π²Ρ…ΠΎΠ΄ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ EMERGENCY ΠΈ выводится ΠΏΡ€Π΅Π΄ΡƒΠΏΡ€Π΅ΠΆΠ΄Π΅Π½ΠΈΠ΅.

Π’ список ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π° sequelize_admin_users, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ пСрСчислСны всС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΠΈ, Π° Ρ‚Π°ΠΊΠΆΠ΅ Π΅ΡΡ‚ΡŒ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ управлСния ΠΏΡ€Π°Π²Π°ΠΌΠΈ доступа ΠΊ модСлям согласно CRUD.

ΠžΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΡ

  • подразумСваСтся Ρ‡Ρ‚ΠΎ primary key являСтся числовым
  • Ρ€Π°Π±ΠΎΡ‚Π° c paranoid option Π½Π΅ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΡΠ»Π°ΡΡŒ
  • для парсинга Ρ„ΠΎΡ€ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ formidable, ΠΆΠ΅Π»Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ enctype="multipart/form-data". Настройки парсСра ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‚ΡŒ Π² свойствС formidableOpts Ρ‚Ρ€Π΅Ρ‚ΡŒΠ΅Π³ΠΎ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π° sequelizeAdmin
  • трСбуСтся NodeJS Π½Π΅ Π½ΠΈΠΆΠ΅ 6.0.0 вСрсии, ΠΏΡ€ΠΎΠ²Π΅Ρ€ΡΠ»ΠΎΡΡŒ Π½Π° 6.14.2LTS ΠΈ 8.11.2LTS
  • сСссии хранятся Π² памяти