mirror of
https://github.com/strapi/strapi.git
synced 2025-12-28 23:57:32 +00:00
Merge branch 'master' into docs-app-update
This commit is contained in:
commit
27f47d6eea
@ -129,6 +129,7 @@ module.exports = {
|
||||
'/3.0.0-beta.x/guides/deployment',
|
||||
'/3.0.0-beta.x/guides/jwt-validation',
|
||||
'/3.0.0-beta.x/guides/error-catching',
|
||||
'/3.0.0-beta.x/guides/slug',
|
||||
'/3.0.0-beta.x/guides/webhooks',
|
||||
],
|
||||
},
|
||||
|
||||
BIN
docs/3.0.0-beta.x/assets/guides/slug/fields.png
Normal file
BIN
docs/3.0.0-beta.x/assets/guides/slug/fields.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/3.0.0-beta.x/assets/guides/slug/layout-after.png
Normal file
BIN
docs/3.0.0-beta.x/assets/guides/slug/layout-after.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/3.0.0-beta.x/assets/guides/slug/layout-before.png
Normal file
BIN
docs/3.0.0-beta.x/assets/guides/slug/layout-before.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/3.0.0-beta.x/assets/guides/slug/layout-config.png
Normal file
BIN
docs/3.0.0-beta.x/assets/guides/slug/layout-config.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
@ -4,48 +4,50 @@
|
||||
|
||||
Models are a representation of the database's structure and lifecycle. They are split into two separate files. A JavaScript file that contains the lifecycle callbacks, and a JSON one that represents the data stored in the database and their format. The models also allow you to define the relationships between them.
|
||||
|
||||
**Path —** `./api/user/models/User.js`.
|
||||
**Path —** `./api/restaurant/models/Restaurant.js`.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// Before saving a value.
|
||||
// Fired before an `insert` or `update` query.
|
||||
beforeSave: next => {
|
||||
// Use `this` to get your current object
|
||||
next();
|
||||
},
|
||||
beforeSave: (model, attrs, options) => {},
|
||||
|
||||
// After saving a value.
|
||||
// Fired after an `insert` or `update` query.
|
||||
afterSave: (doc, next) => {
|
||||
next();
|
||||
},
|
||||
afterSave: (model, attrs, options) => {},
|
||||
|
||||
// ... and more
|
||||
};
|
||||
```
|
||||
|
||||
**Path —** `./api/user/models/User.settings.json`.
|
||||
**Path —** `./api/restaurant/models/Restaurant.settings.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"connection": "default",
|
||||
"info": {
|
||||
"name": "user",
|
||||
"description": "This represents the User Model"
|
||||
"name": "restaurant",
|
||||
"description": "This represents the Restaurant Model"
|
||||
},
|
||||
"attributes": {
|
||||
"firstname": {
|
||||
"cover": {
|
||||
"collection": "file",
|
||||
"via": "related",
|
||||
"plugin": "upload"
|
||||
},
|
||||
"name": {
|
||||
"default": "",
|
||||
"type": "string"
|
||||
},
|
||||
"lastname": {
|
||||
"type": "string"
|
||||
"description": {
|
||||
"default": "",
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this example, there is a `User` model which contains two attributes `firstname` and `lastname`.
|
||||
In this example, there is a `Restaurant` model which contains two attributes `cover`, `name` and `description`.
|
||||
|
||||
### Where are the models defined?
|
||||
|
||||
@ -57,12 +59,12 @@ The models are defined in each `./api/**/models/` folder. Every JavaScript or JS
|
||||
If you are just starting out it is very convenient to generate some models with the Content Type Builder, directly in the admin interface. You can then review the generated model mappings on the code level. The UI takes over a lot of validation tasks and gives you a fast feeling for available features.
|
||||
:::
|
||||
|
||||
Use the CLI, and run the following command `strapi generate:model user firstname:string lastname:string`. Read the [CLI documentation](../cli/CLI.md) for more information.
|
||||
Use the CLI, and run the following command `strapi generate:model restaurant name:string description:text`. Read the [CLI documentation](../cli/CLI.md) for more information.
|
||||
|
||||
This will create two files located at `./api/user/models`:
|
||||
This will create two files located at `./api/restaurant/models`:
|
||||
|
||||
- `User.settings.json`: contains the list of attributes and settings. The JSON format makes the file easily editable.
|
||||
- `User.js`: imports `User.settings.json` and extends it with additional settings and lifecycle callbacks.
|
||||
- `Restaurant.settings.json`: contains the list of attributes and settings. The JSON format makes the file easily editable.
|
||||
- `Restaurant.js`: imports `Restaurant.settings.json` and extends it with additional settings and lifecycle callbacks.
|
||||
|
||||
::: note
|
||||
when you create a new API using the CLI (`strapi generate:api <name>`), a model is automatically created.
|
||||
@ -76,18 +78,18 @@ Additional settings can be set on models:
|
||||
- `collectionName` (string) - Collection's name (or table's name) in which the data should be stored.
|
||||
- `globalId` (string) -Global variable name for this model (case-sensitive).
|
||||
|
||||
**Path —** `User.settings.json`.
|
||||
**Path —** `Restaurant.settings.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"connection": "mongo",
|
||||
"collectionName": "Users_v1",
|
||||
"globalId": "Users",
|
||||
"collectionName": "Restaurants_v1",
|
||||
"globalId": "Restaurants",
|
||||
"attributes": {}
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the model `User` will be accessible through the `Users` global variable. The data will be stored in the `Users_v1` collection or table and the model will use the `mongo` connection defined in `./config/environments/**/database.json`
|
||||
In this example, the model `Restaurant` will be accessible through the `Restaurants` global variable. The data will be stored in the `Restaurants_v1` collection or table and the model will use the `mongo` connection defined in `./config/environments/**/database.json`
|
||||
|
||||
::: note
|
||||
The `connection` value can be changed whenever you want, but you should be aware that there is no automatic data migration process. Also if the new connection doesn't use the same ORM you will have to rewrite your queries.
|
||||
@ -101,12 +103,12 @@ The info key on the model-json states information about the model. This informat
|
||||
- `description`: The description of the model.
|
||||
- `mainField`: Determines which model-attribute is shown when displaying the model.
|
||||
|
||||
**Path —** `User.settings.json`.
|
||||
**Path —** `Restaurant.settings.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"info": {
|
||||
"name": "user",
|
||||
"name": "restaurant",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
@ -137,6 +139,7 @@ The following types are currently available:
|
||||
|
||||
- `string`
|
||||
- `text`
|
||||
- `richtext`
|
||||
- `integer`
|
||||
- `biginteger`
|
||||
- `float`
|
||||
@ -172,45 +175,24 @@ To improve the Developer eXperience when developing or using the administration
|
||||
|
||||
### Example
|
||||
|
||||
**Path —** `User.settings.json`.
|
||||
**Path —** `Restaurant.settings.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"connection": "default",
|
||||
"info": {
|
||||
"name": "user",
|
||||
"description": "This represents the User Model",
|
||||
"mainField": "email"
|
||||
},
|
||||
...
|
||||
"attributes": {
|
||||
"firstname": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastname": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "email",
|
||||
"required": true,
|
||||
"title": {
|
||||
"type": "string",
|
||||
"min": 3,
|
||||
"max": 99,
|
||||
"unique": true
|
||||
},
|
||||
"password": {
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"private": true
|
||||
"description": {
|
||||
"default": "My descrioption",
|
||||
"type": "text",
|
||||
"required": true
|
||||
},
|
||||
"about": {
|
||||
"type": "description"
|
||||
},
|
||||
"age": {
|
||||
"type": "integer",
|
||||
"min": 18,
|
||||
"max": 99,
|
||||
"index": true
|
||||
},
|
||||
"birthday": {
|
||||
"type": "date"
|
||||
}
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -243,21 +225,6 @@ A `pet` can be owned by someone (a `user`).
|
||||
}
|
||||
```
|
||||
|
||||
**Path —** `./api/pet/controllers/Pet.js`.
|
||||
|
||||
```js
|
||||
// Mongoose example
|
||||
module.exports = {
|
||||
findPetsWithOwners: async ctx => {
|
||||
// Retrieve the list of pets with their owners.
|
||||
const pets = Pet.find().populate('owner');
|
||||
|
||||
// Send the list of pets.
|
||||
ctx.body = pets;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
@ -309,36 +276,6 @@ A `user` can have one `address`. And this address is only related to this `user`
|
||||
}
|
||||
```
|
||||
|
||||
**Path —** `./api/user/controllers/User.js`.
|
||||
|
||||
```js
|
||||
// Mongoose example
|
||||
module.exports = {
|
||||
findUsersWithAddresses: async ctx => {
|
||||
// Retrieve the list of users with their addresses.
|
||||
const users = User.find().populate('address');
|
||||
|
||||
// Send the list of users.
|
||||
ctx.body = users;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Path —** `./api/adress/controllers/Address.js`.
|
||||
|
||||
```js
|
||||
// Mongoose example
|
||||
module.exports = {
|
||||
findArticlesWithUsers: async ctx => {
|
||||
// Retrieve the list of addresses with their users.
|
||||
const articles = Address.find().populate('user');
|
||||
|
||||
// Send the list of addresses.
|
||||
ctx.body = addresses;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
@ -390,36 +327,6 @@ A `user` can have many `articles`, and an `article` can be related to one `user`
|
||||
}
|
||||
```
|
||||
|
||||
**Path —** `./api/user/controllers/User.js`.
|
||||
|
||||
```js
|
||||
// Mongoose example
|
||||
module.exports = {
|
||||
findUsersWithArticles: async ctx => {
|
||||
// Retrieve the list of users with their articles.
|
||||
const users = User.find().populate('articles');
|
||||
|
||||
// Send the list of users.
|
||||
ctx.body = users;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Path —** `./api/article/controllers/Article.js`.
|
||||
|
||||
```js
|
||||
// Mongoose example
|
||||
module.exports = {
|
||||
findArticlesWithAuthors: async ctx => {
|
||||
// Retrieve the list of articles with their authors.
|
||||
const articles = Article.find().populate('author');
|
||||
|
||||
// Send the list of users.
|
||||
ctx.body = articles;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Examples**
|
||||
|
||||
```js
|
||||
@ -486,36 +393,6 @@ A `product` can be related to many `categories`, so a `category` can have many `
|
||||
}
|
||||
```
|
||||
|
||||
**Path —** `./api/product/controllers/Product.js`.
|
||||
|
||||
```js
|
||||
// Mongoose example
|
||||
module.exports = {
|
||||
findProductsWithCategories: async ctx => {
|
||||
// Retrieve the list of products.
|
||||
const products = Product.find().populate('categories');
|
||||
|
||||
// Send the list of products.
|
||||
ctx.body = products;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Path —** `./api/category/controllers/Category.js`.
|
||||
|
||||
```js
|
||||
// Mongoose example
|
||||
module.exports = {
|
||||
findCategoriesWithProducts: async ctx => {
|
||||
// Retrieve the list of categories.
|
||||
const categories = Category.find().populate('products');
|
||||
|
||||
// Send the list of categories.
|
||||
ctx.body = categories;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
@ -644,102 +521,6 @@ A `Image` model might belongs to many either `Article` models or a `Product` mod
|
||||
}
|
||||
```
|
||||
|
||||
**Path —** `./api/image/controllers/Image.js`.
|
||||
|
||||
```js
|
||||
// Mongoose example
|
||||
module.exports = {
|
||||
findFiles: async ctx => {
|
||||
// Retrieve the list of images with the Article or Product entries related to them.
|
||||
const images = Images.find().populate('related');
|
||||
|
||||
/*
|
||||
[{
|
||||
"_id": "5a81b0fa8c063a53298a934a",
|
||||
"url": "http://....",
|
||||
"name": "john_doe_avatar.png",
|
||||
"related": [{
|
||||
"_id": "5a81b0fa8c063a5393qj934a",
|
||||
"title": "John Doe is awesome",
|
||||
"description": "..."
|
||||
}, {
|
||||
"_id": "5a81jei389ns5abd75f79c",
|
||||
"name": "A simple chair",
|
||||
"description": "..."
|
||||
}]
|
||||
}]
|
||||
*/
|
||||
|
||||
// Send the list of files.
|
||||
ctx.body = images;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Path —** `./api/article/controllers/Article.js`.
|
||||
|
||||
```js
|
||||
// Mongoose example
|
||||
module.exports = {
|
||||
findArticlesWithAvatar: async ctx => {
|
||||
// Retrieve the list of articles with the avatar (image).
|
||||
const articles = Article.find().populate('avatar');
|
||||
|
||||
/*
|
||||
[{
|
||||
"_id": "5a81b0fa8c063a5393qj934a",
|
||||
"title": "John Doe is awesome",
|
||||
"description": "...",
|
||||
"avatar": {
|
||||
"_id": "5a81b0fa8c063a53298a934a",
|
||||
"url": "http://....",
|
||||
"name": "john_doe_avatar.png"
|
||||
}
|
||||
}]
|
||||
*/
|
||||
|
||||
// Send the list of users.
|
||||
ctx.body = articles;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Path —** `./api/product/controllers/Product.js`.
|
||||
|
||||
```js
|
||||
// Mongoose example
|
||||
module.exports = {
|
||||
findProductWithPictures: async ctx => {
|
||||
// Retrieve the list of products with the pictures (images).
|
||||
const products = Product.find().populate('pictures');
|
||||
|
||||
/*
|
||||
[{
|
||||
"_id": "5a81jei389ns5abd75f79c",
|
||||
"name": "A simple chair",
|
||||
"description": "...",
|
||||
"pictures": [{
|
||||
"_id": "5a81b0fa8c063a53298a934a",
|
||||
"url": "http://....",
|
||||
"name": "chair_position_1.png"
|
||||
}, {
|
||||
"_id": "5a81d22bee1ad45abd75f79c",
|
||||
"url": "http://....",
|
||||
"name": "chair_position_2.png"
|
||||
}, {
|
||||
"_id": "5a81d232ee1ad45abd75f79e",
|
||||
"url": "http://....",
|
||||
"name": "chair_position_3.png"
|
||||
}]
|
||||
}]
|
||||
*/
|
||||
|
||||
// Send the list of users.
|
||||
ctx.body = products;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### Database implementation
|
||||
|
||||
If you're using MongoDB as a database, you don't need to do anything. Everything is natively handled by Strapi. However, to implement a polymorphic relationship with SQL databases, you need to create two tables.
|
||||
|
||||
107
docs/3.0.0-beta.x/guides/slug.md
Normal file
107
docs/3.0.0-beta.x/guides/slug.md
Normal file
@ -0,0 +1,107 @@
|
||||
# Create a slug system
|
||||
|
||||
This guide will explain how to create a slug system for a Post, Article or any Content Type you want.
|
||||
|
||||
## Create attributes
|
||||
|
||||
To start building your slug system you need a `string` field as a **base** for your slug, in this example we will use `title`.
|
||||
|
||||
You will also need another `string` field that contains the slugified value of your `title`, in this case we will use `slug`.
|
||||
|
||||

|
||||
|
||||
## Configure the layout for the content editor
|
||||
|
||||
Let's configure the layout of the **edit page** to make it more user friendly for the content editor.
|
||||
|
||||
- Click on the **Content Manager** link in the left menu.
|
||||
- Then on the `Article` Content Type.
|
||||
- And finally on the **Edit View** tab.
|
||||
|
||||
Here we will be able to setup the `slug` field.
|
||||
|
||||
- Click on the `slug` field.
|
||||
- At the bottom of the page, edit the **placeholder** value to `Generated automatically based on the title`.
|
||||
- And click **OFF** for **Editable field** option.
|
||||
- Don't forget to save your updates.
|
||||
|
||||
:::: tabs cache-lifetime="10" :options="{ useUrlFragment: false }"
|
||||
|
||||
::: tab "Edit View before" id="before"
|
||||
|
||||

|
||||
|
||||
:::
|
||||
|
||||
::: tab "Edit View after" id="after"
|
||||
|
||||

|
||||
|
||||
:::
|
||||
|
||||
::: tab "Edit View configuration" id="config"
|
||||
|
||||

|
||||
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
## Auto create/update the `slug` attribute
|
||||
|
||||
For that you will have to install `slugify` node module in your application.
|
||||
|
||||
When it's done, you have to update the life cycle of the **Article** Content Type to auto complete the `slug` field.
|
||||
|
||||
**Path —** `./api/article/models/Article.js`
|
||||
|
||||
:::: tabs cache-lifetime="10" :options="{ useUrlFragment: false }"
|
||||
|
||||
::: tab "Mongoose" id="mongoose"
|
||||
|
||||
```js
|
||||
const slugify = require('slugify');
|
||||
|
||||
module.exports = {
|
||||
beforeSave: async model => {
|
||||
if (model.title) {
|
||||
model.slug = slugify(model.title);
|
||||
}
|
||||
},
|
||||
beforeUpdate: async model => {
|
||||
if (model.getUpdate().title) {
|
||||
model.update({
|
||||
slug: slugify(model.getUpdate().title),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: tab "Bookshelf" id="bookshelf"
|
||||
|
||||
```js
|
||||
const slugify = require('slugify');
|
||||
|
||||
module.exports = {
|
||||
beforeSave: async (model, attrs, options) => {
|
||||
if (options.method === 'insert' && attrs.title) {
|
||||
model.set('slug', slugify(attrs.title));
|
||||
} else if (options.method === 'update' && attrs.title) {
|
||||
attrs.slug = slugify(attrs.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
## Fetch article by `slug`
|
||||
|
||||
Then you will have to be able to fetch your **Articles** by this slug.
|
||||
|
||||
You will be able to find your articles by slug with this request `GET /articles?slug=my-article-slug`
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user