SlideShare uma empresa 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

Mais conteúdo relacionado

Semelhante a 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 web
Matt 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 1
Mohammad Qureshi
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and Python
PiXeL16
 
Django
DjangoDjango
Django
webuploader
 
Switch to Backend 2023
Switch to Backend 2023Switch to Backend 2023

Semelhante a 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
 

Mais 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
 

Mais 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
 

Último

manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalmanuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
wolfsoftcompanyco
 
办理毕业证(NYU毕业证)纽约大学毕业证成绩单官方原版办理
办理毕业证(NYU毕业证)纽约大学毕业证成绩单官方原版办理办理毕业证(NYU毕业证)纽约大学毕业证成绩单官方原版办理
办理毕业证(NYU毕业证)纽约大学毕业证成绩单官方原版办理
uehowe
 
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
3a0sd7z3
 
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ó
 
一比一原版(USYD毕业证)悉尼大学毕业证如何办理
一比一原版(USYD毕业证)悉尼大学毕业证如何办理一比一原版(USYD毕业证)悉尼大学毕业证如何办理
一比一原版(USYD毕业证)悉尼大学毕业证如何办理
k4ncd0z
 
不能毕业如何获得(USYD毕业证)悉尼大学毕业证成绩单一比一原版制作
不能毕业如何获得(USYD毕业证)悉尼大学毕业证成绩单一比一原版制作不能毕业如何获得(USYD毕业证)悉尼大学毕业证成绩单一比一原版制作
不能毕业如何获得(USYD毕业证)悉尼大学毕业证成绩单一比一原版制作
bseovas
 
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
vmemo1
 
Azure EA Sponsorship - Customer Guide.pdf
Azure EA Sponsorship - Customer Guide.pdfAzure EA Sponsorship - Customer Guide.pdf
Azure EA Sponsorship - Customer Guide.pdf
AanSulistiyo
 
留学挂科(UofM毕业证)明尼苏达大学毕业证成绩单复刻办理
留学挂科(UofM毕业证)明尼苏达大学毕业证成绩单复刻办理留学挂科(UofM毕业证)明尼苏达大学毕业证成绩单复刻办理
留学挂科(UofM毕业证)明尼苏达大学毕业证成绩单复刻办理
uehowe
 
Design Thinking NETFLIX using all techniques.pptx
Design Thinking NETFLIX using all techniques.pptxDesign Thinking NETFLIX using all techniques.pptx
Design Thinking NETFLIX using all techniques.pptx
saathvikreddy2003
 
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
cuobya
 
[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
 
Discover the benefits of outsourcing SEO to India
Discover the benefits of outsourcing SEO to IndiaDiscover the benefits of outsourcing SEO to India
Discover the benefits of outsourcing SEO to India
davidjhones387
 
Ready to Unlock the Power of Blockchain!
Ready to Unlock the Power of Blockchain!Ready to Unlock the Power of Blockchain!
Ready to Unlock the Power of Blockchain!
Toptal Tech
 
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
cuobya
 
Should Repositories Participate in the Fediverse?
Should Repositories Participate in the Fediverse?Should Repositories Participate in the Fediverse?
Should Repositories Participate in the Fediverse?
Paul Walk
 
国外证书(Lincoln毕业证)新西兰林肯大学毕业证成绩单不能毕业办理
国外证书(Lincoln毕业证)新西兰林肯大学毕业证成绩单不能毕业办理国外证书(Lincoln毕业证)新西兰林肯大学毕业证成绩单不能毕业办理
国外证书(Lincoln毕业证)新西兰林肯大学毕业证成绩单不能毕业办理
zoowe
 
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
 
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
 
留学学历(UoA毕业证)奥克兰大学毕业证成绩单官方原版办理
留学学历(UoA毕业证)奥克兰大学毕业证成绩单官方原版办理留学学历(UoA毕业证)奥克兰大学毕业证成绩单官方原版办理
留学学历(UoA毕业证)奥克兰大学毕业证成绩单官方原版办理
bseovas
 

Último (20)

manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalmanuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
manuaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal
 
办理毕业证(NYU毕业证)纽约大学毕业证成绩单官方原版办理
办理毕业证(NYU毕业证)纽约大学毕业证成绩单官方原版办理办理毕业证(NYU毕业证)纽约大学毕业证成绩单官方原版办理
办理毕业证(NYU毕业证)纽约大学毕业证成绩单官方原版办理
 
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
快速办理(新加坡SMU毕业证书)新加坡管理大学毕业证文凭证书一模一样
 
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
 
一比一原版(USYD毕业证)悉尼大学毕业证如何办理
一比一原版(USYD毕业证)悉尼大学毕业证如何办理一比一原版(USYD毕业证)悉尼大学毕业证如何办理
一比一原版(USYD毕业证)悉尼大学毕业证如何办理
 
不能毕业如何获得(USYD毕业证)悉尼大学毕业证成绩单一比一原版制作
不能毕业如何获得(USYD毕业证)悉尼大学毕业证成绩单一比一原版制作不能毕业如何获得(USYD毕业证)悉尼大学毕业证成绩单一比一原版制作
不能毕业如何获得(USYD毕业证)悉尼大学毕业证成绩单一比一原版制作
 
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
 
Azure EA Sponsorship - Customer Guide.pdf
Azure EA Sponsorship - Customer Guide.pdfAzure EA Sponsorship - Customer Guide.pdf
Azure EA Sponsorship - Customer Guide.pdf
 
留学挂科(UofM毕业证)明尼苏达大学毕业证成绩单复刻办理
留学挂科(UofM毕业证)明尼苏达大学毕业证成绩单复刻办理留学挂科(UofM毕业证)明尼苏达大学毕业证成绩单复刻办理
留学挂科(UofM毕业证)明尼苏达大学毕业证成绩单复刻办理
 
Design Thinking NETFLIX using all techniques.pptx
Design Thinking NETFLIX using all techniques.pptxDesign Thinking NETFLIX using all techniques.pptx
Design Thinking NETFLIX using all techniques.pptx
 
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
可查真实(Monash毕业证)西澳大学毕业证成绩单退学买
 
[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024
 
Discover the benefits of outsourcing SEO to India
Discover the benefits of outsourcing SEO to IndiaDiscover the benefits of outsourcing SEO to India
Discover the benefits of outsourcing SEO to India
 
Ready to Unlock the Power of Blockchain!
Ready to Unlock the Power of Blockchain!Ready to Unlock the Power of Blockchain!
Ready to Unlock the Power of Blockchain!
 
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
 
Should Repositories Participate in the Fediverse?
Should Repositories Participate in the Fediverse?Should Repositories Participate in the Fediverse?
Should Repositories Participate in the Fediverse?
 
国外证书(Lincoln毕业证)新西兰林肯大学毕业证成绩单不能毕业办理
国外证书(Lincoln毕业证)新西兰林肯大学毕业证成绩单不能毕业办理国外证书(Lincoln毕业证)新西兰林肯大学毕业证成绩单不能毕业办理
国外证书(Lincoln毕业证)新西兰林肯大学毕业证成绩单不能毕业办理
 
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
 
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
 
留学学历(UoA毕业证)奥克兰大学毕业证成绩单官方原版办理
留学学历(UoA毕业证)奥克兰大学毕业证成绩单官方原版办理留学学历(UoA毕业证)奥克兰大学毕业证成绩单官方原版办理
留学学历(UoA毕业证)奥克兰大学毕业证成绩单官方原版办理
 

How to Build a Site Using Nick