CINXE.COM

Blog | Django and Python Tutorials | News | Caktus Group

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"><script type="text/javascript">(window.NREUM||(NREUM={})).init={ajax:{deny_list:["bam.nr-data.net"]}};(window.NREUM||(NREUM={})).loader_config={licenseKey:"NRBR-2fdf56539d4020c5401",applicationID:"957418355"};;/*! For license information please see nr-loader-rum-1.286.0.min.js.LICENSE.txt */ (()=>{var e,t,r={8122:(e,t,r)=>{"use strict";r.d(t,{a:()=>i});var n=r(944);function i(e,t){try{if(!e||"object"!=typeof e)return(0,n.R)(3);if(!t||"object"!=typeof t)return(0,n.R)(4);const r=Object.create(Object.getPrototypeOf(t),Object.getOwnPropertyDescriptors(t)),o=0===Object.keys(r).length?e:r;for(let a in o)if(void 0!==e[a])try{if(null===e[a]){r[a]=null;continue}Array.isArray(e[a])&&Array.isArray(t[a])?r[a]=Array.from(new Set([...e[a],...t[a]])):"object"==typeof e[a]&&"object"==typeof t[a]?r[a]=i(e[a],t[a]):r[a]=e[a]}catch(e){(0,n.R)(1,e)}return r}catch(e){(0,n.R)(2,e)}}},2555:(e,t,r)=>{"use strict";r.d(t,{fn:()=>s,x1:()=>c});var n=r(384),i=r(8122);const o={beacon:n.NT.beacon,errorBeacon:n.NT.errorBeacon,licenseKey:void 0,applicationID:void 0,sa:void 0,queueTime:void 0,applicationTime:void 0,ttGuid:void 0,user:void 0,account:void 0,product:void 0,extra:void 0,jsAttributes:{},userAttributes:void 0,atts:void 0,transactionName:void 0,tNamePlain:void 0},a={};function s(e){try{const t=function(e){if(!e)throw new Error("All info objects require an agent identifier!");if(!a[e])throw new Error("Info for ".concat(e," was never set"));return a[e]}(e);return!!t.licenseKey&&!!t.errorBeacon&&!!t.applicationID}catch(e){return!1}}function c(e,t){if(!e)throw new Error("All info objects require an agent identifier!");a[e]=(0,i.a)(t,o);const r=(0,n.nY)(e);r&&(r.info=a[e])}},5217:(e,t,r)=>{"use strict";r.d(t,{gD:()=>h,xN:()=>m});r(860).K7.genericEvents;const n="experimental.marks",i="experimental.measures",o="experimental.resources",a=e=>{if(!e||"string"!=typeof e)return!1;try{document.createDocumentFragment().querySelector(e)}catch{return!1}return!0};var s=r(2614),c=r(944),u=r(384),d=r(8122);const l="[data-nr-mask]",f=()=>{const e={feature_flags:[],experimental:{marks:!1,measures:!1,resources:!1},mask_selector:"*",block_selector:"[data-nr-block]",mask_input_options:{color:!1,date:!1,"datetime-local":!1,email:!1,month:!1,number:!1,range:!1,search:!1,tel:!1,text:!1,time:!1,url:!1,week:!1,textarea:!1,select:!1,password:!0}};return{ajax:{deny_list:void 0,block_internal:!0,enabled:!0,autoStart:!0},distributed_tracing:{enabled:void 0,exclude_newrelic_header:void 0,cors_use_newrelic_header:void 0,cors_use_tracecontext_headers:void 0,allowed_origins:void 0},get feature_flags(){return e.feature_flags},set feature_flags(t){e.feature_flags=t},generic_events:{enabled:!0,autoStart:!0},harvest:{interval:30},jserrors:{enabled:!0,autoStart:!0},logging:{enabled:!0,autoStart:!0},metrics:{enabled:!0,autoStart:!0},obfuscate:void 0,page_action:{enabled:!0},page_view_event:{enabled:!0,autoStart:!0},page_view_timing:{enabled:!0,autoStart:!0},performance:{get capture_marks(){return e.feature_flags.includes(n)||e.experimental.marks},set capture_marks(t){e.experimental.marks=t},get capture_measures(){return e.feature_flags.includes(i)||e.experimental.measures},set capture_measures(t){e.experimental.measures=t},capture_detail:!0,resources:{get enabled(){return e.feature_flags.includes(o)||e.experimental.resources},set enabled(t){e.experimental.resources=t},asset_types:[],first_party_domains:[],ignore_newrelic:!0}},privacy:{cookies_enabled:!0},proxy:{assets:void 0,beacon:void 0},session:{expiresMs:s.wk,inactiveMs:s.BB},session_replay:{autoStart:!0,enabled:!1,preload:!1,sampling_rate:10,error_sampling_rate:100,collect_fonts:!1,inline_images:!1,fix_stylesheets:!0,mask_all_inputs:!0,get mask_text_selector(){return e.mask_selector},set mask_text_selector(t){a(t)?e.mask_selector="".concat(t,",").concat(l):""===t||null===t?e.mask_selector=l:(0,c.R)(5,t)},get block_class(){return"nr-block"},get ignore_class(){return"nr-ignore"},get mask_text_class(){return"nr-mask"},get block_selector(){return e.block_selector},set block_selector(t){a(t)?e.block_selector+=",".concat(t):""!==t&&(0,c.R)(6,t)},get mask_input_options(){return e.mask_input_options},set mask_input_options(t){t&&"object"==typeof t?e.mask_input_options={...t,password:!0}:(0,c.R)(7,t)}},session_trace:{enabled:!0,autoStart:!0},soft_navigations:{enabled:!0,autoStart:!0},spa:{enabled:!0,autoStart:!0},ssl:void 0,user_actions:{enabled:!0,elementAttributes:["id","className","tagName","type"]}}},g={},p="All configuration objects require an agent identifier!";function m(e,t){if(!e)throw new Error(p);g[e]=(0,d.a)(t,f());const r=(0,u.nY)(e);r&&(r.init=g[e])}function h(e,t){if(!e)throw new Error(p);var r=function(e){if(!e)throw new Error(p);if(!g[e])throw new Error("Configuration for ".concat(e," was never set"));return g[e]}(e);if(r){for(var n=t.split("."),i=0;i<n.length-1;i++)if("object"!=typeof(r=r[n[i]]))return;r=r[n[n.length-1]]}return r}},3371:(e,t,r)=>{"use strict";r.d(t,{V:()=>f,f:()=>l});var n=r(8122),i=r(384),o=r(6154),a=r(9324);let s=0;const c={buildEnv:a.F3,distMethod:a.Xs,version:a.xv,originTime:o.WN},u={customTransaction:void 0,disabled:!1,isolatedBacklog:!1,loaderType:void 0,maxBytes:3e4,onerror:void 0,ptid:void 0,releaseIds:{},appMetadata:{},session:void 0,denyList:void 0,timeKeeper:void 0,obfuscator:void 0,harvester:void 0},d={};function l(e){if(!e)throw new Error("All runtime objects require an agent identifier!");if(!d[e])throw new Error("Runtime for ".concat(e," was never set"));return d[e]}function f(e,t){if(!e)throw new Error("All runtime objects require an agent identifier!");d[e]={...(0,n.a)(t,u),...c},Object.hasOwnProperty.call(d[e],"harvestCount")||Object.defineProperty(d[e],"harvestCount",{get:()=>++s});const r=(0,i.nY)(e);r&&(r.runtime=d[e])}},9324:(e,t,r)=>{"use strict";r.d(t,{F3:()=>i,Xs:()=>o,xv:()=>n});const n="1.286.0",i="PROD",o="CDN"},6154:(e,t,r)=>{"use strict";r.d(t,{OF:()=>c,RI:()=>i,WN:()=>d,bv:()=>o,gm:()=>a,mw:()=>s,sb:()=>u});var n=r(1863);const i="undefined"!=typeof window&&!!window.document,o="undefined"!=typeof WorkerGlobalScope&&("undefined"!=typeof self&&self instanceof WorkerGlobalScope&&self.navigator instanceof WorkerNavigator||"undefined"!=typeof globalThis&&globalThis instanceof WorkerGlobalScope&&globalThis.navigator instanceof WorkerNavigator),a=i?window:"undefined"!=typeof WorkerGlobalScope&&("undefined"!=typeof self&&self instanceof WorkerGlobalScope&&self||"undefined"!=typeof globalThis&&globalThis instanceof WorkerGlobalScope&&globalThis),s=Boolean("hidden"===a?.document?.visibilityState),c=/iPad|iPhone|iPod/.test(a.navigator?.userAgent),u=c&&"undefined"==typeof SharedWorker,d=((()=>{const e=a.navigator?.userAgent?.match(/Firefox[/\s](\d+\.\d+)/);Array.isArray(e)&&e.length>=2&&e[1]})(),Date.now()-(0,n.t)())},3241:(e,t,r)=>{"use strict";r.d(t,{W:()=>o});var n=r(6154);const i="newrelic";function o(e={}){try{n.gm.dispatchEvent(new CustomEvent(i,{detail:e}))}catch(e){}}},1687:(e,t,r)=>{"use strict";r.d(t,{Ak:()=>c,Ze:()=>l,x3:()=>u});var n=r(7836),i=r(3606),o=r(860),a=r(2646);const s={};function c(e,t){const r={staged:!1,priority:o.P3[t]||0};d(e),s[e].get(t)||s[e].set(t,r)}function u(e,t){e&&s[e]&&(s[e].get(t)&&s[e].delete(t),g(e,t,!1),s[e].size&&f(e))}function d(e){if(!e)throw new Error("agentIdentifier required");s[e]||(s[e]=new Map)}function l(e="",t="feature",r=!1){if(d(e),!e||!s[e].get(t)||r)return g(e,t);s[e].get(t).staged=!0,f(e)}function f(e){const t=Array.from(s[e]);t.every((([e,t])=>t.staged))&&(t.sort(((e,t)=>e[1].priority-t[1].priority)),t.forEach((([t])=>{s[e].delete(t),g(e,t)})))}function g(e,t,r=!0){const o=e?n.ee.get(e):n.ee,s=i.i.handlers;if(!o.aborted&&o.backlog&&s){if(r){const e=o.backlog[t],r=s[t];if(r){for(let t=0;e&&t<e.length;++t)p(e[t],r);Object.entries(r).forEach((([e,t])=>{Object.values(t||{}).forEach((t=>{t[0]?.on&&t[0]?.context()instanceof a.y&&t[0].on(e,t[1])}))}))}}o.isolatedBacklog||delete s[t],o.backlog[t]=null,o.emit("drain-"+t,[])}}function p(e,t){var r=e[1];Object.values(t[r]||{}).forEach((t=>{var r=e[0];if(t[0]===r){var n=t[1],i=e[3],o=e[2];n.apply(i,o)}}))}},7836:(e,t,r)=>{"use strict";r.d(t,{P:()=>c,ee:()=>u});var n=r(384),i=r(8990),o=r(3371),a=r(2646),s=r(5607);const c="nr@context:".concat(s.W),u=function e(t,r){var n={},s={},d={},l=!1;try{l=16===r.length&&(0,o.f)(r).isolatedBacklog}catch(e){}var f={on:p,addEventListener:p,removeEventListener:function(e,t){var r=n[e];if(!r)return;for(var i=0;i<r.length;i++)r[i]===t&&r.splice(i,1)},emit:function(e,r,n,i,o){!1!==o&&(o=!0);if(u.aborted&&!i)return;t&&o&&t.emit(e,r,n);for(var a=g(n),c=m(e),d=c.length,l=0;l<d;l++)c[l].apply(a,r);var p=v()[s[e]];p&&p.push([f,e,r,a]);return a},get:h,listeners:m,context:g,buffer:function(e,t){const r=v();if(t=t||"feature",f.aborted)return;Object.entries(e||{}).forEach((([e,n])=>{s[n]=t,t in r||(r[t]=[])}))},abort:function(){f._aborted=!0,Object.keys(f.backlog).forEach((e=>{delete f.backlog[e]}))},isBuffering:function(e){return!!v()[s[e]]},debugId:r,backlog:l?{}:t&&"object"==typeof t.backlog?t.backlog:{},isolatedBacklog:l};return Object.defineProperty(f,"aborted",{get:()=>{let e=f._aborted||!1;return e||(t&&(e=t.aborted),e)}}),f;function g(e){return e&&e instanceof a.y?e:e?(0,i.I)(e,c,(()=>new a.y(c))):new a.y(c)}function p(e,t){n[e]=m(e).concat(t)}function m(e){return n[e]||[]}function h(t){return d[t]=d[t]||e(f,t)}function v(){return f.backlog}}(void 0,"globalEE"),d=(0,n.Zm)();d.ee||(d.ee=u)},2646:(e,t,r)=>{"use strict";r.d(t,{y:()=>n});class n{constructor(e){this.contextId=e}}},9908:(e,t,r)=>{"use strict";r.d(t,{d:()=>n,p:()=>i});var n=r(7836).ee.get("handle");function i(e,t,r,i,o){o?(o.buffer([e],i),o.emit(e,t,r)):(n.buffer([e],i),n.emit(e,t,r))}},3606:(e,t,r)=>{"use strict";r.d(t,{i:()=>o});var n=r(9908);o.on=a;var i=o.handlers={};function o(e,t,r,o){a(o||n.d,i,e,t,r)}function a(e,t,r,i,o){o||(o="feature"),e||(e=n.d);var a=t[o]=t[o]||{};(a[r]=a[r]||[]).push([e,i])}},3878:(e,t,r)=>{"use strict";function n(e,t){return{capture:e,passive:!1,signal:t}}function i(e,t,r=!1,i){window.addEventListener(e,t,n(r,i))}function o(e,t,r=!1,i){document.addEventListener(e,t,n(r,i))}r.d(t,{DD:()=>o,jT:()=>n,sp:()=>i})},5607:(e,t,r)=>{"use strict";r.d(t,{W:()=>n});const n=(0,r(9566).bz)()},9566:(e,t,r)=>{"use strict";r.d(t,{LA:()=>s,bz:()=>a});var n=r(6154);const i="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";function o(e,t){return e?15&e[t]:16*Math.random()|0}function a(){const e=n.gm?.crypto||n.gm?.msCrypto;let t,r=0;return e&&e.getRandomValues&&(t=e.getRandomValues(new Uint8Array(30))),i.split("").map((e=>"x"===e?o(t,r++).toString(16):"y"===e?(3&o()|8).toString(16):e)).join("")}function s(e){const t=n.gm?.crypto||n.gm?.msCrypto;let r,i=0;t&&t.getRandomValues&&(r=t.getRandomValues(new Uint8Array(e)));const a=[];for(var s=0;s<e;s++)a.push(o(r,i++).toString(16));return a.join("")}},2614:(e,t,r)=>{"use strict";r.d(t,{BB:()=>a,H3:()=>n,g:()=>u,iL:()=>c,tS:()=>s,uh:()=>i,wk:()=>o});const n="NRBA",i="SESSION",o=144e5,a=18e5,s={STARTED:"session-started",PAUSE:"session-pause",RESET:"session-reset",RESUME:"session-resume",UPDATE:"session-update"},c={SAME_TAB:"same-tab",CROSS_TAB:"cross-tab"},u={OFF:0,FULL:1,ERROR:2}},1863:(e,t,r)=>{"use strict";function n(){return Math.floor(performance.now())}r.d(t,{t:()=>n})},944:(e,t,r)=>{"use strict";function n(e,t){"function"==typeof console.debug&&console.debug("New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#".concat(e),t)}r.d(t,{R:()=>n})},5701:(e,t,r)=>{"use strict";r.d(t,{B:()=>a,t:()=>s});var n=r(7836),i=r(3241);const o=new Set,a={};function s(e,t){const r=n.ee.get(t);a[t]??={},e&&"object"==typeof e&&(o.has(t)||(r.emit("rumresp",[e]),a[t]=e,o.add(t),(0,i.W)({agentIdentifier:t,loaded:!0,drained:!0,type:"lifecycle",name:"load",feature:void 0,data:e})))}},8990:(e,t,r)=>{"use strict";r.d(t,{I:()=>i});var n=Object.prototype.hasOwnProperty;function i(e,t,r){if(n.call(e,t))return e[t];var i=r();if(Object.defineProperty&&Object.keys)try{return Object.defineProperty(e,t,{value:i,writable:!0,enumerable:!1}),i}catch(e){}return e[t]=i,i}},6389:(e,t,r)=>{"use strict";function n(e,t=500,r={}){const n=r?.leading||!1;let i;return(...r)=>{n&&void 0===i&&(e.apply(this,r),i=setTimeout((()=>{i=clearTimeout(i)}),t)),n||(clearTimeout(i),i=setTimeout((()=>{e.apply(this,r)}),t))}}function i(e){let t=!1;return(...r)=>{t||(t=!0,e.apply(this,r))}}r.d(t,{J:()=>i,s:()=>n})},5289:(e,t,r)=>{"use strict";r.d(t,{GG:()=>o,sB:()=>a});var n=r(3878);function i(){return"undefined"==typeof document||"complete"===document.readyState}function o(e,t){if(i())return e();(0,n.sp)("load",e,t)}function a(e){if(i())return e();(0,n.DD)("DOMContentLoaded",e)}},384:(e,t,r)=>{"use strict";r.d(t,{NT:()=>o,US:()=>d,Zm:()=>a,bQ:()=>c,dV:()=>s,nY:()=>u,pV:()=>l});var n=r(6154),i=r(1863);const o={beacon:"bam.nr-data.net",errorBeacon:"bam.nr-data.net"};function a(){return n.gm.NREUM||(n.gm.NREUM={}),void 0===n.gm.newrelic&&(n.gm.newrelic=n.gm.NREUM),n.gm.NREUM}function s(){let e=a();return e.o||(e.o={ST:n.gm.setTimeout,SI:n.gm.setImmediate,CT:n.gm.clearTimeout,XHR:n.gm.XMLHttpRequest,REQ:n.gm.Request,EV:n.gm.Event,PR:n.gm.Promise,MO:n.gm.MutationObserver,FETCH:n.gm.fetch,WS:n.gm.WebSocket}),e}function c(e,t){let r=a();r.initializedAgents??={},t.initializedAt={ms:(0,i.t)(),date:new Date},r.initializedAgents[e]=t}function u(e){let t=a();return t.initializedAgents?.[e]}function d(e,t){a()[e]=t}function l(){return function(){let e=a();const t=e.info||{};e.info={beacon:o.beacon,errorBeacon:o.errorBeacon,...t}}(),function(){let e=a();const t=e.init||{};e.init={...t}}(),s(),function(){let e=a();const t=e.loader_config||{};e.loader_config={...t}}(),a()}},2843:(e,t,r)=>{"use strict";r.d(t,{u:()=>i});var n=r(3878);function i(e,t=!1,r,i){(0,n.DD)("visibilitychange",(function(){if(t)return void("hidden"===document.visibilityState&&e());e(document.visibilityState)}),r,i)}},3434:(e,t,r)=>{"use strict";r.d(t,{Jt:()=>o,YM:()=>c});var n=r(7836),i=r(5607);const o="nr@original:".concat(i.W);var a=Object.prototype.hasOwnProperty,s=!1;function c(e,t){return e||(e=n.ee),r.inPlace=function(e,t,n,i,o){n||(n="");const a="-"===n.charAt(0);for(let s=0;s<t.length;s++){const c=t[s],u=e[c];d(u)||(e[c]=r(u,a?c+n:n,i,c,o))}},r.flag=o,r;function r(t,r,n,s,c){return d(t)?t:(r||(r=""),nrWrapper[o]=t,function(e,t,r){if(Object.defineProperty&&Object.keys)try{return Object.keys(e).forEach((function(r){Object.defineProperty(t,r,{get:function(){return e[r]},set:function(t){return e[r]=t,t}})})),t}catch(e){u([e],r)}for(var n in e)a.call(e,n)&&(t[n]=e[n])}(t,nrWrapper,e),nrWrapper);function nrWrapper(){var o,a,d,l;try{a=this,o=[...arguments],d="function"==typeof n?n(o,a):n||{}}catch(t){u([t,"",[o,a,s],d],e)}i(r+"start",[o,a,s],d,c);try{return l=t.apply(a,o)}catch(e){throw i(r+"err",[o,a,e],d,c),e}finally{i(r+"end",[o,a,l],d,c)}}}function i(r,n,i,o){if(!s||t){var a=s;s=!0;try{e.emit(r,n,i,t,o)}catch(t){u([t,r,n,i],e)}s=a}}}function u(e,t){t||(t=n.ee);try{t.emit("internal-error",e)}catch(e){}}function d(e){return!(e&&"function"==typeof e&&e.apply&&!e[o])}},993:(e,t,r)=>{"use strict";r.d(t,{A$:()=>o,ET:()=>a,p_:()=>i});var n=r(860);const i={ERROR:"ERROR",WARN:"WARN",INFO:"INFO",DEBUG:"DEBUG",TRACE:"TRACE"},o={OFF:0,ERROR:1,WARN:2,INFO:3,DEBUG:4,TRACE:5},a="log";n.K7.logging},8154:(e,t,r)=>{"use strict";r.d(t,{z_:()=>o,XG:()=>s,TZ:()=>n,rs:()=>i,xV:()=>a});r(6154),r(9566),r(384);const n=r(860).K7.metrics,i="sm",o="cm",a="storeSupportabilityMetrics",s="storeEventMetrics"},6630:(e,t,r)=>{"use strict";r.d(t,{T:()=>n});const n=r(860).K7.pageViewEvent},782:(e,t,r)=>{"use strict";r.d(t,{T:()=>n});const n=r(860).K7.pageViewTiming},6344:(e,t,r)=>{"use strict";r.d(t,{G4:()=>i});var n=r(2614);r(860).K7.sessionReplay;const i={RECORD:"recordReplay",PAUSE:"pauseReplay",REPLAY_RUNNING:"replayRunning",ERROR_DURING_REPLAY:"errorDuringReplay"};n.g.ERROR,n.g.FULL,n.g.OFF},4234:(e,t,r)=>{"use strict";r.d(t,{W:()=>o});var n=r(7836),i=r(1687);class o{constructor(e,t){this.agentIdentifier=e,this.ee=n.ee.get(e),this.featureName=t,this.blocked=!1}deregisterDrain(){(0,i.x3)(this.agentIdentifier,this.featureName)}}},7603:(e,t,r)=>{"use strict";r.d(t,{j:()=>V});var n=r(860),i=r(2555),o=r(9908),a=r(1687),s=r(5289),c=r(6154),u=r(944),d=r(8154),l=r(384),f=r(6344);const g=["setErrorHandler","finished","addToTrace","addRelease","recordCustomEvent","addPageAction","setCurrentRouteName","setPageViewName","setCustomAttribute","interaction","noticeError","setUserId","setApplicationVersion","start",f.G4.RECORD,f.G4.PAUSE,"log","wrapLogger"],p=["setErrorHandler","finished","addToTrace","addRelease"];var m=r(1863),h=r(2614),v=r(993);var b=r(7836),y=r(2646),w=r(3434);const R=new Map;function A(e,t,r,n){if("object"!=typeof t||!t||"string"!=typeof r||!r||"function"!=typeof t[r])return(0,u.R)(29);const i=function(e){return(e||b.ee).get("logger")}(e),o=(0,w.YM)(i),a=new y.y(b.P);a.level=n.level,a.customAttributes=n.customAttributes;const s=t[r]?.[w.Jt]||t[r];return R.set(s,a),o.inPlace(t,[r],"wrap-logger-",(()=>R.get(s))),i}var E=r(3241),_=r(5701);function x(){const e=(0,l.pV)();g.forEach((t=>{e[t]=(...r)=>function(t,...r){let n=[];return Object.values(e.initializedAgents).forEach((e=>{e&&e.runtime?e.exposed&&e[t]&&"micro-agent"!==e.runtime.loaderType&&n.push(e[t](...r)):(0,u.R)(38,t)})),n[0]}(t,...r)}))}const N={};function I(e,t){t||(0,a.Ak)(e.agentIdentifier,"api");const l=e.ee.get("tracer");N[e.agentIdentifier]=h.g.OFF,e.ee.on(f.G4.REPLAY_RUNNING,(t=>{N[e.agentIdentifier]=t}));const g="api-",b=g+"ixn-";function y(t,r,n,o){const a=e.info;return null===r?delete a.jsAttributes[t]:(0,i.x1)(e.agentIdentifier,{...a,jsAttributes:{...a.jsAttributes,[t]:r}}),x(g,n,!0,o||null===r?"session":void 0)(t,r)}function w(){}e.log=function(t,{customAttributes:r={},level:i=v.p_.INFO}={}){(0,o.p)(d.xV,["API/log/called"],void 0,n.K7.metrics,e.ee),function(e,t,r={},i=v.p_.INFO){(0,o.p)(d.xV,["API/logging/".concat(i.toLowerCase(),"/called")],void 0,n.K7.metrics,e),(0,o.p)(v.ET,[(0,m.t)(),t,r,i],void 0,n.K7.logging,e)}(e.ee,t,r,i)},e.wrapLogger=(t,r,{customAttributes:i={},level:a=v.p_.INFO}={})=>{(0,o.p)(d.xV,["API/wrapLogger/called"],void 0,n.K7.metrics,e.ee),A(e.ee,t,r,{customAttributes:i,level:a})},p.forEach((t=>{e[t]=x(g,t,!0,"api")})),e.addPageAction=x(g,"addPageAction",!0,n.K7.genericEvents),e.recordCustomEvent=x(g,"recordCustomEvent",!0,n.K7.genericEvents),e.setPageViewName=function(t,r){if("string"==typeof t)return"/"!==t.charAt(0)&&(t="/"+t),e.runtime.customTransaction=(r||"http://custom.transaction")+t,x(g,"setPageViewName",!0)()},e.setCustomAttribute=function(e,t,r=!1){if("string"==typeof e){if(["string","number","boolean"].includes(typeof t)||null===t)return y(e,t,"setCustomAttribute",r);(0,u.R)(40,typeof t)}else(0,u.R)(39,typeof e)},e.setUserId=function(e){if("string"==typeof e||null===e)return y("enduser.id",e,"setUserId",!0);(0,u.R)(41,typeof e)},e.setApplicationVersion=function(e){if("string"==typeof e||null===e)return y("application.version",e,"setApplicationVersion",!1);(0,u.R)(42,typeof e)},e.start=()=>{try{(0,o.p)(d.xV,["API/start/called"],void 0,n.K7.metrics,e.ee),e.ee.emit("manual-start-all")}catch(e){(0,u.R)(23,e)}},e[f.G4.RECORD]=function(){(0,o.p)(d.xV,["API/recordReplay/called"],void 0,n.K7.metrics,e.ee),(0,o.p)(f.G4.RECORD,[],void 0,n.K7.sessionReplay,e.ee)},e[f.G4.PAUSE]=function(){(0,o.p)(d.xV,["API/pauseReplay/called"],void 0,n.K7.metrics,e.ee),(0,o.p)(f.G4.PAUSE,[],void 0,n.K7.sessionReplay,e.ee)},e.interaction=function(e){return(new w).get("object"==typeof e?e:{})};const R=w.prototype={createTracer:function(t,r){var i={},a=this,s="function"==typeof r;return(0,o.p)(d.xV,["API/createTracer/called"],void 0,n.K7.metrics,e.ee),e.runSoftNavOverSpa||(0,o.p)(b+"tracer",[(0,m.t)(),t,i],a,n.K7.spa,e.ee),function(){if(l.emit((s?"":"no-")+"fn-start",[(0,m.t)(),a,s],i),s)try{return r.apply(this,arguments)}catch(e){const t="string"==typeof e?new Error(e):e;throw l.emit("fn-err",[arguments,this,t],i),t}finally{l.emit("fn-end",[(0,m.t)()],i)}}}};function x(t,r,i,a){return function(){return(0,o.p)(d.xV,["API/"+r+"/called"],void 0,n.K7.metrics,e.ee),(0,E.W)({agentIdentifier:e.agentIdentifier,drained:!!_.B?.[e.agentIdentifier],type:"data",name:"api",feature:t+r,data:{notSpa:i,bufferGroup:a}}),a&&(0,o.p)(t+r,[i?(0,m.t)():performance.now(),...arguments],i?null:this,a,e.ee),i?void 0:this}}function I(){r.e(296).then(r.bind(r,8778)).then((({setAsyncAPI:t})=>{t(e),(0,a.Ze)(e.agentIdentifier,"api")})).catch((t=>{(0,u.R)(27,t),e.ee.abort()}))}return["actionText","setName","setAttribute","save","ignore","onEnd","getContext","end","get"].forEach((t=>{R[t]=x(b,t,void 0,e.runSoftNavOverSpa?n.K7.softNav:n.K7.spa)})),e.setCurrentRouteName=e.runSoftNavOverSpa?x(b,"routeName",void 0,n.K7.softNav):x(g,"routeName",!0,n.K7.spa),e.noticeError=function(t,r){"string"==typeof t&&(t=new Error(t)),(0,o.p)(d.xV,["API/noticeError/called"],void 0,n.K7.metrics,e.ee),(0,o.p)("err",[t,(0,m.t)(),!1,r,!!N[e.agentIdentifier]],void 0,n.K7.jserrors,e.ee)},c.RI?(0,s.GG)((()=>I()),!0):I(),!0}var k=r(5217),S=r(8122);const T={accountID:void 0,trustKey:void 0,agentID:void 0,licenseKey:void 0,applicationID:void 0,xpid:void 0},O={};var j=r(3371);const P=e=>{const t=e.startsWith("http");e+="/",r.p=t?e:"https://"+e},K=new Set;function V(e,t={},r,n){let{init:o,info:a,loader_config:s,runtime:u={},exposed:d=!0}=t;u.loaderType=r;const f=(0,l.pV)();a||(o=f.init,a=f.info,s=f.loader_config),(0,k.xN)(e.agentIdentifier,o||{}),function(e,t){if(!e)throw new Error("All loader-config objects require an agent identifier!");O[e]=(0,S.a)(t,T);const r=(0,l.nY)(e);r&&(r.loader_config=O[e])}(e.agentIdentifier,s||{}),a.jsAttributes??={},c.bv&&(a.jsAttributes.isWorker=!0),(0,i.x1)(e.agentIdentifier,a);const g=e.init,p=[a.beacon,a.errorBeacon];K.has(e.agentIdentifier)||(g.proxy.assets&&(P(g.proxy.assets),p.push(g.proxy.assets)),g.proxy.beacon&&p.push(g.proxy.beacon),x(),(0,l.US)("activatedFeatures",_.B),e.runSoftNavOverSpa&&=!0===g.soft_navigations.enabled&&g.feature_flags.includes("soft_nav")),u.denyList=[...g.ajax.deny_list||[],...g.ajax.block_internal?p:[]],u.ptid=e.agentIdentifier,(0,j.V)(e.agentIdentifier,u),K.has(e.agentIdentifier)||(e.ee=b.ee.get(e.agentIdentifier),e.exposed=d,I(e,n),(0,E.W)({agentIdentifier:e.agentIdentifier,drained:!!_.B?.[e.agentIdentifier],type:"lifecycle",name:"initialize",feature:void 0,data:e.config})),K.add(e.agentIdentifier)}},8374:(e,t,r)=>{r.nc=(()=>{try{return document?.currentScript?.nonce}catch(e){}return""})()},860:(e,t,r)=>{"use strict";r.d(t,{$J:()=>d,K7:()=>c,P3:()=>u,XX:()=>i,Yy:()=>s,df:()=>o,qY:()=>n,v4:()=>a});const n="events",i="jserrors",o="browser/blobs",a="rum",s="browser/logs",c={ajax:"ajax",genericEvents:"generic_events",jserrors:i,logging:"logging",metrics:"metrics",pageAction:"page_action",pageViewEvent:"page_view_event",pageViewTiming:"page_view_timing",sessionReplay:"session_replay",sessionTrace:"session_trace",softNav:"soft_navigations",spa:"spa"},u={[c.pageViewEvent]:1,[c.pageViewTiming]:2,[c.metrics]:3,[c.jserrors]:4,[c.spa]:5,[c.ajax]:6,[c.sessionTrace]:7,[c.softNav]:8,[c.sessionReplay]:9,[c.logging]:10,[c.genericEvents]:11},d={[c.pageViewEvent]:a,[c.pageViewTiming]:n,[c.ajax]:n,[c.spa]:n,[c.softNav]:n,[c.metrics]:i,[c.jserrors]:i,[c.sessionTrace]:o,[c.sessionReplay]:o,[c.logging]:s,[c.genericEvents]:"ins"}}},n={};function i(e){var t=n[e];if(void 0!==t)return t.exports;var o=n[e]={exports:{}};return r[e](o,o.exports,i),o.exports}i.m=r,i.d=(e,t)=>{for(var r in t)i.o(t,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},i.f={},i.e=e=>Promise.all(Object.keys(i.f).reduce(((t,r)=>(i.f[r](e,t),t)),[])),i.u=e=>"nr-rum-1.286.0.min.js",i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),e={},t="NRBA-1.286.0.PROD:",i.l=(r,n,o,a)=>{if(e[r])e[r].push(n);else{var s,c;if(void 0!==o)for(var u=document.getElementsByTagName("script"),d=0;d<u.length;d++){var l=u[d];if(l.getAttribute("src")==r||l.getAttribute("data-webpack")==t+o){s=l;break}}if(!s){c=!0;var f={296:"sha512-+MkNp41sKZ0iYMHsept2X5HfDqyTLnDR9rprfuuxTRn6FVcYOei0L1PleWwmSuU2wrJDnMXcNYjTfSPQ3xYU/w=="};(s=document.createElement("script")).charset="utf-8",s.timeout=120,i.nc&&s.setAttribute("nonce",i.nc),s.setAttribute("data-webpack",t+o),s.src=r,0!==s.src.indexOf(window.location.origin+"/")&&(s.crossOrigin="anonymous"),f[a]&&(s.integrity=f[a])}e[r]=[n];var g=(t,n)=>{s.onerror=s.onload=null,clearTimeout(p);var i=e[r];if(delete e[r],s.parentNode&&s.parentNode.removeChild(s),i&&i.forEach((e=>e(n))),t)return t(n)},p=setTimeout(g.bind(null,void 0,{type:"timeout",target:s}),12e4);s.onerror=g.bind(null,s.onerror),s.onload=g.bind(null,s.onload),c&&document.head.appendChild(s)}},i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.p="https://js-agent.newrelic.com/",(()=>{var e={374:0,840:0};i.f.j=(t,r)=>{var n=i.o(e,t)?e[t]:void 0;if(0!==n)if(n)r.push(n[2]);else{var o=new Promise(((r,i)=>n=e[t]=[r,i]));r.push(n[2]=o);var a=i.p+i.u(t),s=new Error;i.l(a,(r=>{if(i.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var o=r&&("load"===r.type?"missing":r.type),a=r&&r.target&&r.target.src;s.message="Loading chunk "+t+" failed.\n("+o+": "+a+")",s.name="ChunkLoadError",s.type=o,s.request=a,n[1](s)}}),"chunk-"+t,t)}};var t=(t,r)=>{var n,o,[a,s,c]=r,u=0;if(a.some((t=>0!==e[t]))){for(n in s)i.o(s,n)&&(i.m[n]=s[n]);if(c)c(i)}for(t&&t(r);u<a.length;u++)o=a[u],i.o(e,o)&&e[o]&&e[o][0](),e[o]=0},r=self["webpackChunk:NRBA-1.286.0.PROD"]=self["webpackChunk:NRBA-1.286.0.PROD"]||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))})(),(()=>{"use strict";i(8374);var e=i(944),t=i(6344),r=i(9566);class n{agentIdentifier;constructor(){this.agentIdentifier=(0,r.LA)(16)}#e(t,...r){if(this[t]!==n.prototype[t])return this[t](...r);(0,e.R)(35,t)}addPageAction(e,t){return this.#e("addPageAction",e,t)}recordCustomEvent(e,t){return this.#e("recordCustomEvent",e,t)}setPageViewName(e,t){return this.#e("setPageViewName",e,t)}setCustomAttribute(e,t,r){return this.#e("setCustomAttribute",e,t,r)}noticeError(e,t){return this.#e("noticeError",e,t)}setUserId(e){return this.#e("setUserId",e)}setApplicationVersion(e){return this.#e("setApplicationVersion",e)}setErrorHandler(e){return this.#e("setErrorHandler",e)}addRelease(e,t){return this.#e("addRelease",e,t)}log(e,t){return this.#e("log",e,t)}}class o extends n{#e(t,...r){if(this[t]!==o.prototype[t]&&this[t]!==n.prototype[t])return this[t](...r);(0,e.R)(35,t)}start(){return this.#e("start")}finished(e){return this.#e("finished",e)}recordReplay(){return this.#e(t.G4.RECORD)}pauseReplay(){return this.#e(t.G4.PAUSE)}addToTrace(e){return this.#e("addToTrace",e)}setCurrentRouteName(e){return this.#e("setCurrentRouteName",e)}interaction(){return this.#e("interaction")}wrapLogger(e,t,r){return this.#e("wrapLogger",e,t,r)}}var a=i(860),s=i(5217);const c=Object.values(a.K7);function u(e){const t={};return c.forEach((r=>{t[r]=function(e,t){return!0===(0,s.gD)(t,"".concat(e,".enabled"))}(r,e)})),t}var d=i(7603);var l=i(1687),f=i(4234),g=i(5289),p=i(6154),m=i(384);const h=e=>p.RI&&!0===(0,s.gD)(e,"privacy.cookies_enabled");function v(e){return!!(0,m.dV)().o.MO&&h(e)&&!0===(0,s.gD)(e,"session_trace.enabled")}var b=i(6389);class y extends f.W{constructor(e,t,r=!0){super(e.agentIdentifier,t),this.auto=r,this.abortHandler=void 0,this.featAggregate=void 0,this.onAggregateImported=void 0,!1===e.init[this.featureName].autoStart&&(this.auto=!1),this.auto?(0,l.Ak)(e.agentIdentifier,t):this.ee.on("manual-start-all",(0,b.J)((()=>{(0,l.Ak)(e.agentIdentifier,this.featureName),this.auto=!0,this.importAggregator(e)})))}importAggregator(t,r={}){if(this.featAggregate||!this.auto)return;let n;this.onAggregateImported=new Promise((e=>{n=e}));const o=async()=>{let o;try{if(h(this.agentIdentifier)){const{setupAgentSession:e}=await i.e(296).then(i.bind(i,3861));o=e(t)}}catch(t){(0,e.R)(20,t),this.ee.emit("internal-error",[t]),this.featureName===a.K7.sessionReplay&&this.abortHandler?.()}try{if(!this.#t(this.featureName,o))return(0,l.Ze)(this.agentIdentifier,this.featureName),void n(!1);const{lazyFeatureLoader:e}=await i.e(296).then(i.bind(i,6103)),{Aggregate:a}=await e(this.featureName,"aggregate");this.featAggregate=new a(t,r),t.runtime.harvester.initializedAggregates.push(this.featAggregate),n(!0)}catch(t){(0,e.R)(34,t),this.abortHandler?.(),(0,l.Ze)(this.agentIdentifier,this.featureName,!0),n(!1),this.ee&&this.ee.abort()}};p.RI?(0,g.GG)((()=>o()),!0):o()}#t(e,t){switch(e){case a.K7.sessionReplay:return v(this.agentIdentifier)&&!!t;case a.K7.sessionTrace:return!!t;default:return!0}}}var w=i(6630);class R extends y{static featureName=w.T;constructor(e,t=!0){super(e,w.T,t),this.importAggregator(e)}}var A=i(9908),E=i(2843),_=i(3878),x=i(782),N=i(1863);class I extends y{static featureName=x.T;constructor(e,t=!0){super(e,x.T,t),p.RI&&((0,E.u)((()=>(0,A.p)("docHidden",[(0,N.t)()],void 0,x.T,this.ee)),!0),(0,_.sp)("pagehide",(()=>(0,A.p)("winPagehide",[(0,N.t)()],void 0,x.T,this.ee))),this.importAggregator(e))}}var k=i(8154);class S extends y{static featureName=k.TZ;constructor(e,t=!0){super(e,k.TZ,t),p.RI&&document.addEventListener("securitypolicyviolation",(e=>{(0,A.p)(k.xV,["Generic/CSPViolation/Detected"],void 0,this.featureName,this.ee)})),this.importAggregator(e)}}new class extends o{constructor(t){super(),p.gm?(this.features={},(0,m.bQ)(this.agentIdentifier,this),this.desiredFeatures=new Set(t.features||[]),this.desiredFeatures.add(R),this.runSoftNavOverSpa=[...this.desiredFeatures].some((e=>e.featureName===a.K7.softNav)),(0,d.j)(this,t,t.loaderType||"agent"),this.run()):(0,e.R)(21)}get config(){return{info:this.info,init:this.init,loader_config:this.loader_config,runtime:this.runtime}}get api(){return this}run(){try{const t=u(this.agentIdentifier),r=[...this.desiredFeatures];r.sort(((e,t)=>a.P3[e.featureName]-a.P3[t.featureName])),r.forEach((r=>{if(!t[r.featureName]&&r.featureName!==a.K7.pageViewEvent)return;if(this.runSoftNavOverSpa&&r.featureName===a.K7.spa)return;if(!this.runSoftNavOverSpa&&r.featureName===a.K7.softNav)return;const n=function(e){switch(e){case a.K7.ajax:return[a.K7.jserrors];case a.K7.sessionTrace:return[a.K7.ajax,a.K7.pageViewEvent];case a.K7.sessionReplay:return[a.K7.sessionTrace];case a.K7.pageViewTiming:return[a.K7.pageViewEvent];default:return[]}}(r.featureName).filter((e=>!(e in this.features)));n.length>0&&(0,e.R)(36,{targetFeature:r.featureName,missingDependencies:n}),this.features[r.featureName]=new r(this)}))}catch(t){(0,e.R)(22,t);for(const e in this.features)this.features[e].abortHandler?.();const r=(0,m.Zm)();delete r.initializedAgents[this.agentIdentifier]?.features,delete this.sharedAggregator;return r.ee.get(this.agentIdentifier).abort(),!1}}}({features:[R,I,S],loaderType:"lite"})})()})();</script><script type="text/javascript">window.NREUM||(NREUM={});NREUM.info={"beacon":"bam.nr-data.net","errorBeacon":"bam.nr-data.net","licenseKey":"NRBR-2fdf56539d4020c5401","applicationID":"957418355","transactionName":"YFYBYEIAXBFTW0NbClkcJUFeAkYLXVYYQQBZRxFNbxJWCRxRWUYAUEECQFkOXBEcXF1TC1BcTVlZBVYOV09WQAANbBRGURFtD1tcU14AQFIRUR5dXg1RWVtBWxlgBlpEE0s1QFlHQgxZVC5dVAVeB0VZRVc=","queueTime":0,"applicationTime":3,"agent":""}</script> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="Read up on tips, tutorials, and best practices for Django and front-end development, plus UX, QA, and project management."> <meta name="author" content=""> <link rel="shortcut icon" type="image/png" href="https://storage.caktusgroup.com/static/images/favicon.1ca0b1cf7100.png"> <link rel="apple-touch-icon" href="https://storage.caktusgroup.com/static/images/favicon-152.a7c8b2b74b4d.png"> <meta property="og:type" content="website" /> <meta name="twitter:card" content="summary" /> <meta property="og:title" content="Blog | Django and Python Tutorials | News | Caktus Group" /> <meta property="og:url" content="https://www.caktusgroup.com/blog/" /> <meta property="og:image" content="https://storage.caktusgroup.com/media/hero_images/bloghero2.png" /> <meta property="og:description" content="Read up on tips, tutorials, and best practices for Django and front-end development, plus UX, QA, and project management." /> <meta name="twitter:description" content="Read up on tips, tutorials, and best practices for Django and front-end development, plus UX, QA, and project management." /> <meta name="twitter:image" content="https://storage.caktusgroup.com/media/hero_images/bloghero2.png" /> <link rel="alternate" type="application/rss+xml" href="/feeds/main/" title="Caktus Blog" /> <title>Blog | Django and Python Tutorials | News | Caktus Group</title> <link rel="stylesheet" type="text/css" media="all" href="https://storage.caktusgroup.com/static/css/bundle.8bf3128f69a3.css"> <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> <![endif]--> <!-- Google Tag Manager --> <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-TSBBHZR');</script> <!-- End Google Tag Manager --> <!-- Global site tag (gtag.js) - Google Analytics --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-VZFPBRGT04"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-VZFPBRGT04'); </script> <!-- Global site tag (gtag.js) - Google AdWords --> <script async src="https://www.googletagmanager.com/gtag/js?id=AW-1061434655"></script> <script> window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'AW-1061434655'); </script> <script> !function(f,b,e,v,n,t,s) {if(f.fbq)return;n=f.fbq=function(){n.callMethod? n.callMethod.apply(n,arguments):n.queue.push(arguments)}; if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; n.queue=[];t=b.createElement(e);t.async=!0; t.src=v;s=b.getElementsByTagName(e)[0]; s.parentNode.insertBefore(t,s)}(window, document,'script', 'https://connect.facebook.net/en_US/fbevents.js'); fbq('init', '702425087568619'); fbq('track', 'PageView'); </script> <noscript><img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=702425087568619&ev=PageView&noscript=1" /></noscript> </head> <body id="posts" class=""> <!-- Google Tag Manager (noscript) --> <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-TSBBHZR" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> <!-- End Google Tag Manager (noscript) --> <!-- Begin Navbar --> <div id="nav" class="navbar" role="navigation"> <div class="wrapper"> <div class="navbar-header"> <button type="button" class="navbar-toggle"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/"> <img src="https://storage.caktusgroup.com/static/images/caktus-logo-426x234.deaf567beb3d.png"/> </a> </div> <nav class="navbar-caktus navbar-off"> <ul class="nav navbar-nav"> <li><a class="service-page" href="/services/">Services <span class="fa"><svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="10px" height="10px" viewBox="0 0 284.929 284.929" style="enable-background:new 0 0 284.929 284.929;" xml:space="preserve"> <title>Angle Down</title> <g> <path d="M282.082,76.511l-14.274-14.273c-1.902-1.906-4.093-2.856-6.57-2.856c-2.471,0-4.661,0.95-6.563,2.856L142.466,174.441 L30.262,62.241c-1.903-1.906-4.093-2.856-6.567-2.856c-2.475,0-4.665,0.95-6.567,2.856L2.856,76.515C0.95,78.417,0,80.607,0,83.082 c0,2.473,0.953,4.663,2.856,6.565l133.043,133.046c1.902,1.903,4.093,2.854,6.567,2.854s4.661-0.951,6.562-2.854L282.082,89.647 c1.902-1.903,2.847-4.093,2.847-6.565C284.929,80.607,283.984,78.417,282.082,76.511z"/> </g> </svg> </span></a> <ul> <li><a href="/services/#section-service-types">Service types</a></li> <li><a href="/services/#section-areas-of-expertise">Areas of expertise</a></li> <li><a href="/services/#section-partnerships">Partnerships & Community Involvement</a></li> <li><a href="/services/#section-pricing">Pricing</a></li> </ul> </li> <li><a class="our-work" href="/casestudies/">Our work <span class="fa"><svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="10px" height="10px" viewBox="0 0 284.929 284.929" style="enable-background:new 0 0 284.929 284.929;" xml:space="preserve"> <title>Angle Down</title> <g> <path d="M282.082,76.511l-14.274-14.273c-1.902-1.906-4.093-2.856-6.57-2.856c-2.471,0-4.661,0.95-6.563,2.856L142.466,174.441 L30.262,62.241c-1.903-1.906-4.093-2.856-6.567-2.856c-2.475,0-4.665,0.95-6.567,2.856L2.856,76.515C0.95,78.417,0,80.607,0,83.082 c0,2.473,0.953,4.663,2.856,6.565l133.043,133.046c1.902,1.903,4.093,2.854,6.567,2.854s4.661-0.951,6.562-2.854L282.082,89.647 c1.902-1.903,2.847-4.093,2.847-6.565C284.929,80.607,283.984,78.417,282.082,76.511z"/> </g> </svg> </span></a> <ul> <li><a href="/casestudies/#section-case-studies">Case studies</a></li> <li><a href="/casestudies/#section-client-feedback">Client feedback</a></li> </ul> </li> <li><a class="about-us" href="/about/">About us <span class="fa"><svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="10px" height="10px" viewBox="0 0 284.929 284.929" style="enable-background:new 0 0 284.929 284.929;" xml:space="preserve"> <title>Angle Down</title> <g> <path d="M282.082,76.511l-14.274-14.273c-1.902-1.906-4.093-2.856-6.57-2.856c-2.471,0-4.661,0.95-6.563,2.856L142.466,174.441 L30.262,62.241c-1.903-1.906-4.093-2.856-6.567-2.856c-2.475,0-4.665,0.95-6.567,2.856L2.856,76.515C0.95,78.417,0,80.607,0,83.082 c0,2.473,0.953,4.663,2.856,6.565l133.043,133.046c1.902,1.903,4.093,2.854,6.567,2.854s4.661-0.951,6.562-2.854L282.082,89.647 c1.902-1.903,2.847-4.093,2.847-6.565C284.929,80.607,283.984,78.417,282.082,76.511z"/> </g> </svg> </span></a> <ul> <li><a href="/about/#section-open-source">Open source</a></li> <li><a href="/about/#section-community-participation">Community engagement</a></li> <li><a href="/about/#section-press">Press</a></li> <li><a href="/ebooks/">Ebooks</a></li> <li><a href="/about/#section-charitable-giving">Charitable giving</a></li> <li><a href="/talks/">Talks &amp; Publications</a></li> <li><a href="/about/#section-our-team">Team</a></li> </ul> </li> <li><a class="careers" href="/careers/">Careers</a></li> <li><a class="posts" href="/blog/">Blog</a></li> <li class="focus"><a class="contact" href="/contact/">Contact</a></li> </ul> </nav> </div> </div><!-- /end Navbar --> <div id="wrap"> <main id="main-content"> <div class="container"> </div> <div class="landing-hero row landing-hero-blogs" style="background-image: linear-gradient(rgba(20, 10, 0, .55), rgba(20, 10, 0, .7)), url('https://storage.caktusgroup.com/media/hero_images/bloghero2.png')"> <div class="wrapper cushion-big-space"> <div class="half white center"> <h1>Caktus Blog</h1> <div class="spacer"></div> <h3> <div>Web development blog with how-to's on UX, quality assurance, project management, and more.</div> </h3> </div> </div> </div> <!-- Blog Posts --> <section> <div class="full row lt-grey-bg dark-blue list-view-filter underline-link"> <!-- SEARCH / FILTER SORT --> <div class="wrapper"> <div class="topic-categories three-quarters"> <h3>Topics:</h3> <ul> <li> <a class="active-underline" href="/blog/">All</a> </li> <li> <a href="/blog/categories/kubernetes/">Kubernetes</a> </li> <li> <a href="/blog/categories/caktus-universe/">Caktus Universe</a> </li> <li> <a href="/blog/categories/our-services/">Our Services</a> </li> <li> <a href="/blog/categories/technical/">Technical</a> </li> <li> <a href="/blog/categories/ux/">UX</a> </li> <li> <a href="/blog/categories/content-management/">Content Management</a> </li> <li> <a href="/blog/categories/design/">Design</a> </li> <li> <a href="/blog/categories/project-management/">Project Management</a> </li> <li> <a href="/blog/categories/testing-qa/">Testing &amp; QA</a> </li> <li> <a href="/blog/categories/caktus-community/">Caktus &amp; Community</a> </li> <li> <a href="/blog/categories/news-events/">News &amp; Events</a> </li> <li> <a href="/blog/categories/social-innovation/">Social Innovation</a> </li> </ul> </div> <div class="quarter"> <form method="GET" charset="utf-8" action="/blog/search/"> <div class="search-container"> <span class="fa"><?xml version="1.0" encoding="utf-8"?> <svg width="20" height="20" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><title>Search</title><path d="M1216 832q0-185-131.5-316.5t-316.5-131.5-316.5 131.5-131.5 316.5 131.5 316.5 316.5 131.5 316.5-131.5 131.5-316.5zm512 832q0 52-38 90t-90 38q-54 0-90-38l-343-342q-179 124-399 124-143 0-273.5-55.5t-225-150-150-225-55.5-273.5 55.5-273.5 150-225 225-150 273.5-55.5 273.5 55.5 225 150 150 225 55.5 273.5q0 220-124 399l343 343q37 37 37 90z"/></svg></span> <input type="search" id="search" name="q" value="" placeholder="Search..." /> </div> </form> </div> </div> </div> <!-- POST PREVIEW CARDS --> <!-- Display hairline at top of blog posts, but not within the card-page, so it doesn't get loaded each time user clicks "Load more" --> <div class="home-page"> <div class="full our-blog lt-grey-bg"> <div class="wrapper search_result_text"> <div class="hairline"></div> </div> </div> </div> <!-- The card-page --> <div id="card-page" class="home-page"> <div id="our-blog" class="full lt-grey-bg"> <div class="wrapper search_result_text"> <ul class="blog-card-wrapper full"> <!-- start-of-card --> <!-- The blogpost class is used to find the number of cards on the page in the jscroll callback --> <li class="card card-blog blogpost preprocess"> <div class="card-common--image_container"> <a href="/blog/2025/03/27/cakti-share-their-favorite-tools-for-streamlined-worklows/" class="card-common--inner_image_container"><picture> <source srcset="https://storage.caktusgroup.com/media/thumbnail-cache/df/35/df3577bc983d1413b36b1602f413be2e.png" media="(min-width: 400px) and (max-width: 768px)" /> <img src="https://storage.caktusgroup.com/media/thumbnail-cache/6c/84/6c8464856b37a92a7eac264d8d8196e8.png" alt="Cakti Share Their Favorite Tools For Streamlined Workflows"> </picture></a> </div> <div class="card-blog--main_container"> <div> <a class="card-blog--tag" href="/blog/categories/technical/">Technical</a> &amp; <a class="card-blog--tag" href="/blog/categories/caktus-community/">Caktus &amp; Community</a> </div> <h3><a href="/blog/2025/03/27/cakti-share-their-favorite-tools-for-streamlined-worklows/">Cakti Share Their Favorite Tools For Streamlined Workflows</a></h3> <!-- The author --> <div class="card-common--author_container"> <a class="card-common--author" href="/about/ronard-luna/">Ronard Luna</a> &amp; <a class="card-common--author" href="/about/keanya-phelps/">Keanya Phelps</a> &amp; <a class="card-common--author" href="/about/colin-copeland/">Colin Copeland</a> &amp; <a class="card-common--author" href="/about/tobias-mcnulty/">Tobias McNulty</a> &amp; <a class="card-common--author" href="/about/gerald-carlton/">Gerald Carlton</a> &amp; <a class="card-common--author" href="/about/jeanette-obrien/">Jeanette O&#39;Brien</a> &amp; <a class="card-common--author" href="/about/karen-tracey/">Karen Tracey</a> </div> <!-- The blogpost description --> <p>At Caktus, We鈥檙e always looking for tools that help us streamline our workflows, increase productivity and make our day to day tasks more efficient.&nbsp;<a href="/blog/2025/03/27/cakti-share-their-favorite-tools-for-streamlined-worklows/">Read more</a></p> </div> <div class="card-common--extras"> <span class="card-common--date">March 27, 2025</span> <div class="card-common--social-media"> <!-- Twitter --> <a target="_blank" href="https://twitter.com/intent/tweet?text=Cakti%20Share%20Their%20Favorite%20Tools%20For%20Streamlined%20Workflows&tw_p=tweetbutton&url=https://www.caktusgroup.com/blog/2025/03/27/cakti-share-their-favorite-tools-for-streamlined-worklows/"><span class="fa"><svg version="1.1" id="svg_twitter" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 449.956 449.956" style="enable-background:new 0 0 449.956 449.956;" xml:space="preserve"> <title>Twitter</title> <g> <path d="M449.956,85.657c-17.702,7.614-35.408,12.369-53.102,14.279c19.985-11.991,33.503-28.931,40.546-50.819 c-18.281,10.847-37.787,18.268-58.532,22.267c-18.274-19.414-40.73-29.125-67.383-29.125c-25.502,0-47.246,8.992-65.24,26.98 c-17.984,17.987-26.977,39.731-26.977,65.235c0,6.851,0.76,13.896,2.284,21.128c-37.688-1.903-73.042-11.372-106.068-28.407 C82.46,110.158,54.433,87.46,31.403,59.101c-8.375,14.272-12.564,29.787-12.564,46.536c0,15.798,3.711,30.456,11.138,43.97 c7.422,13.512,17.417,24.455,29.98,32.831c-14.849-0.572-28.743-4.475-41.684-11.708v1.142c0,22.271,6.995,41.824,20.983,58.674 c13.99,16.848,31.645,27.453,52.961,31.833c-7.995,2.091-16.086,3.138-24.269,3.138c-5.33,0-11.136-0.475-17.416-1.42 c5.9,18.459,16.75,33.633,32.546,45.535c15.799,11.896,33.691,18.028,53.677,18.418c-33.498,26.262-71.66,39.393-114.486,39.393 c-8.186,0-15.607-0.373-22.27-1.139c42.827,27.596,90.03,41.394,141.612,41.394c32.738,0,63.478-5.181,92.21-15.557 c28.746-10.369,53.297-24.267,73.665-41.686c20.362-17.415,37.925-37.448,52.674-60.097c14.75-22.651,25.738-46.298,32.977-70.946 c7.23-24.653,10.848-49.344,10.848-74.092c0-5.33-0.096-9.325-0.287-11.991C421.785,120.202,437.202,104.306,449.956,85.657z"/> </g> </svg> </span></a> &ensp; <!-- LinkedIn --> <a target="_blank" href="https://www.linkedin.com/cws/share?url=https://www.caktusgroup.com/blog/2025/03/27/cakti-share-their-favorite-tools-for-streamlined-worklows/"><span class="fa"><svg version="1.1" id="svg_linkedin" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 438.536 438.535" style="enable-background:new 0 0 438.536 438.535;" xml:space="preserve"> <title>LinkedIn</title> <g> <g> <rect x="5.424" y="145.895" width="94.216" height="282.932"/> <path d="M408.842,171.739c-19.791-21.604-45.967-32.408-78.512-32.408c-11.991,0-22.891,1.475-32.695,4.427 c-9.801,2.95-18.079,7.089-24.838,12.419c-6.755,5.33-12.135,10.278-16.129,14.844c-3.798,4.337-7.512,9.389-11.136,15.104 v-40.232h-93.935l0.288,13.706c0.193,9.139,0.288,37.307,0.288,84.508c0,47.205-0.19,108.777-0.572,184.722h93.931V270.942 c0-9.705,1.041-17.412,3.139-23.127c4-9.712,10.037-17.843,18.131-24.407c8.093-6.572,18.13-9.855,30.125-9.855 c16.364,0,28.407,5.662,36.117,16.987c7.707,11.324,11.561,26.98,11.561,46.966V428.82h93.931V266.664 C438.529,224.976,428.639,193.336,408.842,171.739z"/> <path d="M53.103,9.708c-15.796,0-28.595,4.619-38.4,13.848C4.899,32.787,0,44.441,0,58.529c0,13.891,4.758,25.505,14.275,34.829 c9.514,9.325,22.078,13.99,37.685,13.99h0.571c15.99,0,28.887-4.661,38.688-13.99c9.801-9.324,14.606-20.934,14.417-34.829 c-0.19-14.087-5.047-25.742-14.561-34.973C81.562,14.323,68.9,9.708,53.103,9.708z"/> </g> </g> </svg> </span></a> &ensp; <!-- Facebook --> <a target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https://www.caktusgroup.com/blog/2025/03/27/cakti-share-their-favorite-tools-for-streamlined-worklows/"><span class="fa"><svg version="1.1" id="svg_facebook" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 470.513 470.513" style="enable-background:new 0 0 470.513 470.513;" xml:space="preserve"> <title>Facebook</title> <g> <path d="M271.521,154.17v-40.541c0-6.086,0.28-10.8,0.849-14.13c0.567-3.335,1.857-6.615,3.859-9.853 c1.999-3.236,5.236-5.47,9.706-6.708c4.476-1.24,10.424-1.858,17.85-1.858h40.539V0h-64.809c-37.5,0-64.433,8.897-80.803,26.691 c-16.368,17.798-24.551,44.014-24.551,78.658v48.82h-48.542v81.086h48.539v235.256h97.362V235.256h64.805l8.566-81.086H271.521z"/> </g> </svg> </span></a> </div> </div> </li> <!-- End-of-card --> <!-- start-of-card --> <!-- The blogpost class is used to find the number of cards on the page in the jscroll callback --> <li class="card card-blog blogpost preprocess"> <div class="card-common--image_container"> <a href="/blog/2025/03/27/upgrade-smarter-not-harder-python-tools-code-modernization/" class="card-common--inner_image_container"><picture> <source srcset="https://storage.caktusgroup.com/media/thumbnail-cache/b4/71/b471088086189be38ac915eb04f63943.png" media="(min-width: 400px) and (max-width: 768px)" /> <img src="https://storage.caktusgroup.com/media/thumbnail-cache/39/a5/39a5b3d82cb372a682606436152bf13d.png" alt="Upgrade Smarter, Not Harder: Python Tools for Code Modernization"> </picture></a> </div> <div class="card-blog--main_container"> <div> <a class="card-blog--tag" href="/blog/categories/technical/">Technical</a> </div> <h3><a href="/blog/2025/03/27/upgrade-smarter-not-harder-python-tools-code-modernization/">Upgrade Smarter, Not Harder: Python Tools for Code Modernization</a></h3> <!-- The author --> <div class="card-common--author_container"> <a class="card-common--author" href="/about/ronard-luna/">Ronard Luna</a> &amp; <a class="card-common--author" href="/about/colin-copeland/">Colin Copeland</a> </div> <!-- The blogpost description --> <p> Upgrading projects is somewhat equivalent to flossing, you know you have to do it, but rarely make time for it. After all, if the project is in active development, there are ex...&nbsp;<a href="/blog/2025/03/27/upgrade-smarter-not-harder-python-tools-code-modernization/">Read more</a></p> </div> <div class="card-common--extras"> <span class="card-common--date">March 27, 2025</span> <div class="card-common--social-media"> <!-- Twitter --> <a target="_blank" href="https://twitter.com/intent/tweet?text=Upgrade%20Smarter%2C%20Not%20Harder%3A%20Python%20Tools%20for%20Code%20Modernization&tw_p=tweetbutton&url=https://www.caktusgroup.com/blog/2025/03/27/upgrade-smarter-not-harder-python-tools-code-modernization/"><span class="fa"><svg version="1.1" id="svg_twitter" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 449.956 449.956" style="enable-background:new 0 0 449.956 449.956;" xml:space="preserve"> <title>Twitter</title> <g> <path d="M449.956,85.657c-17.702,7.614-35.408,12.369-53.102,14.279c19.985-11.991,33.503-28.931,40.546-50.819 c-18.281,10.847-37.787,18.268-58.532,22.267c-18.274-19.414-40.73-29.125-67.383-29.125c-25.502,0-47.246,8.992-65.24,26.98 c-17.984,17.987-26.977,39.731-26.977,65.235c0,6.851,0.76,13.896,2.284,21.128c-37.688-1.903-73.042-11.372-106.068-28.407 C82.46,110.158,54.433,87.46,31.403,59.101c-8.375,14.272-12.564,29.787-12.564,46.536c0,15.798,3.711,30.456,11.138,43.97 c7.422,13.512,17.417,24.455,29.98,32.831c-14.849-0.572-28.743-4.475-41.684-11.708v1.142c0,22.271,6.995,41.824,20.983,58.674 c13.99,16.848,31.645,27.453,52.961,31.833c-7.995,2.091-16.086,3.138-24.269,3.138c-5.33,0-11.136-0.475-17.416-1.42 c5.9,18.459,16.75,33.633,32.546,45.535c15.799,11.896,33.691,18.028,53.677,18.418c-33.498,26.262-71.66,39.393-114.486,39.393 c-8.186,0-15.607-0.373-22.27-1.139c42.827,27.596,90.03,41.394,141.612,41.394c32.738,0,63.478-5.181,92.21-15.557 c28.746-10.369,53.297-24.267,73.665-41.686c20.362-17.415,37.925-37.448,52.674-60.097c14.75-22.651,25.738-46.298,32.977-70.946 c7.23-24.653,10.848-49.344,10.848-74.092c0-5.33-0.096-9.325-0.287-11.991C421.785,120.202,437.202,104.306,449.956,85.657z"/> </g> </svg> </span></a> &ensp; <!-- LinkedIn --> <a target="_blank" href="https://www.linkedin.com/cws/share?url=https://www.caktusgroup.com/blog/2025/03/27/upgrade-smarter-not-harder-python-tools-code-modernization/"><span class="fa"><svg version="1.1" id="svg_linkedin" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 438.536 438.535" style="enable-background:new 0 0 438.536 438.535;" xml:space="preserve"> <title>LinkedIn</title> <g> <g> <rect x="5.424" y="145.895" width="94.216" height="282.932"/> <path d="M408.842,171.739c-19.791-21.604-45.967-32.408-78.512-32.408c-11.991,0-22.891,1.475-32.695,4.427 c-9.801,2.95-18.079,7.089-24.838,12.419c-6.755,5.33-12.135,10.278-16.129,14.844c-3.798,4.337-7.512,9.389-11.136,15.104 v-40.232h-93.935l0.288,13.706c0.193,9.139,0.288,37.307,0.288,84.508c0,47.205-0.19,108.777-0.572,184.722h93.931V270.942 c0-9.705,1.041-17.412,3.139-23.127c4-9.712,10.037-17.843,18.131-24.407c8.093-6.572,18.13-9.855,30.125-9.855 c16.364,0,28.407,5.662,36.117,16.987c7.707,11.324,11.561,26.98,11.561,46.966V428.82h93.931V266.664 C438.529,224.976,428.639,193.336,408.842,171.739z"/> <path d="M53.103,9.708c-15.796,0-28.595,4.619-38.4,13.848C4.899,32.787,0,44.441,0,58.529c0,13.891,4.758,25.505,14.275,34.829 c9.514,9.325,22.078,13.99,37.685,13.99h0.571c15.99,0,28.887-4.661,38.688-13.99c9.801-9.324,14.606-20.934,14.417-34.829 c-0.19-14.087-5.047-25.742-14.561-34.973C81.562,14.323,68.9,9.708,53.103,9.708z"/> </g> </g> </svg> </span></a> &ensp; <!-- Facebook --> <a target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https://www.caktusgroup.com/blog/2025/03/27/upgrade-smarter-not-harder-python-tools-code-modernization/"><span class="fa"><svg version="1.1" id="svg_facebook" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 470.513 470.513" style="enable-background:new 0 0 470.513 470.513;" xml:space="preserve"> <title>Facebook</title> <g> <path d="M271.521,154.17v-40.541c0-6.086,0.28-10.8,0.849-14.13c0.567-3.335,1.857-6.615,3.859-9.853 c1.999-3.236,5.236-5.47,9.706-6.708c4.476-1.24,10.424-1.858,17.85-1.858h40.539V0h-64.809c-37.5,0-64.433,8.897-80.803,26.691 c-16.368,17.798-24.551,44.014-24.551,78.658v48.82h-48.542v81.086h48.539v235.256h97.362V235.256h64.805l8.566-81.086H271.521z"/> </g> </svg> </span></a> </div> </div> </li> <!-- End-of-card --> <!-- start-of-card --> <!-- The blogpost class is used to find the number of cards on the page in the jscroll callback --> <li class="card card-blog blogpost preprocess"> <div class="card-common--image_container"> <a href="/blog/2025/03/19/how-use-regexp_matches-and-regexp_match-postgresql/" class="card-common--inner_image_container"><picture> <source srcset="https://storage.caktusgroup.com/media/thumbnail-cache/7c/b3/7cb38b1b1bf455fb6544dd6e4a9f7bb5.png" media="(min-width: 400px) and (max-width: 768px)" /> <img src="https://storage.caktusgroup.com/media/thumbnail-cache/22/b3/22b3ac8f6eeff5cac2405c81da48b141.png" alt="How to Use regexp_matches and regexp_match in PostgreSQL"> </picture></a> </div> <div class="card-blog--main_container"> <div> <a class="card-blog--tag" href="/blog/categories/technical/">Technical</a> </div> <h3><a href="/blog/2025/03/19/how-use-regexp_matches-and-regexp_match-postgresql/">How to Use regexp_matches and regexp_match in PostgreSQL</a></h3> <!-- The author --> <div class="card-common--author_container"> <a class="card-common--author" href="/about/tobias-mcnulty/">Tobias McNulty</a> </div> <!-- The blogpost description --> <p> Introduction regexp_matches() and regexp_match() are two similar string functions that support regular expression matching directly in the PostgreSQL database. regexp_matches(...&nbsp;<a href="/blog/2025/03/19/how-use-regexp_matches-and-regexp_match-postgresql/">Read more</a></p> </div> <div class="card-common--extras"> <span class="card-common--date">March 19, 2025</span> <div class="card-common--social-media"> <!-- Twitter --> <a target="_blank" href="https://twitter.com/intent/tweet?text=How%20to%20Use%20regexp_matches%20and%20regexp_match%20in%20PostgreSQL&tw_p=tweetbutton&url=https://www.caktusgroup.com/blog/2025/03/19/how-use-regexp_matches-and-regexp_match-postgresql/"><span class="fa"><svg version="1.1" id="svg_twitter" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 449.956 449.956" style="enable-background:new 0 0 449.956 449.956;" xml:space="preserve"> <title>Twitter</title> <g> <path d="M449.956,85.657c-17.702,7.614-35.408,12.369-53.102,14.279c19.985-11.991,33.503-28.931,40.546-50.819 c-18.281,10.847-37.787,18.268-58.532,22.267c-18.274-19.414-40.73-29.125-67.383-29.125c-25.502,0-47.246,8.992-65.24,26.98 c-17.984,17.987-26.977,39.731-26.977,65.235c0,6.851,0.76,13.896,2.284,21.128c-37.688-1.903-73.042-11.372-106.068-28.407 C82.46,110.158,54.433,87.46,31.403,59.101c-8.375,14.272-12.564,29.787-12.564,46.536c0,15.798,3.711,30.456,11.138,43.97 c7.422,13.512,17.417,24.455,29.98,32.831c-14.849-0.572-28.743-4.475-41.684-11.708v1.142c0,22.271,6.995,41.824,20.983,58.674 c13.99,16.848,31.645,27.453,52.961,31.833c-7.995,2.091-16.086,3.138-24.269,3.138c-5.33,0-11.136-0.475-17.416-1.42 c5.9,18.459,16.75,33.633,32.546,45.535c15.799,11.896,33.691,18.028,53.677,18.418c-33.498,26.262-71.66,39.393-114.486,39.393 c-8.186,0-15.607-0.373-22.27-1.139c42.827,27.596,90.03,41.394,141.612,41.394c32.738,0,63.478-5.181,92.21-15.557 c28.746-10.369,53.297-24.267,73.665-41.686c20.362-17.415,37.925-37.448,52.674-60.097c14.75-22.651,25.738-46.298,32.977-70.946 c7.23-24.653,10.848-49.344,10.848-74.092c0-5.33-0.096-9.325-0.287-11.991C421.785,120.202,437.202,104.306,449.956,85.657z"/> </g> </svg> </span></a> &ensp; <!-- LinkedIn --> <a target="_blank" href="https://www.linkedin.com/cws/share?url=https://www.caktusgroup.com/blog/2025/03/19/how-use-regexp_matches-and-regexp_match-postgresql/"><span class="fa"><svg version="1.1" id="svg_linkedin" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 438.536 438.535" style="enable-background:new 0 0 438.536 438.535;" xml:space="preserve"> <title>LinkedIn</title> <g> <g> <rect x="5.424" y="145.895" width="94.216" height="282.932"/> <path d="M408.842,171.739c-19.791-21.604-45.967-32.408-78.512-32.408c-11.991,0-22.891,1.475-32.695,4.427 c-9.801,2.95-18.079,7.089-24.838,12.419c-6.755,5.33-12.135,10.278-16.129,14.844c-3.798,4.337-7.512,9.389-11.136,15.104 v-40.232h-93.935l0.288,13.706c0.193,9.139,0.288,37.307,0.288,84.508c0,47.205-0.19,108.777-0.572,184.722h93.931V270.942 c0-9.705,1.041-17.412,3.139-23.127c4-9.712,10.037-17.843,18.131-24.407c8.093-6.572,18.13-9.855,30.125-9.855 c16.364,0,28.407,5.662,36.117,16.987c7.707,11.324,11.561,26.98,11.561,46.966V428.82h93.931V266.664 C438.529,224.976,428.639,193.336,408.842,171.739z"/> <path d="M53.103,9.708c-15.796,0-28.595,4.619-38.4,13.848C4.899,32.787,0,44.441,0,58.529c0,13.891,4.758,25.505,14.275,34.829 c9.514,9.325,22.078,13.99,37.685,13.99h0.571c15.99,0,28.887-4.661,38.688-13.99c9.801-9.324,14.606-20.934,14.417-34.829 c-0.19-14.087-5.047-25.742-14.561-34.973C81.562,14.323,68.9,9.708,53.103,9.708z"/> </g> </g> </svg> </span></a> &ensp; <!-- Facebook --> <a target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https://www.caktusgroup.com/blog/2025/03/19/how-use-regexp_matches-and-regexp_match-postgresql/"><span class="fa"><svg version="1.1" id="svg_facebook" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 470.513 470.513" style="enable-background:new 0 0 470.513 470.513;" xml:space="preserve"> <title>Facebook</title> <g> <path d="M271.521,154.17v-40.541c0-6.086,0.28-10.8,0.849-14.13c0.567-3.335,1.857-6.615,3.859-9.853 c1.999-3.236,5.236-5.47,9.706-6.708c4.476-1.24,10.424-1.858,17.85-1.858h40.539V0h-64.809c-37.5,0-64.433,8.897-80.803,26.691 c-16.368,17.798-24.551,44.014-24.551,78.658v48.82h-48.542v81.086h48.539v235.256h97.362V235.256h64.805l8.566-81.086H271.521z"/> </g> </svg> </span></a> </div> </div> </li> <!-- End-of-card --> <!-- start-of-card --> <!-- The blogpost class is used to find the number of cards on the page in the jscroll callback --> <li class="card card-blog blogpost preprocess"> <div class="card-common--image_container"> <a href="/blog/2025/03/18/how-create-helm-chart-django-app/" class="card-common--inner_image_container"><picture> <source srcset="https://storage.caktusgroup.com/media/thumbnail-cache/7c/b3/7cb38b1b1bf455fb6544dd6e4a9f7bb5.png" media="(min-width: 400px) and (max-width: 768px)" /> <img src="https://storage.caktusgroup.com/media/thumbnail-cache/22/b3/22b3ac8f6eeff5cac2405c81da48b141.png" alt="How to Create a Helm Chart for a Django App"> </picture></a> </div> <div class="card-blog--main_container"> <div> <a class="card-blog--tag" href="/blog/categories/technical/">Technical</a> </div> <h3><a href="/blog/2025/03/18/how-create-helm-chart-django-app/">How to Create a Helm Chart for a Django App</a></h3> <!-- The author --> <div class="card-common--author_container"> <span>Simon Kagwi</span> </div> <!-- The blogpost description --> <p> At Caktus, we use Helm charts to simplify our deployment process for Django projects. Helm is a package manager for Kubernetes, and using Helm charts allows us to automate the ...&nbsp;<a href="/blog/2025/03/18/how-create-helm-chart-django-app/">Read more</a></p> </div> <div class="card-common--extras"> <span class="card-common--date">March 18, 2025</span> <div class="card-common--social-media"> <!-- Twitter --> <a target="_blank" href="https://twitter.com/intent/tweet?text=How%20to%20Create%20a%20Helm%20Chart%20for%20a%20Django%20App&tw_p=tweetbutton&url=https://www.caktusgroup.com/blog/2025/03/18/how-create-helm-chart-django-app/"><span class="fa"><svg version="1.1" id="svg_twitter" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 449.956 449.956" style="enable-background:new 0 0 449.956 449.956;" xml:space="preserve"> <title>Twitter</title> <g> <path d="M449.956,85.657c-17.702,7.614-35.408,12.369-53.102,14.279c19.985-11.991,33.503-28.931,40.546-50.819 c-18.281,10.847-37.787,18.268-58.532,22.267c-18.274-19.414-40.73-29.125-67.383-29.125c-25.502,0-47.246,8.992-65.24,26.98 c-17.984,17.987-26.977,39.731-26.977,65.235c0,6.851,0.76,13.896,2.284,21.128c-37.688-1.903-73.042-11.372-106.068-28.407 C82.46,110.158,54.433,87.46,31.403,59.101c-8.375,14.272-12.564,29.787-12.564,46.536c0,15.798,3.711,30.456,11.138,43.97 c7.422,13.512,17.417,24.455,29.98,32.831c-14.849-0.572-28.743-4.475-41.684-11.708v1.142c0,22.271,6.995,41.824,20.983,58.674 c13.99,16.848,31.645,27.453,52.961,31.833c-7.995,2.091-16.086,3.138-24.269,3.138c-5.33,0-11.136-0.475-17.416-1.42 c5.9,18.459,16.75,33.633,32.546,45.535c15.799,11.896,33.691,18.028,53.677,18.418c-33.498,26.262-71.66,39.393-114.486,39.393 c-8.186,0-15.607-0.373-22.27-1.139c42.827,27.596,90.03,41.394,141.612,41.394c32.738,0,63.478-5.181,92.21-15.557 c28.746-10.369,53.297-24.267,73.665-41.686c20.362-17.415,37.925-37.448,52.674-60.097c14.75-22.651,25.738-46.298,32.977-70.946 c7.23-24.653,10.848-49.344,10.848-74.092c0-5.33-0.096-9.325-0.287-11.991C421.785,120.202,437.202,104.306,449.956,85.657z"/> </g> </svg> </span></a> &ensp; <!-- LinkedIn --> <a target="_blank" href="https://www.linkedin.com/cws/share?url=https://www.caktusgroup.com/blog/2025/03/18/how-create-helm-chart-django-app/"><span class="fa"><svg version="1.1" id="svg_linkedin" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 438.536 438.535" style="enable-background:new 0 0 438.536 438.535;" xml:space="preserve"> <title>LinkedIn</title> <g> <g> <rect x="5.424" y="145.895" width="94.216" height="282.932"/> <path d="M408.842,171.739c-19.791-21.604-45.967-32.408-78.512-32.408c-11.991,0-22.891,1.475-32.695,4.427 c-9.801,2.95-18.079,7.089-24.838,12.419c-6.755,5.33-12.135,10.278-16.129,14.844c-3.798,4.337-7.512,9.389-11.136,15.104 v-40.232h-93.935l0.288,13.706c0.193,9.139,0.288,37.307,0.288,84.508c0,47.205-0.19,108.777-0.572,184.722h93.931V270.942 c0-9.705,1.041-17.412,3.139-23.127c4-9.712,10.037-17.843,18.131-24.407c8.093-6.572,18.13-9.855,30.125-9.855 c16.364,0,28.407,5.662,36.117,16.987c7.707,11.324,11.561,26.98,11.561,46.966V428.82h93.931V266.664 C438.529,224.976,428.639,193.336,408.842,171.739z"/> <path d="M53.103,9.708c-15.796,0-28.595,4.619-38.4,13.848C4.899,32.787,0,44.441,0,58.529c0,13.891,4.758,25.505,14.275,34.829 c9.514,9.325,22.078,13.99,37.685,13.99h0.571c15.99,0,28.887-4.661,38.688-13.99c9.801-9.324,14.606-20.934,14.417-34.829 c-0.19-14.087-5.047-25.742-14.561-34.973C81.562,14.323,68.9,9.708,53.103,9.708z"/> </g> </g> </svg> </span></a> &ensp; <!-- Facebook --> <a target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https://www.caktusgroup.com/blog/2025/03/18/how-create-helm-chart-django-app/"><span class="fa"><svg version="1.1" id="svg_facebook" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 470.513 470.513" style="enable-background:new 0 0 470.513 470.513;" xml:space="preserve"> <title>Facebook</title> <g> <path d="M271.521,154.17v-40.541c0-6.086,0.28-10.8,0.849-14.13c0.567-3.335,1.857-6.615,3.859-9.853 c1.999-3.236,5.236-5.47,9.706-6.708c4.476-1.24,10.424-1.858,17.85-1.858h40.539V0h-64.809c-37.5,0-64.433,8.897-80.803,26.691 c-16.368,17.798-24.551,44.014-24.551,78.658v48.82h-48.542v81.086h48.539v235.256h97.362V235.256h64.805l8.566-81.086H271.521z"/> </g> </svg> </span></a> </div> </div> </li> <!-- End-of-card --> <!-- start-of-card --> <!-- The blogpost class is used to find the number of cards on the page in the jscroll callback --> <li class="card card-blog blogpost preprocess"> <div class="card-common--image_container"> <a href="/blog/2025/03/17/one-thing-look-out-while-testing-django-import-export/" class="card-common--inner_image_container"><picture> <source srcset="https://storage.caktusgroup.com/media/thumbnail-cache/55/09/550959aab345a3b11960751d31f6fd0d.png" media="(min-width: 400px) and (max-width: 768px)" /> <img src="https://storage.caktusgroup.com/media/thumbnail-cache/82/3d/823d506bd028005c591720c5ae5673f1.png" alt="One Thing to Look Out For While Testing django-import-export"> </picture></a> </div> <div class="card-blog--main_container"> <div> <a class="card-blog--tag" href="/blog/categories/testing-qa/">Testing &amp; QA</a> </div> <h3><a href="/blog/2025/03/17/one-thing-look-out-while-testing-django-import-export/">One Thing to Look Out For While Testing django-import-export</a></h3> <!-- The author --> <div class="card-common--author_container"> <a class="card-common--author" href="/about/gerald-carlton/">Gerald Carlton</a> </div> <!-- The blogpost description --> <p> Often the applications we build at Caktus deal with large sets of Django objects. The attributes of these objects can vary, and may need updating in certain instances. One of t...&nbsp;<a href="/blog/2025/03/17/one-thing-look-out-while-testing-django-import-export/">Read more</a></p> </div> <div class="card-common--extras"> <span class="card-common--date">March 17, 2025</span> <div class="card-common--social-media"> <!-- Twitter --> <a target="_blank" href="https://twitter.com/intent/tweet?text=One%20Thing%20to%20Look%20Out%20For%20While%20Testing%20django-import-export&tw_p=tweetbutton&url=https://www.caktusgroup.com/blog/2025/03/17/one-thing-look-out-while-testing-django-import-export/"><span class="fa"><svg version="1.1" id="svg_twitter" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 449.956 449.956" style="enable-background:new 0 0 449.956 449.956;" xml:space="preserve"> <title>Twitter</title> <g> <path d="M449.956,85.657c-17.702,7.614-35.408,12.369-53.102,14.279c19.985-11.991,33.503-28.931,40.546-50.819 c-18.281,10.847-37.787,18.268-58.532,22.267c-18.274-19.414-40.73-29.125-67.383-29.125c-25.502,0-47.246,8.992-65.24,26.98 c-17.984,17.987-26.977,39.731-26.977,65.235c0,6.851,0.76,13.896,2.284,21.128c-37.688-1.903-73.042-11.372-106.068-28.407 C82.46,110.158,54.433,87.46,31.403,59.101c-8.375,14.272-12.564,29.787-12.564,46.536c0,15.798,3.711,30.456,11.138,43.97 c7.422,13.512,17.417,24.455,29.98,32.831c-14.849-0.572-28.743-4.475-41.684-11.708v1.142c0,22.271,6.995,41.824,20.983,58.674 c13.99,16.848,31.645,27.453,52.961,31.833c-7.995,2.091-16.086,3.138-24.269,3.138c-5.33,0-11.136-0.475-17.416-1.42 c5.9,18.459,16.75,33.633,32.546,45.535c15.799,11.896,33.691,18.028,53.677,18.418c-33.498,26.262-71.66,39.393-114.486,39.393 c-8.186,0-15.607-0.373-22.27-1.139c42.827,27.596,90.03,41.394,141.612,41.394c32.738,0,63.478-5.181,92.21-15.557 c28.746-10.369,53.297-24.267,73.665-41.686c20.362-17.415,37.925-37.448,52.674-60.097c14.75-22.651,25.738-46.298,32.977-70.946 c7.23-24.653,10.848-49.344,10.848-74.092c0-5.33-0.096-9.325-0.287-11.991C421.785,120.202,437.202,104.306,449.956,85.657z"/> </g> </svg> </span></a> &ensp; <!-- LinkedIn --> <a target="_blank" href="https://www.linkedin.com/cws/share?url=https://www.caktusgroup.com/blog/2025/03/17/one-thing-look-out-while-testing-django-import-export/"><span class="fa"><svg version="1.1" id="svg_linkedin" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 438.536 438.535" style="enable-background:new 0 0 438.536 438.535;" xml:space="preserve"> <title>LinkedIn</title> <g> <g> <rect x="5.424" y="145.895" width="94.216" height="282.932"/> <path d="M408.842,171.739c-19.791-21.604-45.967-32.408-78.512-32.408c-11.991,0-22.891,1.475-32.695,4.427 c-9.801,2.95-18.079,7.089-24.838,12.419c-6.755,5.33-12.135,10.278-16.129,14.844c-3.798,4.337-7.512,9.389-11.136,15.104 v-40.232h-93.935l0.288,13.706c0.193,9.139,0.288,37.307,0.288,84.508c0,47.205-0.19,108.777-0.572,184.722h93.931V270.942 c0-9.705,1.041-17.412,3.139-23.127c4-9.712,10.037-17.843,18.131-24.407c8.093-6.572,18.13-9.855,30.125-9.855 c16.364,0,28.407,5.662,36.117,16.987c7.707,11.324,11.561,26.98,11.561,46.966V428.82h93.931V266.664 C438.529,224.976,428.639,193.336,408.842,171.739z"/> <path d="M53.103,9.708c-15.796,0-28.595,4.619-38.4,13.848C4.899,32.787,0,44.441,0,58.529c0,13.891,4.758,25.505,14.275,34.829 c9.514,9.325,22.078,13.99,37.685,13.99h0.571c15.99,0,28.887-4.661,38.688-13.99c9.801-9.324,14.606-20.934,14.417-34.829 c-0.19-14.087-5.047-25.742-14.561-34.973C81.562,14.323,68.9,9.708,53.103,9.708z"/> </g> </g> </svg> </span></a> &ensp; <!-- Facebook --> <a target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https://www.caktusgroup.com/blog/2025/03/17/one-thing-look-out-while-testing-django-import-export/"><span class="fa"><svg version="1.1" id="svg_facebook" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 470.513 470.513" style="enable-background:new 0 0 470.513 470.513;" xml:space="preserve"> <title>Facebook</title> <g> <path d="M271.521,154.17v-40.541c0-6.086,0.28-10.8,0.849-14.13c0.567-3.335,1.857-6.615,3.859-9.853 c1.999-3.236,5.236-5.47,9.706-6.708c4.476-1.24,10.424-1.858,17.85-1.858h40.539V0h-64.809c-37.5,0-64.433,8.897-80.803,26.691 c-16.368,17.798-24.551,44.014-24.551,78.658v48.82h-48.542v81.086h48.539v235.256h97.362V235.256h64.805l8.566-81.086H271.521z"/> </g> </svg> </span></a> </div> </div> </li> <!-- End-of-card --> <!-- start-of-card --> <!-- The blogpost class is used to find the number of cards on the page in the jscroll callback --> <li class="card card-blog blogpost preprocess"> <div class="card-common--image_container"> <a href="/blog/2025/03/12/private-ngrok-dev-servers-tailscale/" class="card-common--inner_image_container"><picture> <source srcset="https://storage.caktusgroup.com/media/thumbnail-cache/83/9e/839e8839049fdb18cffa7cdb4d683ec5.png" media="(min-width: 400px) and (max-width: 768px)" /> <img src="https://storage.caktusgroup.com/media/thumbnail-cache/ff/4e/ff4e61875442be1a9ff500a1e6603ef2.png" alt="Private, ngrok-like Dev Servers with Tailscale"> </picture></a> </div> <div class="card-blog--main_container"> <div> <a class="card-blog--tag" href="/blog/categories/technical/">Technical</a> </div> <h3><a href="/blog/2025/03/12/private-ngrok-dev-servers-tailscale/">Private, ngrok-like Dev Servers with Tailscale</a></h3> <!-- The author --> <div class="card-common--author_container"> <a class="card-common--author" href="/about/tobias-mcnulty/">Tobias McNulty</a> </div> <!-- The blogpost description --> <p> This past week, I learned that it's possible to run Tailscale in a local docker-compose.yml on a developer's workstation, and securely allow access to services in the docker-co...&nbsp;<a href="/blog/2025/03/12/private-ngrok-dev-servers-tailscale/">Read more</a></p> </div> <div class="card-common--extras"> <span class="card-common--date">March 12, 2025</span> <div class="card-common--social-media"> <!-- Twitter --> <a target="_blank" href="https://twitter.com/intent/tweet?text=Private%2C%20ngrok-like%20Dev%20Servers%20with%20Tailscale&tw_p=tweetbutton&url=https://www.caktusgroup.com/blog/2025/03/12/private-ngrok-dev-servers-tailscale/"><span class="fa"><svg version="1.1" id="svg_twitter" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 449.956 449.956" style="enable-background:new 0 0 449.956 449.956;" xml:space="preserve"> <title>Twitter</title> <g> <path d="M449.956,85.657c-17.702,7.614-35.408,12.369-53.102,14.279c19.985-11.991,33.503-28.931,40.546-50.819 c-18.281,10.847-37.787,18.268-58.532,22.267c-18.274-19.414-40.73-29.125-67.383-29.125c-25.502,0-47.246,8.992-65.24,26.98 c-17.984,17.987-26.977,39.731-26.977,65.235c0,6.851,0.76,13.896,2.284,21.128c-37.688-1.903-73.042-11.372-106.068-28.407 C82.46,110.158,54.433,87.46,31.403,59.101c-8.375,14.272-12.564,29.787-12.564,46.536c0,15.798,3.711,30.456,11.138,43.97 c7.422,13.512,17.417,24.455,29.98,32.831c-14.849-0.572-28.743-4.475-41.684-11.708v1.142c0,22.271,6.995,41.824,20.983,58.674 c13.99,16.848,31.645,27.453,52.961,31.833c-7.995,2.091-16.086,3.138-24.269,3.138c-5.33,0-11.136-0.475-17.416-1.42 c5.9,18.459,16.75,33.633,32.546,45.535c15.799,11.896,33.691,18.028,53.677,18.418c-33.498,26.262-71.66,39.393-114.486,39.393 c-8.186,0-15.607-0.373-22.27-1.139c42.827,27.596,90.03,41.394,141.612,41.394c32.738,0,63.478-5.181,92.21-15.557 c28.746-10.369,53.297-24.267,73.665-41.686c20.362-17.415,37.925-37.448,52.674-60.097c14.75-22.651,25.738-46.298,32.977-70.946 c7.23-24.653,10.848-49.344,10.848-74.092c0-5.33-0.096-9.325-0.287-11.991C421.785,120.202,437.202,104.306,449.956,85.657z"/> </g> </svg> </span></a> &ensp; <!-- LinkedIn --> <a target="_blank" href="https://www.linkedin.com/cws/share?url=https://www.caktusgroup.com/blog/2025/03/12/private-ngrok-dev-servers-tailscale/"><span class="fa"><svg version="1.1" id="svg_linkedin" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 438.536 438.535" style="enable-background:new 0 0 438.536 438.535;" xml:space="preserve"> <title>LinkedIn</title> <g> <g> <rect x="5.424" y="145.895" width="94.216" height="282.932"/> <path d="M408.842,171.739c-19.791-21.604-45.967-32.408-78.512-32.408c-11.991,0-22.891,1.475-32.695,4.427 c-9.801,2.95-18.079,7.089-24.838,12.419c-6.755,5.33-12.135,10.278-16.129,14.844c-3.798,4.337-7.512,9.389-11.136,15.104 v-40.232h-93.935l0.288,13.706c0.193,9.139,0.288,37.307,0.288,84.508c0,47.205-0.19,108.777-0.572,184.722h93.931V270.942 c0-9.705,1.041-17.412,3.139-23.127c4-9.712,10.037-17.843,18.131-24.407c8.093-6.572,18.13-9.855,30.125-9.855 c16.364,0,28.407,5.662,36.117,16.987c7.707,11.324,11.561,26.98,11.561,46.966V428.82h93.931V266.664 C438.529,224.976,428.639,193.336,408.842,171.739z"/> <path d="M53.103,9.708c-15.796,0-28.595,4.619-38.4,13.848C4.899,32.787,0,44.441,0,58.529c0,13.891,4.758,25.505,14.275,34.829 c9.514,9.325,22.078,13.99,37.685,13.99h0.571c15.99,0,28.887-4.661,38.688-13.99c9.801-9.324,14.606-20.934,14.417-34.829 c-0.19-14.087-5.047-25.742-14.561-34.973C81.562,14.323,68.9,9.708,53.103,9.708z"/> </g> </g> </svg> </span></a> &ensp; <!-- Facebook --> <a target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https://www.caktusgroup.com/blog/2025/03/12/private-ngrok-dev-servers-tailscale/"><span class="fa"><svg version="1.1" id="svg_facebook" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 470.513 470.513" style="enable-background:new 0 0 470.513 470.513;" xml:space="preserve"> <title>Facebook</title> <g> <path d="M271.521,154.17v-40.541c0-6.086,0.28-10.8,0.849-14.13c0.567-3.335,1.857-6.615,3.859-9.853 c1.999-3.236,5.236-5.47,9.706-6.708c4.476-1.24,10.424-1.858,17.85-1.858h40.539V0h-64.809c-37.5,0-64.433,8.897-80.803,26.691 c-16.368,17.798-24.551,44.014-24.551,78.658v48.82h-48.542v81.086h48.539v235.256h97.362V235.256h64.805l8.566-81.086H271.521z"/> </g> </svg> </span></a> </div> </div> </li> <!-- End-of-card --> </ul> </div> </div> </div> <div id="next-page" class="scroll"> <div class="full row center lt-grey-bg"> <a id="next" href="/blog/?page=2" class="hollow-button-blue"> View more posts <span class="fa"><?xml version="1.0" encoding="utf-8"?> <svg width="20" height="20" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><title>Plus</title><path d="M1344 960v-128q0-26-19-45t-45-19h-256v-256q0-26-19-45t-45-19h-128q-26 0-45 19t-19 45v256h-256q-26 0-45 19t-19 45v128q0 26 19 45t45 19h256v256q0 26 19 45t45 19h128q26 0 45-19t19-45v-256h256q26 0 45-19t19-45zm320-64q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/></svg></span> </a> </div> </div> </section> <!-- Upcoming Events --> <section class="home-page"> <div class="full our-blog blue-bg underline-link"> <div class="wrapper"> <div class="spacer"></div> <h2 class="section-title blue center"> <div>Upcoming Events</div> </h2> <div class="spacer"></div> </div> </div> <!-- View more events button --> <div class="full view-more blue-bg center"> <a href="/events/" class="hollow-button-blue">View more events</a> </div> </section> <!-- END OF THE ROAD --> </main><!-- /end mainContent --> </div><!-- /end wrap --> <footer id="footer" class="full footer"> <div class="wrapper"> <div class="half"> <div class="half"> <div><h3>Contact us</h3></div> <p> <a href="mailto:solutions@caktusgroup.com" target="_blank">solutions@caktusgroup.com</a> </p> <p> T 919-951-0052 <br> F 919-928-5516 </p> <p> 108 Morris St, Suite 2<br> Durham, NC 27701<br> </p> </div> <div class="half site-map"> <div><h3>Quick links</h3></div> <ul> <li><a href="/services/">Services</a></li> <li><a href="/casestudies/">Our work</a></li> <li><a href="/about/">About us</a></li> <li><a href="/blog/">Blog</a></li> <li><a href="/careers/">Careers</a></li> <li><a href="/events/">Events</a></li> <li><a href="/talks/">Talks</a></li> <li><a href="/ebooks/">Ebooks</a></li> <li><a href="/press/">Press</a></li> <li><a href="/contact/">Contact</a></li> </ul> </div> </div><!--End first half--> <div class="half"> <div class="half newsletter-sign-up"> <div><h3>Newsletter</h3></div> <form action="/subscribe/" method="post" class="newsletter-form form-inline" novalidate> <input type="hidden" name="csrfmiddlewaretoken" value="tBOndbd0hPwWaCd6gDRyZua5yF9MufbOv6IBwDGbFrrNf693832tbPGDWyZiAh73"> <div style="display: none;"> <label> Leave this blank to prove your humanity: <input type="text" name="body" value="" /> </label> </div> <ul class="errors list-unstyled"></ul> <label class="sr-only" for="id_email"> Email address </label> <div class="sign-up-container"> <span class="icon"> <button type="submit" aria-label="Submit"> <span class="fa grey"><svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 511.626 511.627" style="enable-background:new 0 0 511.626 511.627;" xml:space="preserve"> <title>Envelope</title> <g> <path d="M498.208,68.235c-8.945-8.947-19.701-13.418-32.261-13.418H45.682c-12.562,0-23.318,4.471-32.264,13.418 C4.471,77.18,0,87.935,0,100.499v310.633c0,12.566,4.471,23.312,13.418,32.257c8.945,8.953,19.701,13.422,32.264,13.422h420.266 c12.56,0,23.315-4.469,32.261-13.422c8.949-8.945,13.418-19.697,13.418-32.257V100.499 C511.626,87.935,507.158,77.18,498.208,68.235z M475.078,411.125c0,2.475-0.903,4.616-2.714,6.424 c-1.81,1.81-3.949,2.706-6.42,2.706H45.679c-2.474,0-4.616-0.896-6.423-2.706c-1.809-1.808-2.712-3.949-2.712-6.424V191.858 c6.09,6.852,12.657,13.134,19.7,18.843c51.012,39.209,91.553,71.374,121.627,96.5c9.707,8.186,17.607,14.561,23.697,19.13 c6.09,4.571,14.322,9.185,24.694,13.846c10.373,4.668,20.129,6.991,29.265,6.991h0.287h0.284c9.134,0,18.894-2.323,29.263-6.991 c10.376-4.661,18.613-9.274,24.701-13.846c6.089-4.569,13.99-10.944,23.698-19.13c30.074-25.126,70.61-57.291,121.624-96.5 c7.043-5.708,13.613-11.991,19.694-18.843V411.125L475.078,411.125z M475.078,107.92v3.14c0,11.229-4.421,23.745-13.271,37.543 c-8.851,13.798-18.419,24.792-28.691,32.974c-36.74,28.936-74.897,59.101-114.495,90.506c-1.14,0.951-4.474,3.757-9.996,8.418 c-5.514,4.668-9.894,8.241-13.131,10.712c-3.241,2.478-7.471,5.475-12.703,8.993c-5.236,3.518-10.041,6.14-14.418,7.851 c-4.377,1.707-8.47,2.562-12.275,2.562h-0.284h-0.287c-3.806,0-7.895-0.855-12.275-2.562c-4.377-1.711-9.185-4.333-14.417-7.851 c-5.231-3.519-9.467-6.516-12.703-8.993c-3.234-2.471-7.614-6.044-13.132-10.712c-5.52-4.661-8.854-7.467-9.995-8.418 c-39.589-31.406-77.75-61.57-114.487-90.506c-27.981-22.076-41.969-49.106-41.969-81.083c0-2.472,0.903-4.615,2.712-6.421 c1.809-1.809,3.949-2.714,6.423-2.714h420.266c1.52,0.855,2.854,1.093,3.997,0.715c1.143-0.385,1.998,0.331,2.566,2.138 c0.571,1.809,1.095,2.664,1.57,2.57c0.477-0.096,0.764,1.093,0.859,3.571c0.089,2.473,0.137,3.718,0.137,3.718V107.92 L475.078,107.92z"/> </g> </svg> </span> </button> </span> <input type="email" value="" name="email" style="border: none" id="id_email" class="form-control" placeholder="youremail@email.com" required /> <input type="hidden" name="page_name" id="id_page_name"> </div> </form> <div class="newsletter-info"> <div>Get tips, see case studies, and stay up to date on Caktus news. <br><a href="https://learn.caktusgroup.com/newsletter" style="font-weight: bold;">Learn more.</a></div> </div> <p> <a href="https://www.iubenda.com/privacy-policy/91739120">Privacy Policy</a> </p> <p> <a href="https://www.iubenda.com/privacy-policy/91739120/cookie-policy">Cookie Policy</a> </p> </div> <div class="half social-media"> <div id="socialicons" class="socialicons"> <div><h3>Follow us</h3></div> <ul> <li> <a href="https://twitter.com/caktusgroup" target="_blank"> <span class="fa grey"><svg version="1.1" id="svg_twitter" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 449.956 449.956" style="enable-background:new 0 0 449.956 449.956;" xml:space="preserve"> <title>Twitter</title> <g> <path d="M449.956,85.657c-17.702,7.614-35.408,12.369-53.102,14.279c19.985-11.991,33.503-28.931,40.546-50.819 c-18.281,10.847-37.787,18.268-58.532,22.267c-18.274-19.414-40.73-29.125-67.383-29.125c-25.502,0-47.246,8.992-65.24,26.98 c-17.984,17.987-26.977,39.731-26.977,65.235c0,6.851,0.76,13.896,2.284,21.128c-37.688-1.903-73.042-11.372-106.068-28.407 C82.46,110.158,54.433,87.46,31.403,59.101c-8.375,14.272-12.564,29.787-12.564,46.536c0,15.798,3.711,30.456,11.138,43.97 c7.422,13.512,17.417,24.455,29.98,32.831c-14.849-0.572-28.743-4.475-41.684-11.708v1.142c0,22.271,6.995,41.824,20.983,58.674 c13.99,16.848,31.645,27.453,52.961,31.833c-7.995,2.091-16.086,3.138-24.269,3.138c-5.33,0-11.136-0.475-17.416-1.42 c5.9,18.459,16.75,33.633,32.546,45.535c15.799,11.896,33.691,18.028,53.677,18.418c-33.498,26.262-71.66,39.393-114.486,39.393 c-8.186,0-15.607-0.373-22.27-1.139c42.827,27.596,90.03,41.394,141.612,41.394c32.738,0,63.478-5.181,92.21-15.557 c28.746-10.369,53.297-24.267,73.665-41.686c20.362-17.415,37.925-37.448,52.674-60.097c14.75-22.651,25.738-46.298,32.977-70.946 c7.23-24.653,10.848-49.344,10.848-74.092c0-5.33-0.096-9.325-0.287-11.991C421.785,120.202,437.202,104.306,449.956,85.657z"/> </g> </svg> </span> </a> </li> <li> <a href="https://github.com/caktus" target="_blank"> <span class="fa grey"><svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 438.549 438.549" style="enable-background:new 0 0 438.549 438.549;" xml:space="preserve"> <title>Github</title> <g> <path d="M409.132,114.573c-19.608-33.596-46.205-60.194-79.798-79.8C295.736,15.166,259.057,5.365,219.271,5.365 c-39.781,0-76.472,9.804-110.063,29.408c-33.596,19.605-60.192,46.204-79.8,79.8C9.803,148.168,0,184.854,0,224.63 c0,47.78,13.94,90.745,41.827,128.906c27.884,38.164,63.906,64.572,108.063,79.227c5.14,0.954,8.945,0.283,11.419-1.996 c2.475-2.282,3.711-5.14,3.711-8.562c0-0.571-0.049-5.708-0.144-15.417c-0.098-9.709-0.144-18.179-0.144-25.406l-6.567,1.136 c-4.187,0.767-9.469,1.092-15.846,1c-6.374-0.089-12.991-0.757-19.842-1.999c-6.854-1.231-13.229-4.086-19.13-8.559 c-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559 c-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-0.951-2.568-2.098-3.711-3.429c-1.142-1.331-1.997-2.663-2.568-3.997 c-0.572-1.335-0.098-2.43,1.427-3.289c1.525-0.859,4.281-1.276,8.28-1.276l5.708,0.853c3.807,0.763,8.516,3.042,14.133,6.851 c5.614,3.806,10.229,8.754,13.846,14.842c4.38,7.806,9.657,13.754,15.846,17.847c6.184,4.093,12.419,6.136,18.699,6.136 c6.28,0,11.704-0.476,16.274-1.423c4.565-0.952,8.848-2.383,12.847-4.285c1.713-12.758,6.377-22.559,13.988-29.41 c-10.848-1.14-20.601-2.857-29.264-5.14c-8.658-2.286-17.605-5.996-26.835-11.14c-9.235-5.137-16.896-11.516-22.985-19.126 c-6.09-7.614-11.088-17.61-14.987-29.979c-3.901-12.374-5.852-26.648-5.852-42.826c0-23.035,7.52-42.637,22.557-58.817 c-7.044-17.318-6.379-36.732,1.997-58.24c5.52-1.715,13.706-0.428,24.554,3.853c10.85,4.283,18.794,7.952,23.84,10.994 c5.046,3.041,9.089,5.618,12.135,7.708c17.705-4.947,35.976-7.421,54.818-7.421s37.117,2.474,54.823,7.421l10.849-6.849 c7.419-4.57,16.18-8.758,26.262-12.565c10.088-3.805,17.802-4.853,23.134-3.138c8.562,21.509,9.325,40.922,2.279,58.24 c15.036,16.18,22.559,35.787,22.559,58.817c0,16.178-1.958,30.497-5.853,42.966c-3.9,12.471-8.941,22.457-15.125,29.979 c-6.191,7.521-13.901,13.85-23.131,18.986c-9.232,5.14-18.182,8.85-26.84,11.136c-8.662,2.286-18.415,4.004-29.263,5.146 c9.894,8.562,14.842,22.077,14.842,40.539v60.237c0,3.422,1.19,6.279,3.572,8.562c2.379,2.279,6.136,2.95,11.276,1.995 c44.163-14.653,80.185-41.062,108.068-79.226c27.88-38.161,41.825-81.126,41.825-128.906 C438.536,184.851,428.728,148.168,409.132,114.573z"/> </g> </svg> </span> </a> </li> <li> <a href="https://www.facebook.com/CaktusGroup" target="_blank"> <span class="fa grey"><svg version="1.1" id="svg_facebook" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 470.513 470.513" style="enable-background:new 0 0 470.513 470.513;" xml:space="preserve"> <title>Facebook</title> <g> <path d="M271.521,154.17v-40.541c0-6.086,0.28-10.8,0.849-14.13c0.567-3.335,1.857-6.615,3.859-9.853 c1.999-3.236,5.236-5.47,9.706-6.708c4.476-1.24,10.424-1.858,17.85-1.858h40.539V0h-64.809c-37.5,0-64.433,8.897-80.803,26.691 c-16.368,17.798-24.551,44.014-24.551,78.658v48.82h-48.542v81.086h48.539v235.256h97.362V235.256h64.805l8.566-81.086H271.521z"/> </g> </svg> </span> </a> </li> <li> <a href="https://www.linkedin.com/company/caktus-consulting-group-llc" target="_blank"> <span class="fa grey"><svg version="1.1" id="svg_linkedin" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 438.536 438.535" style="enable-background:new 0 0 438.536 438.535;" xml:space="preserve"> <title>LinkedIn</title> <g> <g> <rect x="5.424" y="145.895" width="94.216" height="282.932"/> <path d="M408.842,171.739c-19.791-21.604-45.967-32.408-78.512-32.408c-11.991,0-22.891,1.475-32.695,4.427 c-9.801,2.95-18.079,7.089-24.838,12.419c-6.755,5.33-12.135,10.278-16.129,14.844c-3.798,4.337-7.512,9.389-11.136,15.104 v-40.232h-93.935l0.288,13.706c0.193,9.139,0.288,37.307,0.288,84.508c0,47.205-0.19,108.777-0.572,184.722h93.931V270.942 c0-9.705,1.041-17.412,3.139-23.127c4-9.712,10.037-17.843,18.131-24.407c8.093-6.572,18.13-9.855,30.125-9.855 c16.364,0,28.407,5.662,36.117,16.987c7.707,11.324,11.561,26.98,11.561,46.966V428.82h93.931V266.664 C438.529,224.976,428.639,193.336,408.842,171.739z"/> <path d="M53.103,9.708c-15.796,0-28.595,4.619-38.4,13.848C4.899,32.787,0,44.441,0,58.529c0,13.891,4.758,25.505,14.275,34.829 c9.514,9.325,22.078,13.99,37.685,13.99h0.571c15.99,0,28.887-4.661,38.688-13.99c9.801-9.324,14.606-20.934,14.417-34.829 c-0.19-14.087-5.047-25.742-14.561-34.973C81.562,14.323,68.9,9.708,53.103,9.708z"/> </g> </g> </svg> </span> </a> </li> <li> <a href="https://www.youtube.com/channel/UCfA4g648eBpPyhNy-eHP0-A" target="_blank"> <span class="fa grey"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 176 124" width="20px" height="20px" > <g id="Layer_2" data-name="Layer 2"> <g id="Layer_1-2" data-name="Layer 1"> <path class="cls-1" d="M172.32,19.36A22.12,22.12,0,0,0,156.76,3.7C143,0,88,0,88,0S33,0,19.24,3.7A22.12,22.12,0,0,0,3.68,19.36C0,33.18,0,62,0,62s0,28.82,3.68,42.64A22.12,22.12,0,0,0,19.24,120.3C33,124,88,124,88,124s55,0,68.76-3.7a22.12,22.12,0,0,0,15.56-15.66C176,90.82,176,62,176,62S176,33.18,172.32,19.36Z"/> <polygon class="cls-2" points="70 88.17 116 62 70 35.83 70 88.17"/> </g> </g> </svg></span> </a> </li> </ul> </div> <div class="twitter-feed"> <a class="twitter-timeline" href="https://twitter.com/caktusgroup" data-widget-id=616657501325541376 data-tweet-limit="1" data-chrome="noheader noborders transparent nofooter"></a> <script>!function(d,s,id) {var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https'; if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js"; fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs"); </script> </div> </div> </div><!--End second half--> </div><!--End wrapper--> </footer> <div class="modal fade" id="newsletter-modal" role="dialog" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"> <span aria-hidden="true">&times;</span> <span class="sr-only">Close</span> </button> <h4>Success!</h4> </div> <div class="modal-body"></div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal"> Close </button> </div> </div> </div> </div> <script src="https://storage.caktusgroup.com/static/js/bundle.80f81ff0dc33.js"></script> <script type="text/javascript"> var _iub = _iub || []; _iub.csConfiguration = { cookiePolicyId: 91739120, siteId: 1020454, lang: "en" } ; </script> <script type="text/javascript" src="//cdn.iubenda.com/cookie_solution/safemode/iubenda_cs.js" charset="UTF-8" async></script> <!-- Start of Async HubSpot Analytics Code --> <script type="text/javascript"> (function(d,s,i,r) { if (d.getElementById(i)){return;} var n=d.createElement(s),e=d.getElementsByTagName(s)[0]; n.id=i;n.src='//js.hs-analytics.net/analytics/'+(Math.ceil(new Date()/r)*r)+'/1883108.js'; e.parentNode.insertBefore(n, e); })(document,"script","hs-analytics",300000); </script> <!-- End of Async HubSpot Analytics Code --> <!-- Start of Adroll Code --> <script type="text/javascript"> adroll_adv_id = "4SXKCPWKKVFL7CGO4ABFGD"; adroll_pix_id = "S3NXO4MKCNCNTJCTD7FQ3F"; (function () { var _onload = function(){ if (document.readyState && !/loaded|complete/.test(document.readyState)){setTimeout(_onload, 10);return} if (!window.__adroll_loaded){__adroll_loaded=true;setTimeout(_onload, 50);return} var scr = document.createElement("script"); var host = (("https:" == document.location.protocol) ? "https://s.adroll.com" : "http://a.adroll.com"); scr.setAttribute('async', 'true'); scr.type = "text/javascript"; scr.src = host + "/j/roundtrip.js"; ((document.getElementsByTagName('head') || [null])[0] || document.getElementsByTagName('script')[0].parentNode).appendChild(scr); }; if (window.addEventListener) {window.addEventListener('load', _onload, false);} else {window.attachEvent('onload', _onload)} }()); </script> <!-- End of Adroll Code --> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10