{"result":{"depiction_url":null,"edges":{"relation":{"objects":[{"created":"2020-05-30T05:48:14Z","object_id":{"id":1352,"is_a":["text","documentation","developerguide"],"name":"doc_developerguide_templates","title":{"_type":"trans","tr":{"en":"Templates"}},"uri":"https:\/\/zotonic.com\/id\/1352"},"seq":1000000},{"created":"2020-05-30T05:48:14Z","object_id":{"id":322,"is_a":["meta","category"],"name":"model","title":"Models","uri":"https:\/\/test.zotonic.com\/id\/322"},"seq":1000000}],"predicate":{"id":303,"is_a":["meta","predicate"],"name":"relation","title":{"_type":"trans","tr":{"nl":"Relatie","en":"Relation"}},"uri":"http:\/\/purl.org\/dc\/terms\/relation"}}},"id":1573,"is_a":["text","documentation","cookbook"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/1573"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_cookbook_custom_model","page_url":{"en":"https:\/\/zotonic.com\/cookbook\/1573\/create-a-custom-model","x-default":"https:\/\/zotonic.com\/cookbook\/1573\/create-a-custom-model"},"preview_url":null,"resource":{"version":22,"pivot_location_lat":null,"title":{"_type":"trans","tr":{"en":"Create a custom model"}},"is_authoritative":true,"body":{"_type":"trans","tr":{"en":"<div class=\"section\">\n<aside class=\"admonition seealso\">\n<p class=\"first admonition-title\">See also<\/p>\n<p class=\"last\"><a class=\"reference internal\" href=\"\/id\/doc_developerguide_templates#guide-models\"><span class=\"std std-ref\">models section<\/span><\/a> in the Developer Guide<\/p>\n<\/aside>\n<aside class=\"admonition seealso\">\n<p class=\"first admonition-title\">See also<\/p>\n<p class=\"last\">list of <a class=\"reference internal\" href=\"\/id\/model#models\"><span class=\"std std-ref\">all models<\/span><\/a>.<\/p>\n<\/aside>\n<p>In this chapter we will look at how to implement a model around the <a class=\"reference external\" href=\"http:\/\/www.omdbapi.com\/\">The Open Movie Database (OMDB) API<\/a>.<\/p>\n<p>We will touch useful background information at the same time.<\/p>\n<div class=\"section\">\n<h2>Model modules<\/h2>\n<p>Models are Erlang modules that are prefixed with <code class=\"docutils literal notranslate\"><span class=\"pre\">m_<\/span><\/code> and stored in your main module’s subdirectory <cite>models<\/cite>. For example, the model to access Zotonic resources (syntax <code class=\"docutils literal notranslate\"><span class=\"pre\">m.rsc.property<\/span><\/code>) is written in <code class=\"docutils literal notranslate\"><span class=\"pre\">models\/m_rsc.erl<\/span><\/code>.<\/p>\n<p>Each model module is required to implement one function (as defined behavior in <code class=\"docutils literal notranslate\"><span class=\"pre\">zotonic_model.erl<\/span><\/code>):<\/p>\n<pre>m_get\/3<\/pre>\n<\/div>\n<div class=\"section\">\n<h2>m_get<\/h2>\n<p>This function fetches a value from a model. Because there are quite some different variation how to use this function, it is good to understand a bit more about the inner workings of data lookup.<\/p>\n<p>Let’s start with the parsing of a template expression:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">{{<\/span> <span class=\"n\">m<\/span><span class=\"p\">.<\/span><span class=\"n\">rsc<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">].<\/span><span class=\"n\">is_cat<\/span><span class=\"p\">.<\/span><span class=\"n\">person<\/span> <span class=\"p\">}}<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>If you have done some work with Zotonic, you will be familiar with this syntax where we can find out if the resource with id <code class=\"docutils literal notranslate\"><span class=\"pre\">1<\/span><\/code> (the Administrator) is of category Person.<\/p>\n<p>This expression contains 4 parts (separated by dots).<\/p>\n<p>At the very start, the template parser resolves <code class=\"docutils literal notranslate\"><span class=\"pre\">m.rsc<\/span><\/code> to module <code class=\"docutils literal notranslate\"><span class=\"pre\">m_rsc<\/span><\/code>. The parser then calls <code class=\"docutils literal notranslate\"><span class=\"pre\">m_rsc:m_get(Keys,<\/span> Msg, <span class=\"pre\">Context)<\/span><\/code> to fetch the value (where Keys in our expression has the value of <code class=\"docutils literal notranslate\"><span class=\"pre\">[<\/span> <span class=\"pre\">1,<\/span> <span class=\"pre\">&lt;&lt;&quot;is_cat&quot;&gt;&gt;,<\/span> <span class=\"pre\">&lt;&lt;&quot;person&quot;&gt;&gt;<\/span> <span class=\"pre\">]<\/span><\/code>). The Msg is a an MQTT publish message, containing an optional argument passed after the lookup, for example:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">{{<\/span> <span class=\"n\">m<\/span><span class=\"p\">.search.query::%{ cat:&#39;news&#39;<\/span> <span class=\"p\">}}<\/span><\/pre>\n<\/div>\n<\/div>\n<p>Here the map with the search arguments will be passed in the payload of the MQTT Msg.<\/p>\n<p>This is the function specification of <code class=\"docutils literal notranslate\"><span class=\"pre\">m_get<\/span><\/code>:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">-<\/span><span class=\"ni\">spec<\/span> <span class=\"n\">m_get<\/span><span class=\"p\">(<\/span><span class=\"nv\">Keys<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"p\">{<\/span> <span class=\"n\">term<\/span><span class=\"p\">(),<\/span> <span class=\"nv\">RestKeys<\/span> <span class=\"p\">}<\/span> <span class=\"k\">when<\/span>\n    <span class=\"nv\">Keys<\/span> <span class=\"p\">::<\/span> <span class=\"n\">list<\/span><span class=\"p\">(),<\/span>\n    <span class=\"nv\">Msg<\/span> <span class=\"p\">::<\/span> <span class=\"nn\">zotonic_model<\/span><span class=\"p\">:<\/span><span class=\"nf\">opt_msg<\/span><span class=\"p\">(),<\/span>\n    <span class=\"nv\">RestKeys<\/span> <span class=\"p\">::<\/span> <span class=\"n\">list<\/span><span class=\"p\">(),<\/span>\n    <span class=\"nv\">Context<\/span><span class=\"p\">::<\/span> <span class=\"nn\">z<\/span><span class=\"p\">:<\/span><span class=\"nf\">context<\/span><span class=\"p\">().<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>It takes the dotted expression list and returns the looked up value and the unprocessed part of the dotted list (if any).<\/p>\n<p>In this example, the <cite>m_get<\/cite> is called as:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"nn\">m_rsc<\/span><span class=\"p\">:<\/span><span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;is_cat&quot;<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">,<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;person&quot;<\/span><span class=\"o\">&gt;&gt;<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">)<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>And will return:<\/p>\n<pre>{ok, {true, []}}<\/pre>\n<p>Where <code class=\"docutils literal notranslate\"><span class=\"pre\">true<\/span><\/code> is returned because rescource id 1 is indeed a person, and <code class=\"docutils literal notranslate\"><span class=\"pre\">[]<\/span><\/code> is returned because the call consumed all parts of the dotted expression.<\/p>\n<div class=\"section\">\n<h3>Example: Setting up m_omdb<\/h3>\n<p>All data calls to the OMDB go through the url <code class=\"docutils literal notranslate\"><span class=\"pre\">http:\/\/www.omdbapi.com\/?<\/span><\/code>, with query string appended. We can pass the movie id, title, and pass a type (movie\/series\/episode). OMDB offers more parameters but we don’t need them now.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\">\n<h2>Template interface<\/h2>\n<p>Let’s define how will we use the data in templates.<\/p>\n<p>To get all data for a particular ID:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"n\">m<\/span><span class=\"p\">.<\/span><span class=\"n\">omdb<\/span><span class=\"p\">[<\/span><span class=\"s\">&quot;tt1135300&quot;<\/span><span class=\"p\">]<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>… so that we can get properties like the movie title:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">{{<\/span> <span class=\"n\">m<\/span><span class=\"p\">.<\/span><span class=\"n\">omdb<\/span><span class=\"p\">[<\/span><span class=\"s\">&quot;tt1135300&quot;<\/span><span class=\"p\">].<\/span><span class=\"n\">title<\/span> <span class=\"p\">}}<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>Find an item by title:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">{{<\/span> <span class=\"n\">m<\/span><span class=\"p\">.<\/span><span class=\"n\">omdb<\/span><span class=\"p\">[<\/span><span class=\"s\">&quot;Alien&quot;<\/span><span class=\"p\">].<\/span><span class=\"n\">year<\/span> <span class=\"p\">}}<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>Get all data from a movie:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">{<\/span><span class=\"c\">% for k,v in m.omdb.movie[&quot;Alien&quot;] %}{{ k }}: {{ v }}{% endfor %}<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>Get data from a series:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">{{<\/span> <span class=\"n\">m<\/span><span class=\"p\">.<\/span><span class=\"n\">omdb<\/span><span class=\"p\">.<\/span><span class=\"n\">series<\/span><span class=\"p\">[{<\/span><span class=\"k\">query<\/span> <span class=\"n\">title<\/span><span class=\"o\">=<\/span><span class=\"s\">&quot;Dollhouse&quot;<\/span><span class=\"p\">}].<\/span><span class=\"n\">plot<\/span> <span class=\"p\">}}<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>or from an episode:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">{{<\/span> <span class=\"n\">m<\/span><span class=\"p\">.<\/span><span class=\"n\">omdb<\/span><span class=\"p\">.<\/span><span class=\"n\">episode<\/span><span class=\"p\">[{<\/span><span class=\"k\">query<\/span> <span class=\"n\">title<\/span><span class=\"o\">=<\/span><span class=\"s\">&quot;Dollhouse&quot;<\/span><span class=\"p\">}].<\/span><span class=\"n\">plot<\/span> <span class=\"p\">}}<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"section\">\n<h2>Model skeleton<\/h2>\n<p>We will write our model in module <code class=\"docutils literal notranslate\"><span class=\"pre\">models\/m_omdb.erl<\/span><\/code>. Let’s first get the mandatory elements out of the way:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">-<\/span><span class=\"ni\">module<\/span><span class=\"p\">(<\/span><span class=\"n\">m_omdb<\/span><span class=\"p\">).<\/span>\n<span class=\"p\">-<\/span><span class=\"ni\">behaviour<\/span><span class=\"p\">(<\/span><span class=\"n\">zotonic_model<\/span><span class=\"p\">).<\/span>\n\n<span class=\"p\">-<\/span><span class=\"ni\">export<\/span><span class=\"p\">([<\/span>\n    <span class=\"n\">m_get<\/span><span class=\"o\">\/<\/span><span class=\"mi\">3<\/span>\n<span class=\"p\">]).<\/span>\n\n<span class=\"p\">-<\/span><span class=\"ni\">include_lib<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;zotonic_core\/include\/zotonic.hrl&quot;<\/span><span class=\"p\">).<\/span>\n\n<span class=\"c\">% ... We will add our m_get functions here<\/span>\n<span class=\"p\">-<\/span><span class=\"ni\">spec<\/span> <span class=\"n\">m_get<\/span><span class=\"p\">(<\/span> <span class=\"n\">list<\/span><span class=\"p\">(),<\/span> <span class=\"nn\">zotonic_model<\/span><span class=\"p\">:<\/span><span class=\"nf\">opt_msg<\/span><span class=\"p\">(),<\/span> <span class=\"nn\">z<\/span><span class=\"p\">:<\/span><span class=\"nf\">context<\/span><span class=\"p\">()<\/span> <span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span> <span class=\"n\">term<\/span><span class=\"p\">(),<\/span> <span class=\"n\">list<\/span><span class=\"p\">()<\/span> <span class=\"p\">}}<\/span> <span class=\"p\">|<\/span> <span class=\"p\">{<\/span><span class=\"n\">error<\/span><span class=\"p\">,<\/span> <span class=\"n\">term<\/span><span class=\"p\">()}.<\/span>\n<span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"p\">_<\/span> <span class=\"p\">|<\/span> <span class=\"nv\">Rest<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span><span class=\"n\">undefined<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Rest<\/span><span class=\"p\">}};<\/span>\n<span class=\"nf\">m_get<\/span><span class=\"p\">(_,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span><span class=\"n\">undefined<\/span><span class=\"p\">,<\/span> <span class=\"p\">[]}}.<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"section\">\n<h2>Querying the API<\/h2>\n<p>Before diving into the lookup functions, let’s see what we want to achieve as result.<\/p>\n<ol class=\"arabic simple\">\n<li>Using <code class=\"docutils literal notranslate\"><span class=\"pre\">m_get<\/span><\/code> we will generate a list of query parameters, for example <code class=\"docutils literal notranslate\"><span class=\"pre\">[{type,<\/span> <span class=\"pre\">&quot;series&quot;},<\/span> <span class=\"pre\">{title,<\/span> <span class=\"pre\">&quot;Dollhouse&quot;}]<\/span><\/code><\/li>\n<li>And pass this list to a “fetch data” function<\/li>\n<li>That creates a URL from the parameters,<\/li>\n<li>loads JSON data from the URL,<\/li>\n<li>and transforms the JSON into a property list<\/li>\n<\/ol>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">fetch_data<\/span><\/code> function:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">-<\/span><span class=\"ni\">spec<\/span> <span class=\"n\">fetch_data<\/span><span class=\"p\">(<\/span><span class=\"nv\">Query<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">list<\/span><span class=\"p\">()<\/span> <span class=\"k\">when<\/span>\n    <span class=\"nv\">Query<\/span><span class=\"p\">::<\/span> <span class=\"n\">list<\/span><span class=\"p\">().<\/span>\n<span class=\"nf\">fetch_data<\/span><span class=\"p\">([])<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"p\">[{<\/span><span class=\"n\">error<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;Params missing&quot;<\/span><span class=\"p\">}];<\/span>\n<span class=\"nf\">fetch_data<\/span><span class=\"p\">(<\/span><span class=\"nv\">Query<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"c\">% Params title or id must be present<\/span>\n    <span class=\"k\">case<\/span> <span class=\"nn\">proplists<\/span><span class=\"p\">:<\/span><span class=\"nf\">is_defined<\/span><span class=\"p\">(<\/span><span class=\"n\">title<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Query<\/span><span class=\"p\">)<\/span> <span class=\"ow\">or<\/span> <span class=\"nn\">proplists<\/span><span class=\"p\">:<\/span><span class=\"nf\">is_defined<\/span><span class=\"p\">(<\/span><span class=\"n\">id<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Query<\/span><span class=\"p\">)<\/span> <span class=\"k\">of<\/span>\n        <span class=\"n\">false<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"p\">[{<\/span><span class=\"n\">error<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;Param id or title missing&quot;<\/span><span class=\"p\">}];<\/span>\n        <span class=\"n\">true<\/span> <span class=\"o\">-&gt;<\/span>\n            <span class=\"c\">% Translate query params id, title and type<\/span>\n            <span class=\"c\">% into parameters that OMDB wants<\/span>\n            <span class=\"nv\">QueryParts<\/span> <span class=\"o\">=<\/span> <span class=\"nn\">lists<\/span><span class=\"p\">:<\/span><span class=\"nf\">map<\/span><span class=\"p\">(<\/span><span class=\"k\">fun<\/span><span class=\"p\">(<\/span><span class=\"nv\">Q<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n                <span class=\"n\">make_query_string<\/span><span class=\"p\">(<\/span><span class=\"nv\">Q<\/span><span class=\"p\">)<\/span>\n            <span class=\"k\">end<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Query<\/span><span class=\"p\">),<\/span>\n            <span class=\"nv\">Url<\/span> <span class=\"o\">=<\/span> <span class=\"o\">?<\/span><span class=\"nv\">API_URL<\/span> <span class=\"o\">++<\/span> <span class=\"nn\">string<\/span><span class=\"p\">:<\/span><span class=\"nf\">join<\/span><span class=\"p\">(<\/span><span class=\"nv\">QueryParts<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;&amp;&quot;<\/span><span class=\"p\">),<\/span>\n            <span class=\"c\">% Load JSON data<\/span>\n            <span class=\"k\">case<\/span> <span class=\"n\">get_page_body<\/span><span class=\"p\">(<\/span><span class=\"nv\">Url<\/span><span class=\"p\">)<\/span> <span class=\"k\">of<\/span>\n                <span class=\"p\">{<\/span><span class=\"n\">error<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Error<\/span><span class=\"p\">}<\/span> <span class=\"o\">-&gt;<\/span>\n                    <span class=\"p\">[{<\/span><span class=\"n\">error<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Error<\/span><span class=\"p\">}];<\/span>\n                <span class=\"nv\">Json<\/span> <span class=\"o\">-&gt;<\/span>\n                    <span class=\"c\">% Turn JSON into a property list<\/span>\n                    <span class=\"nv\">JsonData<\/span> <span class=\"o\">=<\/span> <span class=\"nn\">z_json<\/span><span class=\"p\">:<\/span><span class=\"nf\">decode<\/span><span class=\"p\">(<\/span><span class=\"nv\">Json<\/span><span class=\"p\">),<\/span>\n                    <span class=\"nn\">lists<\/span><span class=\"p\">:<\/span><span class=\"nf\">map<\/span><span class=\"p\">(<\/span><span class=\"k\">fun<\/span><span class=\"p\">(<\/span><span class=\"nv\">D<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n                        <span class=\"n\">convert_data_prop<\/span><span class=\"p\">(<\/span><span class=\"nv\">D<\/span><span class=\"p\">)<\/span>\n                    <span class=\"k\">end<\/span><span class=\"p\">,<\/span> <span class=\"nv\">JsonData<\/span><span class=\"p\">)<\/span>\n            <span class=\"k\">end<\/span>\n    <span class=\"k\">end<\/span><span class=\"p\">.<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>It is important to know that we will pass a list, and get a list as result (for other template models this may be different).<\/p>\n<\/div>\n<div class=\"section\">\n<h2>Lookup functions<\/h2>\n<p>To illustrate the simplest <code class=\"docutils literal notranslate\"><span class=\"pre\">m_get<\/span><\/code> function, we add one to get the API url:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">-<\/span><span class=\"ni\">define<\/span><span class=\"p\">(<\/span><span class=\"no\">API_URL<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;http:\/\/www.omdbapi.com\/?&quot;<\/span><span class=\"p\">).<\/span>\n\n<span class=\"c\">% Syntax: m.omdb.api_url<\/span>\n<span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;api_url&quot;<\/span><span class=\"o\">&gt;&gt;<\/span> <span class=\"p\">|<\/span> <span class=\"nv\">Rest<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span><span class=\"o\">?<\/span><span class=\"nv\">API_URL<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Rest<\/span><span class=\"p\">}};<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>The functions that will deliver our template interface are a bit more involved. From the template expressions we can discern 2 different patterns:<\/p>\n<ol class=\"arabic simple\">\n<li>Expressions with 1 part:<\/li>\n<\/ol>\n<ul class=\"simple\">\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">m.omdb[&quot;Dollhouse&quot;]<\/span><\/code><\/li>\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">m.omdb[{query<\/span> <span class=\"pre\">title=&quot;Dollhouse&quot;}]<\/span><\/code><\/li>\n<\/ul>\n<ol class=\"arabic simple\">\n<li>Expressions with 2 parts:<\/li>\n<\/ol>\n<ul class=\"simple\">\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">m.omdb.series[&quot;Dollhouse&quot;]<\/span><\/code><\/li>\n<li><code class=\"docutils literal notranslate\"><span class=\"pre\">m.omdb.series[{query<\/span> <span class=\"pre\">title=&quot;Dollhouse&quot;}]<\/span><\/code><\/li>\n<\/ul>\n<p>When an expression is parsed from left to right, each parsed part needs to be passed on using our m record. For instance with <code class=\"docutils literal notranslate\"><span class=\"pre\">m.omdb.series[&quot;Dollhouse&quot;]<\/span><\/code> we first tranform “series” to <code class=\"docutils literal notranslate\"><span class=\"pre\">{type,<\/span> <span class=\"pre\">&quot;series&quot;}<\/span><\/code>, and then “Dollhouse” to <code class=\"docutils literal notranslate\"><span class=\"pre\">{title,<\/span> <span class=\"pre\">&quot;Dollhouse&quot;}<\/span><\/code>, creating the full query <code class=\"docutils literal notranslate\"><span class=\"pre\">[{type,<\/span> <span class=\"pre\">&quot;series&quot;},<\/span> <span class=\"pre\">{title,<\/span> <span class=\"pre\">&quot;Dollhouse&quot;}]<\/span><\/code>.<\/p>\n<p>To parse the type, we add these functions to our module:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"c\">% Syntax: m.omdb.movie[QueryString]<\/span>\n<span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;movie&quot;<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">,<\/span> <span class=\"nv\">QueryString<\/span> <span class=\"p\">|<\/span> <span class=\"nv\">Rest<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"k\">when<\/span> <span class=\"nb\">is_binary<\/span><span class=\"p\">(<\/span><span class=\"nv\">QueryString<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"nv\">Query<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span> <span class=\"p\">{<\/span><span class=\"n\">type<\/span><span class=\"p\">,<\/span> <span class=\"n\">movie<\/span><span class=\"p\">},<\/span> <span class=\"p\">{<\/span><span class=\"n\">title<\/span><span class=\"p\">,<\/span> <span class=\"nv\">QueryString<\/span><span class=\"p\">}<\/span> <span class=\"p\">],<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span><span class=\"n\">fetch_data<\/span><span class=\"p\">(<\/span><span class=\"nv\">Query<\/span><span class=\"p\">),<\/span> <span class=\"nv\">Rest<\/span><span class=\"p\">}};<\/span>\n\n<span class=\"c\">% Syntax: m.omdb.series[QueryString]<\/span>\n<span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;series&quot;<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">,<\/span> <span class=\"nv\">QueryString<\/span> <span class=\"p\">|<\/span> <span class=\"nv\">Rest<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"k\">when<\/span> <span class=\"nb\">is_binary<\/span><span class=\"p\">(<\/span><span class=\"nv\">QueryString<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"nv\">Query<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span> <span class=\"p\">{<\/span><span class=\"n\">type<\/span><span class=\"p\">,<\/span> <span class=\"n\">series<\/span><span class=\"p\">},<\/span> <span class=\"p\">{<\/span><span class=\"n\">title<\/span><span class=\"p\">,<\/span> <span class=\"nv\">QueryString<\/span><span class=\"p\">}<\/span> <span class=\"p\">],<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span><span class=\"n\">fetch_data<\/span><span class=\"p\">(<\/span><span class=\"nv\">Query<\/span><span class=\"p\">),<\/span> <span class=\"nv\">Rest<\/span><span class=\"p\">}};<\/span>\n\n<span class=\"c\">% Syntax: m.omdb.episode[QueryString]<\/span>\n<span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;episode&quot;<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">,<\/span> <span class=\"nv\">QueryString<\/span> <span class=\"p\">|<\/span> <span class=\"nv\">Rest<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"k\">when<\/span> <span class=\"nb\">is_binary<\/span><span class=\"p\">(<\/span><span class=\"nv\">QueryString<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"nv\">Query<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span> <span class=\"p\">{<\/span><span class=\"n\">type<\/span><span class=\"p\">,<\/span> <span class=\"n\">episode<\/span><span class=\"p\">},<\/span> <span class=\"p\">{<\/span><span class=\"n\">title<\/span><span class=\"p\">,<\/span> <span class=\"nv\">QueryString<\/span><span class=\"p\">}<\/span> <span class=\"p\">],<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span><span class=\"n\">fetch_data<\/span><span class=\"p\">(<\/span><span class=\"nv\">Query<\/span><span class=\"p\">),<\/span> <span class=\"nv\">Rest<\/span><span class=\"p\">}};<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>Notice the <code class=\"docutils literal notranslate\"><span class=\"pre\">|<\/span> <span class=\"pre\">Rest<\/span><\/code> in the patterns. This is needed for expressions like:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"n\">m<\/span><span class=\"p\">.<\/span><span class=\"n\">omdb<\/span><span class=\"p\">.<\/span><span class=\"n\">series<\/span><span class=\"p\">[<\/span><span class=\"s\">&quot;Dollhouse&quot;<\/span><span class=\"p\">].<\/span><span class=\"n\">title<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>Which calls our <code class=\"docutils literal notranslate\"><span class=\"pre\">m_get<\/span><\/code> function as:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;series&quot;<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">,<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;Dollhouse&quot;<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">,<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;title&quot;<\/span><span class=\"o\">&gt;&gt;<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">)<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>We can also pass:<\/p>\n<ol class=\"arabic simple\">\n<li>The movie ID: <code class=\"docutils literal notranslate\"><span class=\"pre\">m.omdb[&quot;tt1135300&quot;]<\/span><\/code><\/li>\n<li>The title: <code class=\"docutils literal notranslate\"><span class=\"pre\">m.omdb[&quot;Alien&quot;]<\/span><\/code><\/li>\n<li>A search expression: <code class=\"docutils literal notranslate\"><span class=\"pre\">m.omdb[{query<\/span> <span class=\"pre\">title=&quot;Dollhouse&quot;}]<\/span><\/code><\/li>\n<\/ol>\n<p>Luckily, the movie IDs all start with “tt”, so we can use pattern matching to distinguish IDs from titles.<\/p>\n<p>For the ID we recognize 2 situations - with or without a previously found value:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"c\">% Syntax: m.omdb[&quot;tt1135300&quot;]<\/span>\n<span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;tt&quot;<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"o\">\/<\/span><span class=\"n\">binary<\/span><span class=\"o\">&gt;&gt;<\/span> <span class=\"o\">=<\/span> <span class=\"nv\">Id<\/span> <span class=\"p\">|<\/span> <span class=\"nv\">Rest<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"nv\">Query<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span> <span class=\"p\">{<\/span><span class=\"n\">id<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Id<\/span><span class=\"p\">}<\/span> <span class=\"p\">],<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span><span class=\"n\">fetch_data<\/span><span class=\"p\">(<\/span><span class=\"nv\">Query<\/span><span class=\"p\">),<\/span> <span class=\"nv\">Rest<\/span><span class=\"p\">}};<\/span>\n\n<span class=\"c\">% Syntax: m.omdb.sometype[&quot;tt1135300&quot;]<\/span>\n<span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;sometype&quot;<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">,<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;tt&quot;<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"o\">\/<\/span><span class=\"n\">binary<\/span><span class=\"o\">&gt;&gt;<\/span> <span class=\"o\">=<\/span> <span class=\"nv\">Id<\/span> <span class=\"p\">|<\/span> <span class=\"nv\">Rest<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"nv\">Query<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span> <span class=\"p\">{<\/span><span class=\"n\">type<\/span><span class=\"p\">,<\/span> <span class=\"n\">sometype<\/span><span class=\"p\">},<\/span> <span class=\"p\">{<\/span><span class=\"n\">id<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Id<\/span><span class=\"p\">}<\/span> <span class=\"p\">],<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">ok<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span><span class=\"n\">fetch_data<\/span><span class=\"p\">(<\/span><span class=\"nv\">Query<\/span><span class=\"p\">),<\/span> <span class=\"nv\">Rest<\/span><span class=\"p\">}}.<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>We need to place these two patterns above the title searches we already wrote<\/p>\n<p><code class=\"docutils literal notranslate\"><span class=\"pre\">fetch_data<\/span><\/code> will return a property list, so we can write this to get all values:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"p\">{<\/span><span class=\"c\">% for k,v in m.omdb[&quot;tt1135300&quot;] %}<\/span>\n    <span class=\"p\">{{<\/span> <span class=\"n\">k<\/span> <span class=\"p\">}}:<\/span> <span class=\"p\">{{<\/span> <span class=\"n\">v<\/span> <span class=\"p\">}}<\/span>\n<span class=\"p\">{<\/span><span class=\"c\">% endfor %}<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>Handling the title is similar to the ID. Title must be a string, otherwise it would be a property key (atom):<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"c\">% Syntax: m.omdb[&quot;some title&quot;]<\/span>\n<span class=\"c\">% If no atom is passed it must be a title (string)<\/span>\n<span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"nv\">Title<\/span> <span class=\"p\">|<\/span> <span class=\"nv\">Rest<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"k\">when<\/span> <span class=\"nb\">is_binary<\/span><span class=\"p\">(<\/span><span class=\"nv\">Title<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"nv\">Query<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span> <span class=\"p\">{<\/span><span class=\"n\">title<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Title<\/span><span class=\"p\">}<\/span> <span class=\"p\">],<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">fetch_data<\/span><span class=\"p\">(<\/span><span class=\"nv\">Query<\/span><span class=\"p\">),<\/span> <span class=\"p\">[]};<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>To parse the search expression, we can simply use the readymade property list:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"c\">% Syntax: m.omdb[{query QueryParams}]<\/span>\n<span class=\"c\">% For m.omdb[{query title=&quot;Dollhouse&quot;}], Query is: [{title,&quot;Dollhouse&quot;}]<\/span>\n<span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"p\">{<\/span><span class=\"k\">query<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Query<\/span><span class=\"p\">}<\/span> <span class=\"p\">|<\/span> <span class=\"nv\">Rest<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">fetch_data<\/span><span class=\"p\">(<\/span><span class=\"nv\">Query<\/span><span class=\"p\">),<\/span> <span class=\"p\">[]};<\/span>\n\n<span class=\"c\">% Syntax: m.omdb.sometype[{query QueryParams}]<\/span>\n<span class=\"c\">% For m.omdb.series[{query title=&quot;Dollhouse&quot;}],<\/span>\n<span class=\"c\">% Query is: [{title,&quot;Dollhouse&quot;}] and Q is: [{type,&quot;series&quot;}]<\/span>\n<span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;series&quot;<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span><span class=\"k\">query<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Query<\/span><span class=\"p\">}<\/span> <span class=\"p\">|<\/span> <span class=\"nv\">Rest<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Context<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span>\n    <span class=\"p\">{<\/span><span class=\"n\">fetch_data<\/span><span class=\"p\">([{<\/span><span class=\"n\">type<\/span><span class=\"p\">,<\/span> <span class=\"n\">series<\/span><span class=\"p\">}<\/span> <span class=\"p\">|<\/span> <span class=\"nv\">Query<\/span><span class=\"p\">),<\/span> <span class=\"p\">[]};<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>If we want to fetch the year of the first result we use:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"n\">m<\/span><span class=\"p\">.<\/span><span class=\"n\">omdb<\/span><span class=\"p\">[<\/span><span class=\"s\">&quot;Alien&quot;<\/span><span class=\"p\">].<\/span><span class=\"n\">year<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>… we get called as:<\/p>\n<div class=\"highlight-erlang notranslate\">\n<div class=\"highlight\">\n<pre><span class=\"nf\">m_get<\/span><span class=\"p\">([<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;Alien&quot;<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">,<\/span> <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;year&quot;<\/span><span class=\"o\">&gt;&gt;<\/span> <span class=\"p\">],<\/span> <span class=\"p\">_<\/span><span class=\"nv\">Msg<\/span><span class=\"p\">,<\/span> <span class=\"nv\">Context<\/span><span class=\"p\">).<\/span>\n<\/pre>\n<\/div>\n<\/div>\n<p>Which (after a search on the title “Alien”) returns:<\/p>\n<pre>{ok, {SomeSearchResultList, [ <span class=\"o\">&lt;&lt;<\/span><span class=\"s\">&quot;year&quot;<\/span><span class=\"o\">&gt;&gt;<\/span> ]}}.<\/pre>\n<p>The <code class=\"docutils literal notranslate\"><span class=\"pre\">[<\/span> <span class=\"pre\">year<\/span> <span class=\"pre\">]<\/span><\/code> will then be used to lookup the year property of the found result.<\/p>\n<p>We won’t do any validity checking on the parameter here, but for most modules it makes sense to limit the possibilities. See for instance how <code class=\"docutils literal notranslate\"><span class=\"pre\">m_search:get_result<\/span><\/code> is done.<\/p>\n<\/div>\n<div class=\"section\">\n<h2>Full source code<\/h2>\n<p>The source code of the documentation so far can be found in this gist: <a class=\"reference external\" href=\"https:\/\/gist.github.com\/mworrell\/08a9f2115c2df7a3f3068b500564314d\">Zotonic 1.0 - Template model for the OMDB movie database - source code to accompany the documentation<\/a>.<\/p>\n<\/div>\n<div class=\"section\">\n<h2>Possible enhancements<\/h2>\n<p>For a complete model for this API, I would expect:<\/p>\n<ul class=\"simple\">\n<li>Data caching to speed up identical calls<\/li>\n<li>Support for all API parameters<\/li>\n<li>Better error handling (the service might be down or return wrong data)<\/li>\n<\/ul>\n<\/div>\n<\/div>"}},"slug":"create-a-custom-model","is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"is_featured":false,"content_group_id":{"id":339,"is_a":["meta","content_group"],"name":"default_content_group","title":{"_type":"trans","tr":{"en":"Default Content Group"}},"uri":"https:\/\/zotonic.com\/id\/default_content_group"},"category_id":{"id":318,"is_a":["meta","category"],"name":"cookbook","title":{"_type":"trans","tr":{"en":"Cookbook"}},"uri":"https:\/\/test.zotonic.com\/id\/318"},"publication_start":"2022-02-15T10:01:00Z","is_website_redirect":false,"github_url":"https:\/\/github.com\/zotonic\/zotonic\/tree\/master\/doc\/cookbook\/custom-model.rst","pivot_location_lng":null,"name":"doc_cookbook_custom_model","is_unfindable":false,"is_published":true,"pivot_geocode":null,"custom_slug":false,"created":"2020-05-30T05:47:26Z","uri":null,"date_is_all_day":false,"is_dependent":false,"is_page_path_multiple":false,"publication_end":"9999-08-17T12:00:00Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"privacy":0,"creator_id":{"id":336,"is_a":["person","robot"],"name":"gitbot","title":"Git","uri":"https:\/\/zotonic.com\/id\/336"},"seo_noindex":false,"modified":"2022-05-09T14:19:49Z","title_slug":{"_type":"trans","tr":{"en":"create-a-custom-model"}}},"uri":"https:\/\/zotonic.com\/id\/1573","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/1573"}},"status":"ok"}