SlideShare une entreprise Scribd logo
HOW TO BUILD A SITE USING
NICK
A NEARLY HEADLESS CMS
Plone Conference 2023, Eibar
-
Rob Gietema @robgietema
ABOUT ME
WHAT WILL WE COVER?
What is Nick?
Why?
Architecture
Bootstrap a project
Configuration file
i18n
Logging
Profiles
Contenttypes
Behaviors
Initial Content
Permissions, Users,
Groups & Workflows
Vocabularies
Catalog & Search
Events
Controlpanels
Tests
Docs
WHAT WILL WE COVER?
WHAT IS NICK?
Headless CMS
Build with Node.js
RESTfull API compatible with plone.restapi (Volto)
WHY?
Fun to build!
Plone has a great architecture, great way to learn the
internals
Plone has a great Rest API
Started as a proof of concept on Ploneconf 2018 in
Tokyo
Frontend and backend using the same language
ISSUES WITH PLONE
Disclaimer: my opinion
Lots of legacy code
Lot of code to maintain ourself
Deployment
COMPLEX STACK
Python
Zope
Generic Setup (xml)
ZCML
Page templates
REST
Yaml
JSON
cfg
ini
Markdown
Javascript
Webpack
CSS / LESS / SASS
XSLT
Buildout
KSS
Portal Skins
Restricted Python
DTML
ARCHITECTURE
LANGUAGES
Javascript
JSON
Markdown
STORAGE
Postgres (Transactional, JSON integration, (text)
indexing)
Knex.js ( )
Objection.js ( )
knexjs.org
vincit.github.io/objection.js/
BLOBS
myproject
└─ var
└─ blobstorage
└─ 1d2362de-8090-472b-a06a-0e4d23705f3c
└─ 2bd8d8f2-6d01-4f39-a799-a521acd17dbf
└─ 5e178390-2cf6-498b-9bb3-424c1aa4dea3
└─ ...
WEBSITE
https://nickcms.org
ONLINE DEMO
https://demo.nickcms.org
DOCUMENTATION
https://docs.nickcms.org
CONTRIBUTE
https://github.com/robgietema/nick
GETTING STARTED
CREATE THE DATABASE
CREATE DATABASE myproject;
CREATE USER myproject WITH ENCRYPTED PASSWORD 'myproject';
GRANT ALL PRIVILEGES ON DATABASE myproject TO myproject;
YEOMAN GENERATOR
$ npm install -g yo
$ npm install -g @robgietema/generator-nick
$ yo @robgietema/nick myproject
BOOTSTRAP & START
$ cd myproject
$ yarn bootstrap
$ yarn start
CONFIG
myproject/src/config.js
export const config = {
connection: {
port: 5432,
host: 'localhost',
database: 'myproject',
user: 'myproject',
password: 'myproject',
},
blobsDir: `${__dirname}/var/blobstorage`,
port: 8000,
secret: 'secret',
clientMaxSize: '64mb',
systemUsers: ['admin', 'anonymous'],
systemGroups: ['Owner'],
cors: {
VOLTO CONFIG
import '@plone/volto/config';
import applyAddons from './config-addons.js';
export default function applyConfig(config) {
config.settings.devProxyToApiPath = 'http://localhost:8000';
config.settings.proxyRewriteTarget = new String('');
return applyAddons(config);
}
I18N
I18N
myproject
└─ locales
└─ en
└─ LC_MESSAGES
└─ myproject.po
└─ nl
└─ LC_MESSAGES
└─ myproject.po
└─ myproject.po
└─ en.json
└─ nl.json
I18N
$ yarn i18n
I18N IN JS
req.i18n('Translate me please!')
I18N IN JSON
{
"id": "talk",
"title:i18n": "Talk",
"description:i18n": "Content type for a talk.",
...
}
LOGGING (LOG4JS)
log.info('My message!')
2022-05-30T22:21:06.317 INFO [my-file.js:12] My message!
DEMO
http://localhost:3000
SEEDS / PROFILES
myproject
└─ src
└─ profiles
└─ default
└─ types
└─ schedule.js
└─ talk.js
└─ groups.js
└─ permissions.js
└─ ...
$ yarn seed
$ yarn reset
CONTENTTYPES
CONTENTTYPES
myproject
└─ src
└─ profiles
└─ default
└─ types
└─ schedule.json
└─ talk.json
SCHEDULE.JSON
{
"id": "Schedule",
"title:i18n": "Schedule",
"description:i18n": "Schedule for a conference.",
"global_allow": true,
"filter_content_types": true,
"allowed_content_types": ["Talk"],
"schema": {
"fieldsets": [
{
"fields": ["year"],
"id": "default",
"title:i18n": "Default"
}
],
TALK.JSON
{
"id": "Talk",
"title:i18n": "Talk",
"description:i18n": "Content type for a talk.",
"global_allow": false,
"filter_content_types": true,
"allowed_content_types": [],
"schema": {
"fieldsets": [
{
"fields": ["title", "description"],
"id": "default",
"title:i18n": "Default"
}
],
BEHAVIORS
myproject
└─ src
└─ profiles
└─ default
└─ behaviors
└─ author.json
AUTHOR.JSON
{
"id": "author",
"title:i18n": "Author Information",
"description:i18n": "Adds firstname, lastname, bio and pictu
"schema": {
"fieldsets": [
{
"fields": ["firstname", "lastname", "bio", "picture"],
"id": "default",
"title:i18n": "Default"
}
],
"properties": {
"firstname": {
"title:i18n": "Firstname",
NESTED BEHAVIORS
{
"id": "author",
"title:i18n": "Author Information",
"description:i18n": "Adds name and bio fields.",
"schema": {
"behaviors": ["name", "bio"]
}
}
BEHAVIORS (CLASS BASED)
myproject
└─ src
└─ behaviors
└─ id_title_from_year
└─ id_title_from_year.js
└─ index.js
DOCUMENT MODEL
/**
* Document Model.
* @module models/document/document
*/
/**
* A model for Document.
* @class Document
* @extends Model
*/
export class Document extends Model {
...
/**
ID_TITLE_FROM_YEAR.JS
/**
* Id and title from year behavior.
* @module behaviors/id_title_from_year/id_title_from_year
*/
import { uniqueId } from '@robgietema/nick/src/helpers/utils/u
/**
* Id and title from year behavior.
* @constant id_title_from_year
*/
export const id_title_from_year = {
/**
* Set id
* @method setId
SRC/BEHAVIORS/INDEX.JS
/**
* Point of contact for behaviors.
* @module behaviors
*/
import { id_title_from_year } from './id_title_from_year/id_ti
const behaviors = {
id_title_from_year,
};
export default behaviors;
CONFIG
import behaviors from './src/behaviors';
export const config = {
connection: {
port: 5432,
host: 'localhost',
database: 'myproject',
user: 'myproject',
password: 'myproject',
},
blobsDir: `${__dirname}/var/blobstorage`,
port: 8000,
secret: 'secret',
clientMaxSize: '64mb',
systemUsers: ['admin', 'anonymous'],
INITIAL CONTENT
INITIAL CONTENT
myproject
└─ src
└─ profiles
└─ default
└─ documents
└─ schedule-2023.json
└─ schedule-2023.nick.json
└─ schedule-2023.documentation.json
└─ images
└─ rob.png
└─ steve.jpg
INITIAL CONTENT
profiles/default/documents/schedule-2023.json
{
"uuid": "405ca717-0c68-43a0-88ac-629a82658675",
"type": "Schedule",
"year": 2023,
"owner": "admin",
"workflow_state": "published"
}
INITIAL CONTENT
profiles/default/documents/schedule-2023.nick.json
{
"uuid": "605ca717-0c68-43a0-88ac-629a82658675",
"type": "Talk",
"title": "How to Build a Site Using Nick",
"description": "Nick is a nearly headless CMS written in Nod
"firstname": "Rob",
"lastname": "Gietema",
"bio": "Rob is a frontend webdeveloper for over 25 years. He
"picture": "/images/rob.png",
"length": "Long",
"level": "Beginner",
"owner": "robgietema",
"workflow_state": "approved"
}
VERSIONS
profiles/default/documents/schedule-2023.nick.json
{
"uuid": "605ca717-0c68-43a0-88ac-629a82658675",
"type": "Talk",
"title": "How to Build a Site Using Nick",
"description": "Nick is a nearly headless CMS written in Nod
"firstname": "Rob",
"lastname": "Gietema",
"bio": "Rob is a frontend webdeveloper for over 25 years. He
"picture": "/images/rob.png",
"length": "Long",
"level": "Beginner",
"owner": "robgietema",
"workflow_state": "approved"
"workflow_history": [
{
REDIRECTS
profiles/default/redirects.json
{
"purge": true,
"redirects": [{
"path": "/talks-2023",
"document": "405ca717-0c68-43a0-88ac-629a82658675"
}]
}
PERMISSION SYSTEM
Permissions
Roles (have permissions)
Groups (have roles)
Users (have roles, groups)
Local roles (user/group has a role on an object)
Local role permissions are inherited from the parent
Local role inheritence can be disabled per object
Workflows (have states and transitions)
States (have permissions per role)
Transitions (have permissions)
PERMISSIONS.JSON (GLOBAL)
{
"purge": false,
"permissions": [
{
"id": "View",
"title:i18n": "View"
},
{
"id": "Add",
"title:i18n": "Add"
},
{
"id": "Login",
"title:i18n": "Login"
},
PERMISSIONS.JSON (PROJECT)
{
"purge": false,
"permissions": [
{
"id": "Submit Talk",
"title:i18n": "Submit Talk"
},
{
"id": "Approve Talk",
"title:i18n": "Approve Talk"
}
]
}
ROLES.JSON (GLOBAL)
{
"purge": false,
"roles": [
{
"id": "Anonymous",
"title:i18n": "Anonymous",
"permissions": ["Login", "Register"]
},
{
"id": "Authenticated",
"title:i18n": "Authenticated",
"permissions": ["Logout", "Manage Preferences"]
},
{
"id": "Owner",
ROLES.JSON (PROJECT)
{
"purge": false,
"roles": [
{
"id": "Speaker",
"title:i18n": "Speaker",
"permissions": ["Submit Talk"]
},
{
"id": "Program Manager",
"title:i18n": "Program Manager",
"permissions": ["Approve Talk"]
}
]
}
USERS.JSON (PROJECT)
{
"purge": false,
"users": [
{
"id": "robgietema",
"password": "robgietema",
"fullname": "Rob Gietema",
"email": "robgietema@nickcms.org",
"groups": ["Speakers"]
},
{
"id": "admin",
"password": "admin",
"fullname": "Admin",
"email": "admin@nickcms.org",
GROUPS.JSON (PROJECT)
{
"purge": false,
"groups": [
{
"id": "Speakers",
"title:i18n": "Speakers",
"description:i18n": "",
"email": "",
"roles": ["Speaker"]
}
]
}
WORKFLOWS.JSON
{
"purge": false,
"workflows": [
{
"id": "talk_workflow",
"title:i18n": "Talk Workflow",
"description:i18n": "Workflow for talk submission and ap
"json": {
"initial_state": "submitted",
"states": {
"submitted": {
"title:i18n": "Submitted",
"description:i18n": "Talk has been submitted.",
"transitions": ["approve", "reject"],
"permissions": {
TALK.JSON
{
"id": "Talk",
"title:i18n": "Talk",
"description:i18n": "Content type for a talk.",
"global_allow": false,
"filter_content_types": true,
"allowed_content_types": [],
"schema": {
"fieldsets": [
{
"fields": ["title", "description"],
"id": "default",
"title:i18n": "Default"
}
],
SHARING
myproject/src/profiles/default/documents/schedule-2023.json
{
"uuid": "405ca717-0c68-43a0-88ac-629a82658675",
"type": "Schedule",
"year": 2023,
"owner": "admin",
"workflow_state": "published",
"sharing": {
"users": [
{
"id": "robgietema",
"roles": ["Reader"]
}
],
"groups": [
{
VOCABULARIES
VOCABULARIES
myproject
└─ src
└─ vocabularies
└─ talk-levels
└─ talk-levels.js
└─ index.js
VOCABULARY
/**
* Talk levels vocabulary.
* @module vocabularies/talk-levels/talk-levels
*/
import { objectToVocabulary } from '@robgietema/nick/src/helpe
/**
* Returns the talk levels vocabulary.
* @method talkLevels
* @returns {Array} Array of terms.
*/
export async function talkLevels(req, trx) {
// Return terms
return objectToVocabulary({
SRC/VOCABULARIES/INDEX.JS
/**
* Vocabularies.
* @module vocabularies
*/
import { talkLevels } from './talk-levels/talk-levels';
const vocabularies = {
'talk-levels': talkLevels,
};
export default vocabularies;
CONFIG
import behaviors from './src/behaviors';
import vocabularies from './src/vocabularies';
export const config = {
connection: {
port: 5432,
host: 'localhost',
database: 'myproject',
user: 'myproject',
password: 'myproject',
},
blobsDir: `${__dirname}/var/blobstorage`,
port: 8000,
secret: 'secret',
clientMaxSize: '64mb',
PROFILE VOCABULARIES
myproject
└─ src
└─ profiles
└─ default
└─ vocabularies
└─ talk-length.json
PROFILE VOCABULARY
{
"id": "talk-length",
"title:i18n": "Talk Length",
"items": [
{ "title:i18n": "Short", "token": "Short" },
{ "title:i18n": "Long", "token": "Long" }
]
}
TALK.JSON
{
"id": "Talk",
"title:i18n": "Talk",
"description:i18n": "Content type for a talk.",
"global_allow": false,
"filter_content_types": true,
"allowed_content_types": [],
"schema": {
"fieldsets": [
{
"fields": ["title", "description", "length", "level"],
"id": "default",
"title:i18n": "Default"
}
],
SEARCH & CATALOG
CATALOG
Indexes
type (path, uuid, integer, date, text, string,
boolean, string[])
operators
Metadata
name
type
attribute
CATALOG.JSON (PROJECT)
{
"indexes": [
{
"name": "author",
"type": "string",
"attr": "author",
"title:i18n": "Author",
"description:i18n": "The author's name",
"group": "Text",
"enabled": false,
"sortable": true,
"operators": {
"string.contains": {
"title:i18n": "Contains",
"description:i18n": "",
BEHAVIORS (INDEXES)
myproject
└─ src
└─ behaviors
└─ author_index
└─ author_index.js
└─ total_time_index
└─ total_time_index.js
...
AUTHOR INDEX
/**
* Author index behavior.
* @module behaviors/author_index/author_index
*/
/**
* Author index behavior.
* @constant author_index
*/
export const author_index = {
/**
* Get author
* @method author
* @param {Object} trx Transaction object.
* @returns {String} author
TOTALTIME INDEX
/**
* Total time index behavior.
* @module behaviors/total_time_index/total_time_index
*/
import { map } from 'lodash';
/**
* Total time index behavior.
* @constant total_time_index
*/
export const total_time_index = {
/**
* Get total time
* @method totalTime
SRC/BEHAVIORS/INDEX.JS
/**
* Point of contact for behaviors.
* @module behaviors
*/
import { author_index } from './author_index/author_index';
import { id_title_from_year } from './id_title_from_year/id_ti
import { total_time_index } from './total_time_index/total_tim
const behaviors = {
author_index,
id_title_from_year,
total_time_index,
};
SCHEDULE.JSON
{
"id": "Schedule",
"title:i18n": "Schedule",
"description:i18n": "Schedule for a conference.",
"global_allow": true,
"filter_content_types": true,
"allowed_content_types": ["Talk"],
"schema": {
"fieldsets": [
{
"fields": ["year"],
"id": "default",
"title:i18n": "Default"
}
],
TALK.JSON
{
"id": "Talk",
"title:i18n": "Talk",
"description:i18n": "Content type for a talk.",
"global_allow": false,
"filter_content_types": true,
"allowed_content_types": [],
"schema": {
"fieldsets": [
{
"fields": ["title", "description"],
"id": "default",
"title:i18n": "Default"
}
],
EVENTS
EVENTS
onBeforeAdd
onAfterAdd
onAfterModified
onBeforeCopy
...
EVENT FUNCTION
onBeforeAdd(context, trx, ...params)
EVENTS
myproject
└─ src
└─ events
└─ reindex_parent_on_modified
└─ reindex_parent_on_modified.js
└─ index.js
EVENT
/**
* Reindex parent on modified
* @module events/reindex_parent_on_modified
*/
const reindex_parent_on_modified = {
onAfterModified: async (context, trx) => {
if (context.type !== 'Talk') return;
// Fetch parent
if (!context._parent) {
await context.fetchRelated('_parent', trx);
}
// Reindex parent
SRC/EVENTS/INDEX.JS
/**
* Point of contact for events.
* @module events
* @example import events from './events';
*/
import events from '@robgietema/nick/src/events';
import reindex_parent_on_modified from './reindex_parent_on_mo
events.register(reindex_parent_on_modified);
export default events;
CONFIG
import behaviors from './src/behaviors';
import events from './src/events';
import vocabularies from './src/vocabularies';
export const config = {
connection: {
port: 5432,
host: 'localhost',
database: 'myproject',
user: 'myproject',
password: 'myproject',
},
blobsDir: `${__dirname}/var/blobstorage`,
port: 8000,
secret: 'secret',
CONTROLPANELS
CONTROLPANELS
myproject
└─ src
└─ profiles
└─ default
└─ controlpanels
└─ venue.json
CONTROLPANEL
{
"id": "venue",
"title:i18n": "Venue",
"group": "General",
"schema": {
"fieldsets": [
{
"behavior": "plone",
"fields": [
"name",
"address",
"website"
],
"id": "default",
"title": "Default"
GETTINGS SETTINGS
import { Controlpanel } from '@robgietema/nick/src/models';
const controlpanel = await Controlpanel.fetchById('venue');
const config = controlpanel.data;
config.name
.address
.website
NAVIGATION CONTROLPANEL
{
"id": "navigation",
"title:i18n": "Navigation",
"group": "General",
"schema": {
"fieldsets": [
{
"fields": ["displayed_types", "additional_items"],
"id": "general",
"title": "General"
}
],
"properties": {
"displayed_types": {
"additionalItems": true,
OVERWRITE SETTINGS
{
"id": "navigation",
"data": {
"displayed_types": ["Folder", "Page", "Schedule"]
}
}
TESTING
TESTING
myproject
└─ docs
└─ examples
└─ types
└─ schedule.req
└─ schedule.res
└─ src
└─ tests
└─ types
└─ types.test.js
SCHEDULE.REQ
GET /@types/Schedule HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
SCHEDULE.RES
HTTP/1.1 200 OK
Content-Type: application/json
{
"required": [
"year"
],
"fieldsets": [
{
"id": "default",
"title": "Default",
"fields": [
"year"
]
}
TYPES.TEST.JS
import app from '@robgietema/nick/src/app';
import { testRequest } from '@robgietema/nick/src/helpers';
describe('Types', () => {
it('should return the schedule type', () =>
testRequest(app, 'types/schedule'));
});
TEST RUNNER
$ yarn test
DOCS
DOCS
myproject
└─ docs
└─ index.md
└─ types.md
INDEX.MD
---
layout: default
nav_exclude: true
---
# My Project
## Introduction
My awesome project!
TYPES.MD
---
nav_order: 1
permalink: /types
---
# Types
## Get the schema with GET
To get the schema of a content type, access the `/@types` endp
```
{% include_relative examples/types/schedule.req %}
```
DOCS
QUESTIONS?
Want to implement a site using Nick? Talk to me!
slideshare.net/robgietema/nick-ploneconf-2023
github.com/robgietema/nick-example
How to Build a Site Using Nick

Contenu connexe

Similaire à How to Build a Site Using Nick

web2py:Web development like a boss
web2py:Web development like a bossweb2py:Web development like a boss
web2py:Web development like a boss
Francisco Ribeiro
 
Nick: A Nearly Headless CMS
Nick: A Nearly Headless CMSNick: A Nearly Headless CMS
Nick: A Nearly Headless CMS
Rob Gietema
 
Tame Accidental Complexity with Ruby and MongoMapper
Tame Accidental Complexity with Ruby and MongoMapperTame Accidental Complexity with Ruby and MongoMapper
Tame Accidental Complexity with Ruby and MongoMapper
Giordano Scalzo
 
Exploring MORE Google (Cloud) APIs with Python
Exploring MORE Google (Cloud) APIs with PythonExploring MORE Google (Cloud) APIs with Python
Exploring MORE Google (Cloud) APIs with Python
wesley chun
 
There's more than web
There's more than webThere's more than web
There's more than webMatt Evans
 
OSMC 2023 | Experiments with OpenSearch and AI by Jochen Kressin & Leanne La...
OSMC 2023 | Experiments with OpenSearch and AI by Jochen Kressin &  Leanne La...OSMC 2023 | Experiments with OpenSearch and AI by Jochen Kressin &  Leanne La...
OSMC 2023 | Experiments with OpenSearch and AI by Jochen Kressin & Leanne La...
NETWAYS
 
Sinatra and JSONQuery Web Service
Sinatra and JSONQuery Web ServiceSinatra and JSONQuery Web Service
Sinatra and JSONQuery Web Service
vvatikiotis
 
Scalable web application architecture
Scalable web application architectureScalable web application architecture
Scalable web application architecture
postrational
 
Guillotina: The Asyncio REST Resource API
Guillotina: The Asyncio REST Resource APIGuillotina: The Asyncio REST Resource API
Guillotina: The Asyncio REST Resource API
Nathan Van Gheem
 
OpenAI API crash course
OpenAI API crash courseOpenAI API crash course
OpenAI API crash course
Dimitrios Platis
 
Dev Jumpstart: Build Your First App with MongoDB
Dev Jumpstart: Build Your First App with MongoDBDev Jumpstart: Build Your First App with MongoDB
Dev Jumpstart: Build Your First App with MongoDB
MongoDB
 
Crafting Evolvable Api Responses
Crafting Evolvable Api ResponsesCrafting Evolvable Api Responses
Crafting Evolvable Api Responses
darrelmiller71
 
Three Years of Lessons Running Potentially Malicious Code Inside Containers
Three Years of Lessons Running Potentially Malicious Code Inside ContainersThree Years of Lessons Running Potentially Malicious Code Inside Containers
Three Years of Lessons Running Potentially Malicious Code Inside Containers
Ben Hall
 
Python and MongoDB
Python and MongoDBPython and MongoDB
Python and MongoDB
Christiano Anderson
 
Let's build Developer Portal with Backstage
Let's build Developer Portal with BackstageLet's build Developer Portal with Backstage
Let's build Developer Portal with Backstage
Opsta
 
Constance et qualité du code dans une équipe - Rémi Prévost
Constance et qualité du code dans une équipe - Rémi PrévostConstance et qualité du code dans une équipe - Rémi Prévost
Constance et qualité du code dans une équipe - Rémi Prévost
Web à Québec
 
Intro to node and mongodb 1
Intro to node and mongodb   1Intro to node and mongodb   1
Intro to node and mongodb 1Mohammad Qureshi
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and Python
PiXeL16
 
Switch to Backend 2023
Switch to Backend 2023Switch to Backend 2023

Similaire à How to Build a Site Using Nick (20)

web2py:Web development like a boss
web2py:Web development like a bossweb2py:Web development like a boss
web2py:Web development like a boss
 
Nick: A Nearly Headless CMS
Nick: A Nearly Headless CMSNick: A Nearly Headless CMS
Nick: A Nearly Headless CMS
 
Tame Accidental Complexity with Ruby and MongoMapper
Tame Accidental Complexity with Ruby and MongoMapperTame Accidental Complexity with Ruby and MongoMapper
Tame Accidental Complexity with Ruby and MongoMapper
 
Exploring MORE Google (Cloud) APIs with Python
Exploring MORE Google (Cloud) APIs with PythonExploring MORE Google (Cloud) APIs with Python
Exploring MORE Google (Cloud) APIs with Python
 
There's more than web
There's more than webThere's more than web
There's more than web
 
OSMC 2023 | Experiments with OpenSearch and AI by Jochen Kressin & Leanne La...
OSMC 2023 | Experiments with OpenSearch and AI by Jochen Kressin &  Leanne La...OSMC 2023 | Experiments with OpenSearch and AI by Jochen Kressin &  Leanne La...
OSMC 2023 | Experiments with OpenSearch and AI by Jochen Kressin & Leanne La...
 
Sinatra and JSONQuery Web Service
Sinatra and JSONQuery Web ServiceSinatra and JSONQuery Web Service
Sinatra and JSONQuery Web Service
 
Scalable web application architecture
Scalable web application architectureScalable web application architecture
Scalable web application architecture
 
Guillotina: The Asyncio REST Resource API
Guillotina: The Asyncio REST Resource APIGuillotina: The Asyncio REST Resource API
Guillotina: The Asyncio REST Resource API
 
OpenAI API crash course
OpenAI API crash courseOpenAI API crash course
OpenAI API crash course
 
Dev Jumpstart: Build Your First App with MongoDB
Dev Jumpstart: Build Your First App with MongoDBDev Jumpstart: Build Your First App with MongoDB
Dev Jumpstart: Build Your First App with MongoDB
 
Crafting Evolvable Api Responses
Crafting Evolvable Api ResponsesCrafting Evolvable Api Responses
Crafting Evolvable Api Responses
 
Three Years of Lessons Running Potentially Malicious Code Inside Containers
Three Years of Lessons Running Potentially Malicious Code Inside ContainersThree Years of Lessons Running Potentially Malicious Code Inside Containers
Three Years of Lessons Running Potentially Malicious Code Inside Containers
 
Python and MongoDB
Python and MongoDBPython and MongoDB
Python and MongoDB
 
Let's build Developer Portal with Backstage
Let's build Developer Portal with BackstageLet's build Developer Portal with Backstage
Let's build Developer Portal with Backstage
 
Constance et qualité du code dans une équipe - Rémi Prévost
Constance et qualité du code dans une équipe - Rémi PrévostConstance et qualité du code dans une équipe - Rémi Prévost
Constance et qualité du code dans une équipe - Rémi Prévost
 
Intro to node and mongodb 1
Intro to node and mongodb   1Intro to node and mongodb   1
Intro to node and mongodb 1
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and Python
 
Django
DjangoDjango
Django
 
Switch to Backend 2023
Switch to Backend 2023Switch to Backend 2023
Switch to Backend 2023
 

Plus de Rob Gietema

Van klimhal naar Big Wall: Bergsportdag 2024
Van klimhal naar Big Wall: Bergsportdag 2024Van klimhal naar Big Wall: Bergsportdag 2024
Van klimhal naar Big Wall: Bergsportdag 2024
Rob Gietema
 
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Rob Gietema
 
Van Klimhal naar Big Wall
Van Klimhal naar Big WallVan Klimhal naar Big Wall
Van Klimhal naar Big Wall
Rob Gietema
 
Van 0 naar 6000+
Van 0 naar 6000+Van 0 naar 6000+
Van 0 naar 6000+
Rob Gietema
 
How to create your own Volto site!
How to create your own Volto site!How to create your own Volto site!
How to create your own Volto site!
Rob Gietema
 
Volto Extensibility Story: Plone Conference 2018
Volto Extensibility Story: Plone Conference 2018Volto Extensibility Story: Plone Conference 2018
Volto Extensibility Story: Plone Conference 2018
Rob Gietema
 
Volto: Plone Conference 2018
Volto: Plone Conference 2018Volto: Plone Conference 2018
Volto: Plone Conference 2018
Rob Gietema
 
React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15
Rob Gietema
 
React Native: React Meetup 3
React Native: React Meetup 3React Native: React Meetup 3
React Native: React Meetup 3
Rob Gietema
 
React Router: React Meetup XXL
React Router: React Meetup XXLReact Router: React Meetup XXL
React Router: React Meetup XXL
Rob Gietema
 
Four o Four: World Plone Day 2014
Four o Four: World Plone Day 2014Four o Four: World Plone Day 2014
Four o Four: World Plone Day 2014
Rob Gietema
 
Hackathon: Silicon Alley Lightning Talks
Hackathon: Silicon Alley Lightning TalksHackathon: Silicon Alley Lightning Talks
Hackathon: Silicon Alley Lightning Talks
Rob Gietema
 
Resource Registries: Plone Conference 2014
Resource Registries: Plone Conference 2014Resource Registries: Plone Conference 2014
Resource Registries: Plone Conference 2014
Rob Gietema
 
Arnhem Sprint 2013: Plone Conference 2013
Arnhem Sprint 2013: Plone Conference 2013Arnhem Sprint 2013: Plone Conference 2013
Arnhem Sprint 2013: Plone Conference 2013
Rob Gietema
 
Plone 5: Nederlandse Plone Gebruikersdag 2014
Plone 5: Nederlandse Plone Gebruikersdag 2014Plone 5: Nederlandse Plone Gebruikersdag 2014
Plone 5: Nederlandse Plone Gebruikersdag 2014
Rob Gietema
 
Projectgroep Millennium GroenLinks Arnhem
Projectgroep Millennium GroenLinks ArnhemProjectgroep Millennium GroenLinks Arnhem
Projectgroep Millennium GroenLinks ArnhemRob Gietema
 
Deco UI: Plone Conference 2010
Deco UI: Plone Conference 2010Deco UI: Plone Conference 2010
Deco UI: Plone Conference 2010
Rob Gietema
 
Deco UI: DZUG Tagung 2010
Deco UI: DZUG Tagung 2010Deco UI: DZUG Tagung 2010
Deco UI: DZUG Tagung 2010
Rob Gietema
 
Deco UI: Nederlandse Plone Gebruikesdag 2010
Deco UI: Nederlandse Plone Gebruikesdag 2010Deco UI: Nederlandse Plone Gebruikesdag 2010
Deco UI: Nederlandse Plone Gebruikesdag 2010
Rob Gietema
 
Case Study: Humanitas
Case Study: HumanitasCase Study: Humanitas
Case Study: Humanitas
Rob Gietema
 

Plus de Rob Gietema (20)

Van klimhal naar Big Wall: Bergsportdag 2024
Van klimhal naar Big Wall: Bergsportdag 2024Van klimhal naar Big Wall: Bergsportdag 2024
Van klimhal naar Big Wall: Bergsportdag 2024
 
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
 
Van Klimhal naar Big Wall
Van Klimhal naar Big WallVan Klimhal naar Big Wall
Van Klimhal naar Big Wall
 
Van 0 naar 6000+
Van 0 naar 6000+Van 0 naar 6000+
Van 0 naar 6000+
 
How to create your own Volto site!
How to create your own Volto site!How to create your own Volto site!
How to create your own Volto site!
 
Volto Extensibility Story: Plone Conference 2018
Volto Extensibility Story: Plone Conference 2018Volto Extensibility Story: Plone Conference 2018
Volto Extensibility Story: Plone Conference 2018
 
Volto: Plone Conference 2018
Volto: Plone Conference 2018Volto: Plone Conference 2018
Volto: Plone Conference 2018
 
React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15
 
React Native: React Meetup 3
React Native: React Meetup 3React Native: React Meetup 3
React Native: React Meetup 3
 
React Router: React Meetup XXL
React Router: React Meetup XXLReact Router: React Meetup XXL
React Router: React Meetup XXL
 
Four o Four: World Plone Day 2014
Four o Four: World Plone Day 2014Four o Four: World Plone Day 2014
Four o Four: World Plone Day 2014
 
Hackathon: Silicon Alley Lightning Talks
Hackathon: Silicon Alley Lightning TalksHackathon: Silicon Alley Lightning Talks
Hackathon: Silicon Alley Lightning Talks
 
Resource Registries: Plone Conference 2014
Resource Registries: Plone Conference 2014Resource Registries: Plone Conference 2014
Resource Registries: Plone Conference 2014
 
Arnhem Sprint 2013: Plone Conference 2013
Arnhem Sprint 2013: Plone Conference 2013Arnhem Sprint 2013: Plone Conference 2013
Arnhem Sprint 2013: Plone Conference 2013
 
Plone 5: Nederlandse Plone Gebruikersdag 2014
Plone 5: Nederlandse Plone Gebruikersdag 2014Plone 5: Nederlandse Plone Gebruikersdag 2014
Plone 5: Nederlandse Plone Gebruikersdag 2014
 
Projectgroep Millennium GroenLinks Arnhem
Projectgroep Millennium GroenLinks ArnhemProjectgroep Millennium GroenLinks Arnhem
Projectgroep Millennium GroenLinks Arnhem
 
Deco UI: Plone Conference 2010
Deco UI: Plone Conference 2010Deco UI: Plone Conference 2010
Deco UI: Plone Conference 2010
 
Deco UI: DZUG Tagung 2010
Deco UI: DZUG Tagung 2010Deco UI: DZUG Tagung 2010
Deco UI: DZUG Tagung 2010
 
Deco UI: Nederlandse Plone Gebruikesdag 2010
Deco UI: Nederlandse Plone Gebruikesdag 2010Deco UI: Nederlandse Plone Gebruikesdag 2010
Deco UI: Nederlandse Plone Gebruikesdag 2010
 
Case Study: Humanitas
Case Study: HumanitasCase Study: Humanitas
Case Study: Humanitas
 

Dernier

7 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 20247 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 2024
Danica Gill
 
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
zyfovom
 
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdfJAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
Javier Lasa
 
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
3ipehhoa
 
Gen Z and the marketplaces - let's translate their needs
Gen Z and the marketplaces - let's translate their needsGen Z and the marketplaces - let's translate their needs
Gen Z and the marketplaces - let's translate their needs
Laura Szabó
 
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
vmemo1
 
manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalmanuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
wolfsoftcompanyco
 
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
keoku
 
Understanding User Behavior with Google Analytics.pdf
Understanding User Behavior with Google Analytics.pdfUnderstanding User Behavior with Google Analytics.pdf
Understanding User Behavior with Google Analytics.pdf
SEO Article Boost
 
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdfMeet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Florence Consulting
 
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
cuobya
 
Search Result Showing My Post is Now Buried
Search Result Showing My Post is Now BuriedSearch Result Showing My Post is Now Buried
Search Result Showing My Post is Now Buried
Trish Parr
 
test test test test testtest test testtest test testtest test testtest test ...
test test  test test testtest test testtest test testtest test testtest test ...test test  test test testtest test testtest test testtest test testtest test ...
test test test test testtest test testtest test testtest test testtest test ...
Arif0071
 
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptxBridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
Brad Spiegel Macon GA
 
Explore-Insanony: Watch Instagram Stories Secretly
Explore-Insanony: Watch Instagram Stories SecretlyExplore-Insanony: Watch Instagram Stories Secretly
Explore-Insanony: Watch Instagram Stories Secretly
Trending Blogers
 
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
ysasp1
 
[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024
hackersuli
 
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
cuobya
 
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC
 
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
CIOWomenMagazine
 

Dernier (20)

7 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 20247 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 2024
 
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
 
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdfJAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
 
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
原版仿制(uob毕业证书)英国伯明翰大学毕业证本科学历证书原版一模一样
 
Gen Z and the marketplaces - let's translate their needs
Gen Z and the marketplaces - let's translate their needsGen Z and the marketplaces - let's translate their needs
Gen Z and the marketplaces - let's translate their needs
 
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
 
manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalmanuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
 
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
 
Understanding User Behavior with Google Analytics.pdf
Understanding User Behavior with Google Analytics.pdfUnderstanding User Behavior with Google Analytics.pdf
Understanding User Behavior with Google Analytics.pdf
 
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdfMeet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
 
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
 
Search Result Showing My Post is Now Buried
Search Result Showing My Post is Now BuriedSearch Result Showing My Post is Now Buried
Search Result Showing My Post is Now Buried
 
test test test test testtest test testtest test testtest test testtest test ...
test test  test test testtest test testtest test testtest test testtest test ...test test  test test testtest test testtest test testtest test testtest test ...
test test test test testtest test testtest test testtest test testtest test ...
 
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptxBridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
Bridging the Digital Gap Brad Spiegel Macon, GA Initiative.pptx
 
Explore-Insanony: Watch Instagram Stories Secretly
Explore-Insanony: Watch Instagram Stories SecretlyExplore-Insanony: Watch Instagram Stories Secretly
Explore-Insanony: Watch Instagram Stories Secretly
 
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
 
[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024
 
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
 
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
 
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
 

How to Build a Site Using Nick