Tomislav's Blog https://tmarice.dev/ Recent content on Tomislav's Blog Hugo -- 0.147.0 en-us Wed, 11 Feb 2026 00:00:00 +0000 Nix ❤️ Python: Maybe You Don't Need Devcontainers After All https://tmarice.dev/talks/nix-loves-python/ Wed, 11 Feb 2026 00:00:00 +0000 https://tmarice.dev/talks/nix-loves-python/ <p>This talk was held at <a href="https://www.meetup.com/python-hrvatska/events/313159184">Python meetup Zagreb on February 10, 2026</a>.</p> <script type="text/javascript" src= '/js/pdf-js/build/pdf.js'></script> <style> #embed-pdf-container { position: relative; width: 100%; height: auto; min-height: 20vh; } .pdf-canvas { border: 1px solid black; direction: ltr; width: 100%; height: auto; display: none; } #the-canvas { border: 1px solid black; direction: ltr; width: 100%; height: auto; display: none; } .pdf-loadingWrapper { display: none; justify-content: center; align-items: center; width: 100%; height: 350px; } .pdf-loading { display: inline-block; width: 50px; height: 50px; border: 3px solid #d2d0d0;; border-radius: 50%; border-top-color: #383838; animation: spin 1s ease-in-out infinite; -webkit-animation: spin 1s ease-in-out infinite; } #overlayText { word-wrap: break-word; display: grid; justify-content: end; } #overlayText a { position: relative; top: 10px; right: 4px; color: #000; margin: auto; background-color: #eeeeee; padding: 0.3em 1em; border: solid 2px; border-radius: 12px; border-color: #00000030; text-decoration: none; } #overlayText svg { height: clamp(1em, 2vw, 1.4em); width: clamp(1em, 2vw, 1.4em); } @keyframes spin { to { -webkit-transform: rotate(360deg); } } @-webkit-keyframes spin { to { -webkit-transform: rotate(360deg); } } </style><div class="embed-pdf-container" id="embed-pdf-container-e367faf6"> <div class="pdf-loadingWrapper" id="pdf-loadingWrapper-e367faf6"> <div class="pdf-loading" id="pdf-loading-e367faf6"></div> </div> <div id="overlayText"> <a href="Nix-loves-Python.pdf" aria-label="Download" download> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"> <path d="M9 13c.3 0 .5-.1.7-.3L15.4 7 14 5.6l-4 4V1H8v8.6l-4-4L2.6 7l5.7 5.7c.2.2.4.3.7.3zm-7 2h14v2H2z" /> </svg> </a> </div> <canvas class="pdf-canvas" id="pdf-canvas-e367faf6"></canvas> </div> <div class="pdf-paginator" id="pdf-paginator-e367faf6"> <button id="pdf-prev-e367faf6">Previous</button> <button id="pdf-next-e367faf6">Next</button> &nbsp; &nbsp; <span> <span class="pdf-pagenum" id="pdf-pagenum-e367faf6"></span> / <span class="pdf-pagecount" id="pdf-pagecount-e367faf6"></span> </span> <a class="pdf-source" id="pdf-source-e367faf6" href="Nix-loves-Python.pdf">[pdf]</a> </div> <noscript> View the PDF file <a class="pdf-source" id="pdf-source-noscript-e367faf6" href="Nix-loves-Python.pdf">here</a>. </noscript> <script type="text/javascript"> (function(){ var url = 'Nix-loves-Python.pdf'; var hidePaginator = "" === "true"; var hideLoader = "" === "true"; var selectedPageNum = parseInt("") || 1; var pdfjsLib = window['pdfjs-dist/build/pdf']; if (pdfjsLib.GlobalWorkerOptions.workerSrc == '') pdfjsLib.GlobalWorkerOptions.workerSrc = "https:\/\/tmarice.dev\/" + 'js/pdf-js/build/pdf.worker.js'; var pdfDoc = null, pageNum = selectedPageNum, pageRendering = false, pageNumPending = null, scale = 3, canvas = document.getElementById('pdf-canvas-e367faf6'), ctx = canvas.getContext('2d'), paginator = document.getElementById("pdf-paginator-e367faf6"), loadingWrapper = document.getElementById('pdf-loadingWrapper-e367faf6'); showPaginator(); showLoader(); function renderPage(num) { pageRendering = true; pdfDoc.getPage(num).then(function(page) { var viewport = page.getViewport({scale: scale}); canvas.height = viewport.height; canvas.width = viewport.width; var renderContext = { canvasContext: ctx, viewport: viewport }; var renderTask = page.render(renderContext); renderTask.promise.then(function() { pageRendering = false; showContent(); if (pageNumPending !== null) { renderPage(pageNumPending); pageNumPending = null; } }); }); document.getElementById('pdf-pagenum-e367faf6').textContent = num; } function showContent() { loadingWrapper.style.display = 'none'; canvas.style.display = 'block'; } function showLoader() { if(hideLoader) return loadingWrapper.style.display = 'flex'; canvas.style.display = 'none'; } function showPaginator() { if(hidePaginator) return paginator.style.display = 'block'; } function queueRenderPage(num) { if (pageRendering) { pageNumPending = num; } else { renderPage(num); } } function onPrevPage() { if (pageNum <= 1) { return; } pageNum--; queueRenderPage(pageNum); } document.getElementById('pdf-prev-e367faf6').addEventListener('click', onPrevPage); function onNextPage() { if (pageNum >= pdfDoc.numPages) { return; } pageNum++; queueRenderPage(pageNum); } document.getElementById('pdf-next-e367faf6').addEventListener('click', onNextPage); pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) { pdfDoc = pdfDoc_; var numPages = pdfDoc.numPages; document.getElementById('pdf-pagecount-e367faf6').textContent = numPages; if(pageNum > numPages) { pageNum = numPages } renderPage(pageNum); }); })(); </script> The Illusion of Simplicity https://tmarice.dev/blog/the-illusion-of-simplicity/ Tue, 25 Nov 2025 00:00:00 +0000 https://tmarice.dev/blog/the-illusion-of-simplicity/ <p>I really like Django. I would pick Django over any other option for setting up a website regardless of expected complexity. In my opinion, if you fully embrace Django, it will allow you to focus on the product and not fight an uphill battle against the computer.</p> <p>I do a bit of freelancing on the side, and it saddens me that I rarely see Django projects in the wild. Wherever I join and I&rsquo;m lucky enough that it&rsquo;s a Python gig, it&rsquo;s usually Flask or FastAPI.</p> Vim Mandates >> AI Mandates https://tmarice.dev/blog/vim-mandates-gt-ai-mandates/ Tue, 16 Sep 2025 00:00:00 +0000 https://tmarice.dev/blog/vim-mandates-gt-ai-mandates/ <h2 id="intro">Intro</h2> <p><a href="https://x.com/tobi/status/1909251946235437514">The internet</a> <a href="https://www.linkedin.com/posts/duolingo_below-is-an-all-hands-email-from-our-activity-7322560534824865792-l9vh/">is full</a> <a href="https://x.com/michakaufman/status/1909610844008161380">of articles</a> <a href="https://www.bloomberg.com/news/videos/2024-12-12/klarna-ceo-on-us-banking-ambitions-video">on CEOs</a> declaring their companies &ldquo;AI-first&rdquo;, in the name of increasing efficiency. After all, why have 50 engineers if you can have 5 managing a swarm of AI agents?</p> <p>I am a heavy user of GenAI assistive tools and they truly do help me achieve results faster. But another thing that helps you achieve results faster is not having to fight an uphill battle against whichever text editor you&rsquo;re using. If you have to lift your hands from the keyboard, you&rsquo;re wasting time.</p> Configuring PostgreSQL server parameters on GitHub Actions https://tmarice.dev/blog/configuring-postgres-on-github-actions/ Wed, 27 Aug 2025 00:00:00 +0000 https://tmarice.dev/blog/configuring-postgres-on-github-actions/ <p>If your project is not fully containerized, but you still want to use PostgreSQL in your GitHub Actions workflow, you can use the <a href="https://docs.github.com/en/actions/tutorials/use-containerized-services"><code>services</code></a> feature of GitHub Actions to easily spin up a PostgreSQL container.</p> <p>However, the <code>services</code> functionality restricts what you can configure declaratively in the workflow file &ndash; namely you cannot configure the PostgreSQL server parameters that you would usually set up in the <code>postgresql.conf</code> file. Fortunately, most of these can be configured through <code>ALTER SYSTEM</code> commands.</p> Taskmaster: Solving Deployment Headaches Caused by Long-Running Celery Jobs https://tmarice.dev/talks/taskmaster/ Wed, 11 Jun 2025 00:00:00 +0000 https://tmarice.dev/talks/taskmaster/ <p>This talk was held at <a href="https://www.meetup.com/python-hrvatska/events/303511934/">Python meetup Zagreb on June 11, 2025</a>.</p> <script type="text/javascript" src= '/js/pdf-js/build/pdf.js'></script> <style> #embed-pdf-container { position: relative; width: 100%; height: auto; min-height: 20vh; } .pdf-canvas { border: 1px solid black; direction: ltr; width: 100%; height: auto; display: none; } #the-canvas { border: 1px solid black; direction: ltr; width: 100%; height: auto; display: none; } .pdf-loadingWrapper { display: none; justify-content: center; align-items: center; width: 100%; height: 350px; } .pdf-loading { display: inline-block; width: 50px; height: 50px; border: 3px solid #d2d0d0;; border-radius: 50%; border-top-color: #383838; animation: spin 1s ease-in-out infinite; -webkit-animation: spin 1s ease-in-out infinite; } #overlayText { word-wrap: break-word; display: grid; justify-content: end; } #overlayText a { position: relative; top: 10px; right: 4px; color: #000; margin: auto; background-color: #eeeeee; padding: 0.3em 1em; border: solid 2px; border-radius: 12px; border-color: #00000030; text-decoration: none; } #overlayText svg { height: clamp(1em, 2vw, 1.4em); width: clamp(1em, 2vw, 1.4em); } @keyframes spin { to { -webkit-transform: rotate(360deg); } } @-webkit-keyframes spin { to { -webkit-transform: rotate(360deg); } } </style><div class="embed-pdf-container" id="embed-pdf-container-bd7d3630"> <div class="pdf-loadingWrapper" id="pdf-loadingWrapper-bd7d3630"> <div class="pdf-loading" id="pdf-loading-bd7d3630"></div> </div> <div id="overlayText"> <a href="Taskmaster.pdf" aria-label="Download" download> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"> <path d="M9 13c.3 0 .5-.1.7-.3L15.4 7 14 5.6l-4 4V1H8v8.6l-4-4L2.6 7l5.7 5.7c.2.2.4.3.7.3zm-7 2h14v2H2z" /> </svg> </a> </div> <canvas class="pdf-canvas" id="pdf-canvas-bd7d3630"></canvas> </div> <div class="pdf-paginator" id="pdf-paginator-bd7d3630"> <button id="pdf-prev-bd7d3630">Previous</button> <button id="pdf-next-bd7d3630">Next</button> &nbsp; &nbsp; <span> <span class="pdf-pagenum" id="pdf-pagenum-bd7d3630"></span> / <span class="pdf-pagecount" id="pdf-pagecount-bd7d3630"></span> </span> <a class="pdf-source" id="pdf-source-bd7d3630" href="Taskmaster.pdf">[pdf]</a> </div> <noscript> View the PDF file <a class="pdf-source" id="pdf-source-noscript-bd7d3630" href="Taskmaster.pdf">here</a>. </noscript> <script type="text/javascript"> (function(){ var url = 'Taskmaster.pdf'; var hidePaginator = "" === "true"; var hideLoader = "" === "true"; var selectedPageNum = parseInt("") || 1; var pdfjsLib = window['pdfjs-dist/build/pdf']; if (pdfjsLib.GlobalWorkerOptions.workerSrc == '') pdfjsLib.GlobalWorkerOptions.workerSrc = "https:\/\/tmarice.dev\/" + 'js/pdf-js/build/pdf.worker.js'; var pdfDoc = null, pageNum = selectedPageNum, pageRendering = false, pageNumPending = null, scale = 3, canvas = document.getElementById('pdf-canvas-bd7d3630'), ctx = canvas.getContext('2d'), paginator = document.getElementById("pdf-paginator-bd7d3630"), loadingWrapper = document.getElementById('pdf-loadingWrapper-bd7d3630'); showPaginator(); showLoader(); function renderPage(num) { pageRendering = true; pdfDoc.getPage(num).then(function(page) { var viewport = page.getViewport({scale: scale}); canvas.height = viewport.height; canvas.width = viewport.width; var renderContext = { canvasContext: ctx, viewport: viewport }; var renderTask = page.render(renderContext); renderTask.promise.then(function() { pageRendering = false; showContent(); if (pageNumPending !== null) { renderPage(pageNumPending); pageNumPending = null; } }); }); document.getElementById('pdf-pagenum-bd7d3630').textContent = num; } function showContent() { loadingWrapper.style.display = 'none'; canvas.style.display = 'block'; } function showLoader() { if(hideLoader) return loadingWrapper.style.display = 'flex'; canvas.style.display = 'none'; } function showPaginator() { if(hidePaginator) return paginator.style.display = 'block'; } function queueRenderPage(num) { if (pageRendering) { pageNumPending = num; } else { renderPage(num); } } function onPrevPage() { if (pageNum <= 1) { return; } pageNum--; queueRenderPage(pageNum); } document.getElementById('pdf-prev-bd7d3630').addEventListener('click', onPrevPage); function onNextPage() { if (pageNum >= pdfDoc.numPages) { return; } pageNum++; queueRenderPage(pageNum); } document.getElementById('pdf-next-bd7d3630').addEventListener('click', onNextPage); pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) { pdfDoc = pdfDoc_; var numPages = pdfDoc.numPages; document.getElementById('pdf-pagecount-bd7d3630').textContent = numPages; if(pageNum > numPages) { pageNum = numPages } renderPage(pageNum); }); })(); </script> On Usable Documentation https://tmarice.dev/blog/on-usable-documentation/ Sat, 29 Mar 2025 00:00:00 +0000 https://tmarice.dev/blog/on-usable-documentation/ <p>Having no documentation is often less harmful than having inaccurate documentation.</p> <p>Like code, documentation degrades over time. What was once accurate may now be obsolete. And practices we once ignored might now be part of our daily workflow. Unless maintaining documentation is an intentional process, it will rot, maybe beyond saving.</p> <p>In this article I&rsquo;ll outline a few guidelines that worked well for me in the past for keeping the developer documentation usable. They&rsquo;re certainly not universal, but they are a good starting point for a small team of experienced developers. We’ll skip the philosophical debates and focus on real-world practices that work for small, fast-moving engineering teams - the aim is to get the most value with the least effort. Documentation doesn&rsquo;t pay the bills.</p> Handling Csrf Login Errors Gracefully in Django https://tmarice.dev/blog/handling-csrf-login-errors-gracefully-in-django/ Sat, 22 Mar 2025 00:00:00 +0000 https://tmarice.dev/blog/handling-csrf-login-errors-gracefully-in-django/ <h1 id="whats-csrf">What&rsquo;s CSRF?</h1> <p>Cross site request forgery is a type of attack where a malicious website tricks a user into performing actions on another site where they&rsquo;re authenticated. This is usually done by embedding a form in the malicious site, and submitting it to the target site.</p> <p>An example of this would be a card game website where, when you hit the &ldquo;Play&rdquo; button, it sends a POST request to another site with the payload to change your login email address to the attacker&rsquo;s. Since you&rsquo;re logged in to the target site, the request goes through and you lose access to your account.</p> Better Living Through Optimized Django https://tmarice.dev/blog/better-living-through-optimized-django/ Sat, 31 Aug 2024 00:00:00 +0000 https://tmarice.dev/blog/better-living-through-optimized-django/ <p>Every engineer that loves Django and has a blog has at least one of these posts.</p> <p>Django&rsquo;s ORM is excellent, but given enough time it&rsquo;s easy for approaches that weren&rsquo;t mistakes to grow into mistakes This is a great thing, because it usually means your company didn&rsquo;t go bankrupt, you&rsquo;re still here and can fix things, and the company is doing well because the scale increased (hopefully your compensation as well).</p> On Python's @property Decorator https://tmarice.dev/blog/on-pythons-property-decorator/ Sun, 18 Feb 2024 00:00:00 +0000 https://tmarice.dev/blog/on-pythons-property-decorator/ <p><code>@property</code> decorator is an excellent way to reduce the readability of Python code. It obfuscates a perfectly good function call and tricks readers into thinking they&rsquo;re performing a regular attribute access or assignment.</p> <p>Unless there&rsquo;s a really good and explicit reason to do this, don&rsquo;t.</p> <h2 id="list-of-good-and-explicit-reasons">List of Good and Explicit Reasons:</h2> <ol> <li>Refactoring</li> </ol> <p>That&rsquo;s pretty much it.</p> <p>If you need to turn something that (rightfully so) started out as a simple attribute, but with time accrued some more complex logic, @property is a good way to gracefully transition from attributes to function calls.</p> Why I Always Assign Intermediate Values to Local Variables Instead of Passing Them Directly to Function Calls https://tmarice.dev/blog/why-i-always-assign-intermediate-values-to-local-variables-instead-of-passing-them-directly-to-function-calls/ Wed, 29 Nov 2023 00:00:00 +0000 https://tmarice.dev/blog/why-i-always-assign-intermediate-values-to-local-variables-instead-of-passing-them-directly-to-function-calls/ <p>Instead of</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">do_something</span>(a, b, c): </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> res_fn( </span></span><span style="display:flex;"><span> fn(a, b), </span></span><span style="display:flex;"><span> fn(b), </span></span><span style="display:flex;"><span> c </span></span><span style="display:flex;"><span> ) </span></span></code></pre></div><p>I do:</p> <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">do_something</span>(a, b, c): </span></span><span style="display:flex;"><span> inter_1 <span style="color:#f92672">=</span> fn(a, b) </span></span><span style="display:flex;"><span> inter_2 <span style="color:#f92672">=</span> fn(b) </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span> result <span style="color:#f92672">=</span> res_fn(inter_1, inter_2, c) </span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> result </span></span></code></pre></div><p>The first version is much shorter, and when formatted properly, equally readable.</p> <p>But the reason I prefer the second approach is because all intermediate steps are saved to local variables.</p> <p>Exception tracking tools like Sentry, and even Django&rsquo;s error page that pops up when <code>DEBUG=True</code> is set, capture the local context. On top of that, if you ever have to step through the function with a debugger, you can see the exact return value before stepping out from the function. This is the reason why I even save the final result in a local variable, just before returning it.</p> Using Jupyter Outside of Data Science https://tmarice.dev/talks/using-jupyter-outside-of-data-science/ Tue, 10 Oct 2023 00:00:00 +0000 https://tmarice.dev/talks/using-jupyter-outside-of-data-science/ <p>This talk was held at <a href="https://www.meetup.com/python-hrvatska/events/296540003">Python meetup Zagreb on October 10, 2023</a>.</p> <script type="text/javascript" src= '/js/pdf-js/build/pdf.js'></script> <style> #embed-pdf-container { position: relative; width: 100%; height: auto; min-height: 20vh; } .pdf-canvas { border: 1px solid black; direction: ltr; width: 100%; height: auto; display: none; } #the-canvas { border: 1px solid black; direction: ltr; width: 100%; height: auto; display: none; } .pdf-loadingWrapper { display: none; justify-content: center; align-items: center; width: 100%; height: 350px; } .pdf-loading { display: inline-block; width: 50px; height: 50px; border: 3px solid #d2d0d0;; border-radius: 50%; border-top-color: #383838; animation: spin 1s ease-in-out infinite; -webkit-animation: spin 1s ease-in-out infinite; } #overlayText { word-wrap: break-word; display: grid; justify-content: end; } #overlayText a { position: relative; top: 10px; right: 4px; color: #000; margin: auto; background-color: #eeeeee; padding: 0.3em 1em; border: solid 2px; border-radius: 12px; border-color: #00000030; text-decoration: none; } #overlayText svg { height: clamp(1em, 2vw, 1.4em); width: clamp(1em, 2vw, 1.4em); } @keyframes spin { to { -webkit-transform: rotate(360deg); } } @-webkit-keyframes spin { to { -webkit-transform: rotate(360deg); } } </style><div class="embed-pdf-container" id="embed-pdf-container-6b449905"> <div class="pdf-loadingWrapper" id="pdf-loadingWrapper-6b449905"> <div class="pdf-loading" id="pdf-loading-6b449905"></div> </div> <div id="overlayText"> <a href="Using_jupyter_outside_of_data_science.pdf" aria-label="Download" download> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"> <path d="M9 13c.3 0 .5-.1.7-.3L15.4 7 14 5.6l-4 4V1H8v8.6l-4-4L2.6 7l5.7 5.7c.2.2.4.3.7.3zm-7 2h14v2H2z" /> </svg> </a> </div> <canvas class="pdf-canvas" id="pdf-canvas-6b449905"></canvas> </div> <div class="pdf-paginator" id="pdf-paginator-6b449905"> <button id="pdf-prev-6b449905">Previous</button> <button id="pdf-next-6b449905">Next</button> &nbsp; &nbsp; <span> <span class="pdf-pagenum" id="pdf-pagenum-6b449905"></span> / <span class="pdf-pagecount" id="pdf-pagecount-6b449905"></span> </span> <a class="pdf-source" id="pdf-source-6b449905" href="Using_jupyter_outside_of_data_science.pdf">[pdf]</a> </div> <noscript> View the PDF file <a class="pdf-source" id="pdf-source-noscript-6b449905" href="Using_jupyter_outside_of_data_science.pdf">here</a>. </noscript> <script type="text/javascript"> (function(){ var url = 'Using_jupyter_outside_of_data_science.pdf'; var hidePaginator = "" === "true"; var hideLoader = "" === "true"; var selectedPageNum = parseInt("") || 1; var pdfjsLib = window['pdfjs-dist/build/pdf']; if (pdfjsLib.GlobalWorkerOptions.workerSrc == '') pdfjsLib.GlobalWorkerOptions.workerSrc = "https:\/\/tmarice.dev\/" + 'js/pdf-js/build/pdf.worker.js'; var pdfDoc = null, pageNum = selectedPageNum, pageRendering = false, pageNumPending = null, scale = 3, canvas = document.getElementById('pdf-canvas-6b449905'), ctx = canvas.getContext('2d'), paginator = document.getElementById("pdf-paginator-6b449905"), loadingWrapper = document.getElementById('pdf-loadingWrapper-6b449905'); showPaginator(); showLoader(); function renderPage(num) { pageRendering = true; pdfDoc.getPage(num).then(function(page) { var viewport = page.getViewport({scale: scale}); canvas.height = viewport.height; canvas.width = viewport.width; var renderContext = { canvasContext: ctx, viewport: viewport }; var renderTask = page.render(renderContext); renderTask.promise.then(function() { pageRendering = false; showContent(); if (pageNumPending !== null) { renderPage(pageNumPending); pageNumPending = null; } }); }); document.getElementById('pdf-pagenum-6b449905').textContent = num; } function showContent() { loadingWrapper.style.display = 'none'; canvas.style.display = 'block'; } function showLoader() { if(hideLoader) return loadingWrapper.style.display = 'flex'; canvas.style.display = 'none'; } function showPaginator() { if(hidePaginator) return paginator.style.display = 'block'; } function queueRenderPage(num) { if (pageRendering) { pageNumPending = num; } else { renderPage(num); } } function onPrevPage() { if (pageNum <= 1) { return; } pageNum--; queueRenderPage(pageNum); } document.getElementById('pdf-prev-6b449905').addEventListener('click', onPrevPage); function onNextPage() { if (pageNum >= pdfDoc.numPages) { return; } pageNum++; queueRenderPage(pageNum); } document.getElementById('pdf-next-6b449905').addEventListener('click', onNextPage); pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) { pdfDoc = pdfDoc_; var numPages = pdfDoc.numPages; document.getElementById('pdf-pagecount-6b449905').textContent = numPages; if(pageNum > numPages) { pageNum = numPages } renderPage(pageNum); }); })(); </script> Hacking Analytics With Postgres https://tmarice.dev/talks/hacking-analytics-with-postgres/ Tue, 14 Jun 2022 00:00:00 +0000 https://tmarice.dev/talks/hacking-analytics-with-postgres/ <p>This talk was held at <a href="https://www.meetup.com/python-hrvatska/events/286414733">Python meetup Zagreb on June 14, 2022</a>.</p> <script type="text/javascript" src= '/js/pdf-js/build/pdf.js'></script> <style> #embed-pdf-container { position: relative; width: 100%; height: auto; min-height: 20vh; } .pdf-canvas { border: 1px solid black; direction: ltr; width: 100%; height: auto; display: none; } #the-canvas { border: 1px solid black; direction: ltr; width: 100%; height: auto; display: none; } .pdf-loadingWrapper { display: none; justify-content: center; align-items: center; width: 100%; height: 350px; } .pdf-loading { display: inline-block; width: 50px; height: 50px; border: 3px solid #d2d0d0;; border-radius: 50%; border-top-color: #383838; animation: spin 1s ease-in-out infinite; -webkit-animation: spin 1s ease-in-out infinite; } #overlayText { word-wrap: break-word; display: grid; justify-content: end; } #overlayText a { position: relative; top: 10px; right: 4px; color: #000; margin: auto; background-color: #eeeeee; padding: 0.3em 1em; border: solid 2px; border-radius: 12px; border-color: #00000030; text-decoration: none; } #overlayText svg { height: clamp(1em, 2vw, 1.4em); width: clamp(1em, 2vw, 1.4em); } @keyframes spin { to { -webkit-transform: rotate(360deg); } } @-webkit-keyframes spin { to { -webkit-transform: rotate(360deg); } } </style><div class="embed-pdf-container" id="embed-pdf-container-782bae57"> <div class="pdf-loadingWrapper" id="pdf-loadingWrapper-782bae57"> <div class="pdf-loading" id="pdf-loading-782bae57"></div> </div> <div id="overlayText"> <a href="Hacking_analytics_with_Postgres.pdf" aria-label="Download" download> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"> <path d="M9 13c.3 0 .5-.1.7-.3L15.4 7 14 5.6l-4 4V1H8v8.6l-4-4L2.6 7l5.7 5.7c.2.2.4.3.7.3zm-7 2h14v2H2z" /> </svg> </a> </div> <canvas class="pdf-canvas" id="pdf-canvas-782bae57"></canvas> </div> <div class="pdf-paginator" id="pdf-paginator-782bae57"> <button id="pdf-prev-782bae57">Previous</button> <button id="pdf-next-782bae57">Next</button> &nbsp; &nbsp; <span> <span class="pdf-pagenum" id="pdf-pagenum-782bae57"></span> / <span class="pdf-pagecount" id="pdf-pagecount-782bae57"></span> </span> <a class="pdf-source" id="pdf-source-782bae57" href="Hacking_analytics_with_Postgres.pdf">[pdf]</a> </div> <noscript> View the PDF file <a class="pdf-source" id="pdf-source-noscript-782bae57" href="Hacking_analytics_with_Postgres.pdf">here</a>. </noscript> <script type="text/javascript"> (function(){ var url = 'Hacking_analytics_with_Postgres.pdf'; var hidePaginator = "" === "true"; var hideLoader = "" === "true"; var selectedPageNum = parseInt("") || 1; var pdfjsLib = window['pdfjs-dist/build/pdf']; if (pdfjsLib.GlobalWorkerOptions.workerSrc == '') pdfjsLib.GlobalWorkerOptions.workerSrc = "https:\/\/tmarice.dev\/" + 'js/pdf-js/build/pdf.worker.js'; var pdfDoc = null, pageNum = selectedPageNum, pageRendering = false, pageNumPending = null, scale = 3, canvas = document.getElementById('pdf-canvas-782bae57'), ctx = canvas.getContext('2d'), paginator = document.getElementById("pdf-paginator-782bae57"), loadingWrapper = document.getElementById('pdf-loadingWrapper-782bae57'); showPaginator(); showLoader(); function renderPage(num) { pageRendering = true; pdfDoc.getPage(num).then(function(page) { var viewport = page.getViewport({scale: scale}); canvas.height = viewport.height; canvas.width = viewport.width; var renderContext = { canvasContext: ctx, viewport: viewport }; var renderTask = page.render(renderContext); renderTask.promise.then(function() { pageRendering = false; showContent(); if (pageNumPending !== null) { renderPage(pageNumPending); pageNumPending = null; } }); }); document.getElementById('pdf-pagenum-782bae57').textContent = num; } function showContent() { loadingWrapper.style.display = 'none'; canvas.style.display = 'block'; } function showLoader() { if(hideLoader) return loadingWrapper.style.display = 'flex'; canvas.style.display = 'none'; } function showPaginator() { if(hidePaginator) return paginator.style.display = 'block'; } function queueRenderPage(num) { if (pageRendering) { pageNumPending = num; } else { renderPage(num); } } function onPrevPage() { if (pageNum <= 1) { return; } pageNum--; queueRenderPage(pageNum); } document.getElementById('pdf-prev-782bae57').addEventListener('click', onPrevPage); function onNextPage() { if (pageNum >= pdfDoc.numPages) { return; } pageNum++; queueRenderPage(pageNum); } document.getElementById('pdf-next-782bae57').addEventListener('click', onNextPage); pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) { pdfDoc = pdfDoc_; var numPages = pdfDoc.numPages; document.getElementById('pdf-pagecount-782bae57').textContent = numPages; if(pageNum > numPages) { pageNum = numPages } renderPage(pageNum); }); })(); </script> Projects https://tmarice.dev/projects/ Mon, 01 Jan 0001 00:00:00 +0000 https://tmarice.dev/projects/ <p>Here are some of the projects I&rsquo;ve worked on:</p> <hr> <h1 id="devenvsh-raycast-extension"><a href="https://www.raycast.com/tmarice/devenv-docs">Devenv.sh Raycast Extension</a></h1> <p>Browse devenv.sh docs from the comfort of your Raycast command line.</p> <hr> <h1 id="fast-masked-mail-creator"><a href="https://github.com/tmarice/fast_masked_mail_creator">Fast Masked Mail Creator</a></h1> <p>An unofficial Chrome extension for the easy creation of new, single-purpose Fastmail masked emails.</p> <hr> <h1 id="django-timed-tests"><a href="https://github.com/tmarice/django-timed-tests">django-timed-tests</a></h1> <p>Django test runner that pinpoints the slowest tests with precise timing reports, enabling faster, more efficient test suites.</p> <hr>