<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chiemezuo</title>
    <description>The latest articles on DEV Community by Chiemezuo (@chiemezuo).</description>
    <link>https://dev.to/chiemezuo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F904964%2F2a15daea-b357-47ed-85e1-cf00466deaf8.jpg</url>
      <title>DEV Community: Chiemezuo</title>
      <link>https://dev.to/chiemezuo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chiemezuo"/>
    <language>en</language>
    <item>
      <title>My Google Summer of Code (GSoC) 2025 in View</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Fri, 29 Aug 2025 19:24:48 +0000</pubDate>
      <link>https://dev.to/chiemezuo/my-google-summer-of-code-gsoc-2025-in-view-23j1</link>
      <guid>https://dev.to/chiemezuo/my-google-summer-of-code-gsoc-2025-in-view-23j1</guid>
      <description>&lt;p&gt;On May 8, 2025, I received an email stating that my proposal, titled "&lt;a href="https://summerofcode.withgoogle.com/proposals/details/IToD6EgQ" rel="noopener noreferrer"&gt;Content Security Policy Compatibility&lt;/a&gt;," for the &lt;a href="https://wagtail.org/" rel="noopener noreferrer"&gt;Wagtail CMS&lt;/a&gt; had been accepted. It was an exciting moment, and 3 months later, I'm here to share how things went, along with links to all of the work that was done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw73vg1dmaf31tcfnq3vu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw73vg1dmaf31tcfnq3vu.png" alt="Chiemezuo's project acceptance from Google Summer of Code 2025 containing the project name: " width="800" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I'll start with some context to understand the GSoC project topic, the state of things &lt;strong&gt;before&lt;/strong&gt; the project, the state of things &lt;strong&gt;after&lt;/strong&gt; changes from the project are merged, as well as a scope on what can be done in the future.&lt;/p&gt;

&lt;p&gt;First of all, &lt;strong&gt;what&lt;/strong&gt; is &lt;em&gt;CSP&lt;/em&gt;? To quote the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP" rel="noopener noreferrer"&gt;Mozilla documentation&lt;/a&gt;: "CSP is a feature that helps to prevent or minimize the risk of certain types of security threats. If that's confusing, here's a reference to an explanation of CSP from an &lt;a href="https://dev.to/chiemezuo/configuring-csp-a-test-for-django-60-3a67"&gt;article I wrote&lt;/a&gt; during my project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Starting Point
&lt;/h2&gt;

&lt;p&gt;The foundation of this project was laid in 2015 via a &lt;a href="https://github.com/wagtail/wagtail/issues/1288" rel="noopener noreferrer"&gt;GitHub issue&lt;/a&gt;. Some fixes and discussions went on from that issue, and a few years later, another issue was created as a more up-to-date &lt;a href="https://github.com/wagtail/wagtail/issues/7053" rel="noopener noreferrer"&gt;listing of Wagtail components that triggered strict CSP violations&lt;/a&gt;. Now, in 2025, an even more recent &lt;a href="https://github.com/wagtail/roadmap/issues/96" rel="noopener noreferrer"&gt;CSP audit&lt;/a&gt; was conducted.&lt;/p&gt;

&lt;p&gt;All of this means that at the time of commencement of my project, the Wagtail CMS had a handful of issues holding it back from supporting strict CSP, which were summarized in this &lt;a href="https://github.com/wagtail/wagtail/issues/7053#issuecomment-2697595142" rel="noopener noreferrer"&gt;comment&lt;/a&gt;. This made planning very straightforward, so my mentor &lt;a href="https://github.com/laymonage" rel="noopener noreferrer"&gt;Sage&lt;/a&gt; and I drafted a GitHub project board that became the &lt;a href="https://github.com/orgs/wagtail/projects/71/views/6" rel="noopener noreferrer"&gt;reference guide for my GSoC 2025&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, making CSP improvements would be nothing without a place to test and document them. The &lt;a href="https://github.com/wagtail/bakerydemo" rel="noopener noreferrer"&gt;BakeryDemo&lt;/a&gt; and &lt;a href="https://github.com/wagtail/wagtail.org" rel="noopener noreferrer"&gt;Wagtail.org&lt;/a&gt; websites were ideal for having a template developers could walk through, and a production site with CSP running.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did
&lt;/h2&gt;

&lt;p&gt;The project was divided into sprints of focused tasks, with the easier fixes being addressed first, to make room for tasks that depended on other members of the community. There was a pull request for refactoring the HTML and CSS across the CMS to &lt;a href="https://github.com/wagtail/wagtail/pull/12943/files" rel="noopener noreferrer"&gt;remove the usage of inline style-src&lt;/a&gt;. My mentor and I reviewed this and quickly merged. This was the first merge of my project. Shortly after, I sent in another &lt;a href="https://github.com/wagtail/wagtail/pull/13147" rel="noopener noreferrer"&gt;Pull Request (PR)&lt;/a&gt; for inline &lt;code&gt;style-src&lt;/code&gt;, for issues resulting from &lt;strong&gt;JavaScript&lt;/strong&gt; code.&lt;/p&gt;

&lt;p&gt;After &lt;code&gt;style-src&lt;/code&gt;, the logical progression was to try to tackle &lt;code&gt;script-src&lt;/code&gt; issues in the codebase. However, the culprit component causing these problems was a part of a larger system that required a refactor: Inline Panels. The first part of the refactor was already in the works by &lt;a href="https://github.com/lb-" rel="noopener noreferrer"&gt;LB&lt;/a&gt; and some of the &lt;a href="https://wagtail.org/core-team/" rel="noopener noreferrer"&gt;Wagtail Core team members&lt;/a&gt;. While that was in review, I proceeded to fix an issue with a client-facing aspect: &lt;a href="https://github.com/wagtail/wagtail/pull/13173" rel="noopener noreferrer"&gt;background positioning&lt;/a&gt;. This depended on some feedback from the core team. &lt;/p&gt;

&lt;p&gt;The primary target of the next sprint was inline panel refactoring. There was a &lt;a href="https://github.com/wagtail/wagtail/pull/12945" rel="noopener noreferrer"&gt;PR already in place&lt;/a&gt; for this, but it was incomplete and needed a review of the intended implementation. The contributor allowed me to continue working on it, and I did that in a separate &lt;a href="https://github.com/wagtail/wagtail/pull/13168" rel="noopener noreferrer"&gt;branch/PR&lt;/a&gt;. Work on the new branch went on asynchronously and is still in the works at the time of writing this. The async nature of it allowed me to take on some other tasks in the sprint period, most notably of which was setting up a local version of the &lt;a href="https://github.com/springload/draftail/" rel="noopener noreferrer"&gt;Draftail editor&lt;/a&gt;, which is Wagtail's editing interface. The editor itself had some dynamic style generation, which wasn't CSP-friendly. I started work on it, but didn't have a clear sense of direction, and came up with a draft of what &lt;strong&gt;could&lt;/strong&gt; work. My mentor and I agreed on having the &lt;a href="https://github.com/springload/draftail/pull/461" rel="noopener noreferrer"&gt;draft PR&lt;/a&gt; as a reference, and adding it to the backlog to focus on things where there was already a clearer direction.&lt;/p&gt;

&lt;p&gt;The following sprint fell during the time of Wagtail's &lt;em&gt;code freeze&lt;/em&gt;, which is the span of time just before a new Wagtail release, where code changes do not get merged until &lt;strong&gt;after&lt;/strong&gt; the release. For that Sprint, I focused on reviewing existing and pending CSP-related issues and tasks. It was a period of research and exploration of some options for potential CSP issues. During this period, I also worked on a way to prevent new CSP issues from creeping in in the future. I had the idea of a "code checker", and my mentor mentioned writing a &lt;a href="https://github.com/semgrep/semgrep" rel="noopener noreferrer"&gt;Semgrep&lt;/a&gt; rule to perform the checks. I wrote the rules &lt;a href="https://github.com/wagtail/wagtail/pull/13285" rel="noopener noreferrer"&gt;in this PR&lt;/a&gt;, and made a note to plan towards moving the JavaScript/TypeScript checks to &lt;a href="https://github.com/wagtail/eslint-config-wagtail" rel="noopener noreferrer"&gt;Wagtail's ESLint repo&lt;/a&gt;. The last activity of this sprint was checking that SVGs, when uploaded, would not violate CSP directives when they contained inline styles. This investigation was done, and SVGs didn't get in the way.&lt;/p&gt;

&lt;p&gt;With these out of the way, the next steps (in no specific order) were &lt;a href="https://github.com/wagtail/wagtail/pull/13241" rel="noopener noreferrer"&gt;migrating the file title generation logic on image and document upload&lt;/a&gt; to use a &lt;a href="http://stimulus.hotwired.dev/" rel="noopener noreferrer"&gt;Stimulus&lt;/a&gt; approach, &lt;a href="https://dev.to/chiemezuo/configuring-csp-a-test-for-django-60-3a67"&gt;testing Wagtail with Django 6.0's in-built CSP package&lt;/a&gt;, establishing a CSP baseline with Django 6.0 on both the &lt;a href="https://github.com/wagtail/bakerydemo/pull/551" rel="noopener noreferrer"&gt;BakeryDemo&lt;/a&gt; and the &lt;a href="https://github.com/wagtail/wagtail.org/pull/550/files" rel="noopener noreferrer"&gt;Wagtail.org&lt;/a&gt;, documenting my findings, pushing my work to more public lights for scrutiny and feedback, investigating deeper into the codebase with a focus on dynamic scripts, and putting finishing touches to existing PRs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Current State
&lt;/h2&gt;

&lt;p&gt;At the time of writing this, some of the work I did has been merged, and some is still under review. There has been progress in the state of Wagtail's strict CSP compatibility, and there will soon be another CSP audit when the rest of the unmerged/unreviewed changes are finalized and merged. The GSoC project, along with the addition of CSP to the &lt;a href="https://github.com/orgs/wagtail/projects/16" rel="noopener noreferrer"&gt;Wagtail roadmap&lt;/a&gt;, has also garnered some interest in seeing the CSP goal achieved.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's left to do
&lt;/h2&gt;

&lt;p&gt;At the time of writing this, the pending parts from the GSoC project board are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fixing the Draftail editor's dynamic styles.&lt;/li&gt;
&lt;li&gt;Fixing some dynamically injected JavaScript code from within &lt;strong&gt;Python&lt;/strong&gt; in the Wagtail code.&lt;/li&gt;
&lt;li&gt;Giving a full report on my CSP test findings using the upcoming Django 6.0 in both the BakeryDemo and Wagtail.org projects.&lt;/li&gt;
&lt;li&gt;Finalizing/merging the pending PRs already linked in this blog post.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Thoughts
&lt;/h2&gt;

&lt;p&gt;It's been magical watching and being a part of how a 10-year-old GitHub issue might finally be closed. This is one of those moments I am truly grateful for, that only open-source software can give. The last 3 months have been full of nothing but excitement, learning, and collaboration.&lt;/p&gt;

&lt;p&gt;I'm grateful to my mentors Sage &amp;amp; &lt;a href="https://github.com/thibaudcolas" rel="noopener noreferrer"&gt;Thibaud&lt;/a&gt;, LB, and the Wagtail community as a whole for being such great teachers and collaborators. They made my GSoC experience amazing.&lt;/p&gt;

&lt;p&gt;Finally, big thanks to Google for giving me a life-changing experience.&lt;/p&gt;

&lt;p&gt;Till next time!&lt;/p&gt;

&lt;p&gt;Cheers.🥂&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>django</category>
      <category>wagtail</category>
      <category>programming</category>
    </item>
    <item>
      <title>Configuring CSP: A Test For Django 6.0</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Fri, 29 Aug 2025 15:03:42 +0000</pubDate>
      <link>https://dev.to/chiemezuo/configuring-csp-a-test-for-django-60-3a67</link>
      <guid>https://dev.to/chiemezuo/configuring-csp-a-test-for-django-60-3a67</guid>
      <description>&lt;p&gt;Right away, some things could potentially be confusing about the title of this blog post. What is CSP? How does it relate to Django? Why Django 6.0?&lt;br&gt;
You would be fair to ask these questions, especially because at the time of writing this, the current version of Django is 5.2.5. Let's get these questions answered.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is CSP?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsogzgy85jmyvhm8bmn6k.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsogzgy85jmyvhm8bmn6k.gif" alt="question mark GIF" width="500" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CSP is an acronym that stands for "&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP" rel="noopener noreferrer"&gt;Content Security Policy&lt;/a&gt;". Just as the name implies, it has a lot to do with "&lt;strong&gt;security&lt;/strong&gt;", and this is achieved by placing restrictions (or &lt;strong&gt;policies&lt;/strong&gt;) on the &lt;strong&gt;content&lt;/strong&gt; from websites. Over the years, browsers have evolved to be incredibly powerful, while making sure that content accessed on one website cannot run malicious instructions in the context of another website tab or another part of the computer altogether. However, this leaves a small window of opportunity for the said malicious instructions: "the context of the same website itself". That might sound vague, but if malicious code could somehow get served from whatever website you're on, then it can be executed in the context you're in. An example of this would be if there were a &lt;code&gt;console.log&lt;/code&gt; statement in the resulting HTML of a website, an inspection of the &lt;a href="https://developer.chrome.com/docs/devtools/console" rel="noopener noreferrer"&gt;dev tools console&lt;/a&gt; would reveal it. Here's where things get interesting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Something far more harmful than &lt;code&gt;console.log&lt;/code&gt; scripts could be done outside your immediate view.&lt;/li&gt;
&lt;li&gt;On some websites, especially content-heavy ones without data sanitization, these scripts could just about be added by &lt;strong&gt;anybody&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The scripts don't even have to appear to be &lt;strong&gt;on&lt;/strong&gt; your website (as in the case of "&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Clickjacking" rel="noopener noreferrer"&gt;clickjacking&lt;/a&gt;").&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  How does this relate to Django?
&lt;/h2&gt;

&lt;p&gt;Django is one of the &lt;a href="https://survey.stackoverflow.co/2025/technology/#1-web-frameworks-and-technologies" rel="noopener noreferrer"&gt;most used web frameworks&lt;/a&gt; on the planet, and can be a conduit for the types of attacks that CSP helps prevent: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Clickjacking" rel="noopener noreferrer"&gt;clickjacking&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting" rel="noopener noreferrer"&gt;cross-site scripting (XSS)&lt;/a&gt;. CSP also helps with making sure Django loads your site's pages in &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/HTTPS" rel="noopener noreferrer"&gt;HTTPS&lt;/a&gt;.&lt;br&gt;
In summary, CSP is tool-agnostic; Django developers would benefit from a good grasp of it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Django 6.0?
&lt;/h2&gt;

&lt;p&gt;There has been existing tooling to test and enforce CSP in Django. The most recognizable of those has been the &lt;a href="https://django-csp.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;django-csp&lt;/a&gt; package developed by a team at Mozilla. It is available on &lt;a href="https://pypi.org/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt; and does an excellent job.&lt;br&gt;
You might still be wondering how this answers the question: "Why Django 6.0?" In May 2024, a conversation began to explore the possibility of &lt;a href="https://forum.djangoproject.com/t/adding-csp-support-to-django/31526" rel="noopener noreferrer"&gt;adding CSP support to Django&lt;/a&gt;. The idea was to create something better, with no need for another package installation, and with good out-of-the-box support. That conversation morphed into a Pull Request that has since been merged and will be coming with the release of Django 6.0, scheduled for &lt;a href="https://docs.djangoproject.com/en/dev/releases/6.0/" rel="noopener noreferrer"&gt;December 2025&lt;/a&gt; at the time of writing.&lt;br&gt;
Thankfully, you can get to testing it right away, and that is what the rest of this blog post will show you how to do.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Django 6.0
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F15xrzd9oam0qa5pjzgcq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F15xrzd9oam0qa5pjzgcq.gif" alt="animated duck building bricks" width="480" height="480"&gt;&lt;/a&gt;&lt;br&gt;
To fully understand the setups I used, an understanding of my motivation for this Django 6.0 test run would be helpful. All of this CSP work was geared towards my &lt;a href="https://summerofcode.withgoogle.com/" rel="noopener noreferrer"&gt;Google Summer of Code&lt;/a&gt; (GSoC) 2025 project with &lt;a href="https://wagtail.org/" rel="noopener noreferrer"&gt;Wagtail&lt;/a&gt;. The project was about fixing the gaps and ensuring &lt;a href="https://wagtail.org/blog/closing-the-gap-strict-csp-compatibility/" rel="noopener noreferrer"&gt;strict CSP support for Wagtail&lt;/a&gt;.&lt;br&gt;
In a sense, everything I did revolved around the Wagtail CMS, and how sites built with it interfaced with Django.&lt;/p&gt;

&lt;p&gt;I tested with two different open-source Wagtail websites:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;a href="https://github.com/wagtail/bakerydemo" rel="noopener noreferrer"&gt;BakeryDemo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The official marketing website of &lt;a href="https://wagtail.org/" rel="noopener noreferrer"&gt;Wagtail&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Bakery Demo project uses &lt;strong&gt;pip&lt;/strong&gt; from Python for package management, and the Wagtail dot org website uses &lt;a href="https://python-poetry.org/" rel="noopener noreferrer"&gt;Poetry&lt;/a&gt;. The differences in connecting both were very subtle, with the bakery demo being the easier of the two. The overarching requirement was that you would have cloned the most recent version of Django from its &lt;a href="https://github.com/django/django" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. &lt;br&gt;
For the Bakery Demo, you would need a virtual environment and an installation of Django pointing to your local editable version via &lt;code&gt;pip install -e /path/to/django&lt;/code&gt;. This virtual environment would contain your Django, editable version of Wagtail, and Bakery Demo website.&lt;br&gt;
For a poetry-based installation, modifying the project's &lt;code&gt;pyproject.toml&lt;/code&gt; to point to a locally editable version of Django (or mounted volume of it, as in a potential case of containerization)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Keep in mind that you may need to upgrade some dependencies depending on the support hitherto. This may not be the case for most websites you may want to test this with.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Testing a Wagtail/Django site with CSP.
&lt;/h2&gt;

&lt;p&gt;At the time of writing, the &lt;a href="https://docs.djangoproject.com/en/dev/ref/csp/" rel="noopener noreferrer"&gt;docs for using the built-in CSP tool&lt;/a&gt; proved very useful. You could choose to set up CSP to outrightly block unsupported content using the &lt;code&gt;Content-Security-Policy&lt;/code&gt; header in your settings, or you could set it up to report CSP violations (either on the console or via a dedicated URL) with &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt;.&lt;br&gt;
The question of how to write the logic is something that comes with a bit of flexibility. Ultimately, the built-in CSP support in Django reads from your &lt;code&gt;settings.py&lt;/code&gt; file (which can sometimes be split into &lt;code&gt;base.py&lt;/code&gt;, &lt;code&gt;dev.py&lt;/code&gt;, and &lt;code&gt;prod.py&lt;/code&gt;. A setting like the following would get you on your way to Django's CSP usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.utils.csp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CSP&lt;/span&gt;

&lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.middleware.csp.ContentSecurityPolicyMiddleware&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# To enforce a CSP policy in report-only mode:
&lt;/span&gt;&lt;span class="n"&gt;SECURE_CSP_REPORT_ONLY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default-src&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CSP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SELF&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;# Add more directives as needed.
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are already familiar with the external django-csp package, you may have noticed some slight differences in syntax.&lt;/p&gt;

&lt;p&gt;As shown in the comment of the snippet, you can add more CSP directives as you please. This is where the previously mentioned flexibility comes in. You can either write the CSP directives directly in the &lt;code&gt;settings&lt;/code&gt; file, or have some logic still in the &lt;code&gt;settings&lt;/code&gt; to read from an &lt;code&gt;env&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At the project settings level, you can ensure that certain directives cannot be removed or added without changing the state of the codebase, but that can prove rigid if changes are often required. At the environment level (&lt;code&gt;.env&lt;/code&gt;), you can frequently change things, but risk breaking things if the patterns or string literals do not match what is expected. And a combination of both environment and project levels can give you the best of both worlds, only if you make sure that you don't unintentionally make overrides to some directives you otherwise would have liked to leave.&lt;/p&gt;

&lt;p&gt;I tested the Wagtail dot org and Bakery Demo sites, and all the cases work as expected, with no visible difference in performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdvus5416zecaigv0t3n2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdvus5416zecaigv0t3n2.gif" alt="an animated illustration showing thoughts in an avatar's brain" width="440" height="480"&gt;&lt;/a&gt;&lt;br&gt;
I think this is a huge step for Django for a lot of reasons.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Django aims to follow Python's "&lt;a href="https://docs.python.org/3/tutorial/stdlib.html#tut-batteries-included" rel="noopener noreferrer"&gt;batteries included" philosophy&lt;/a&gt;, and already includes a lot of &lt;a href="https://docs.djangoproject.com/en/5.2/topics/security/" rel="noopener noreferrer"&gt;security features&lt;/a&gt;. I think CSP would make a great addition to that list.&lt;/li&gt;
&lt;li&gt;A lot of users may not have already been aware of the concept of CSP, but even the idea of them seeing it in a changelog or "What's new?" section would easily bring this to their notice.&lt;/li&gt;
&lt;li&gt;The external Django-csp tool is a powerful tool, but I imagine the community feedback on an equivalent Django internal would lead to better iterations.&lt;/li&gt;
&lt;li&gt;I also like the idea of &lt;a href="https://github.com/django/django/pull/19680" rel="noopener noreferrer"&gt;CSP decorators&lt;/a&gt; that will let you fine-tune the CSP configurations for each view!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;I'm looking forward to seeing this feature in Django 6.0, and I'll definitely be on the train of people updating functionality on Django sites that I contribute to. I feel adding this to the already-included "Django batteries" is the right direction.&lt;/p&gt;

&lt;p&gt;Special credits to &lt;a href="https://github.com/robhudson" rel="noopener noreferrer"&gt;Rob Hudson&lt;/a&gt; for taking charge of this feature.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>django</category>
      <category>webdev</category>
      <category>security</category>
      <category>python</category>
    </item>
    <item>
      <title>Persisting Config Files Across Computers</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Fri, 21 Feb 2025 23:07:46 +0000</pubDate>
      <link>https://dev.to/chiemezuo/persisting-config-files-across-computers-520g</link>
      <guid>https://dev.to/chiemezuo/persisting-config-files-across-computers-520g</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Isaac Newton's famous third law of motion says: "For every action, there's an equal (in size) and opposite (in direction) reaction." A classic case of this is how the thrill of getting a new machine is often matched by the dread of setting up the said machine. With developers, for instance, the list of things to migrate can go on endlessly. From SSH keys to tools (or their equivalents in a different operating system), account details, program files, environment setups, and configurations. These can take a chunk of someone's time. Even worse, some of them (e.g. configurations) are only possible to the extent of the previous machine still being available. I imagine configurations being a nightmare to remember if something crashes unless there is a previous backup available on demand. I'll talk about a nifty way of keeping configurations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backstory
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2z90c0vzlqf9z0qf9dd.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2z90c0vzlqf9z0qf9dd.gif" alt="Decorative GIF for the word " width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently switched devices and found myself using the &lt;code&gt;zsh&lt;/code&gt; terminal. It seemed very compatible with the &lt;code&gt;bash&lt;/code&gt; commands that I was more familiar with, but I had a big gripe with it right away: "tab completions were not case-sensitive". This was a big deal because I like the idea of my directory names being capitalized, and having to account for this caused a lot of friction. I decided to switch to &lt;code&gt;bash&lt;/code&gt; in the hopes of that being resolved, but I had no such luck. I figured I would have to play around with my terminal configurations and found some commands that did this. I created the required &lt;code&gt;.zshrc&lt;/code&gt; file, copied the commands, saved them, and tested them. It worked, but I encountered another problem shortly after: My active GitHub branches weren't showing in the terminal.&lt;/p&gt;

&lt;p&gt;I use branches a lot, and having the one I'm in show on my terminal is a big relief, and saves me the stress of manually checking every time. This is something I cannot live without, and I got some code from the internet to add to my &lt;code&gt;.zshrc&lt;/code&gt;. Things worked fine, but three questions came to my mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Would I always have to do this while setting up a new machine?&lt;/li&gt;
&lt;li&gt;What if I (or someone else) accidentally deleted the &lt;code&gt;.zshrc&lt;/code&gt; file?&lt;/li&gt;
&lt;li&gt;What if I made a change and wanted to undo it later on?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The third question had the easiest solution: write comments for every new addition. And while having a trash folder felt like an answer to the second question, it wasn't guaranteed that it would always be retrievable.&lt;/p&gt;

&lt;p&gt;As for the answer to all the questions? Well, that's what I'm here to talk about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Approach
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0afefuy2s7248vsxnz79.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0afefuy2s7248vsxnz79.gif" alt="Decorative GIF of a star wars character walking" width="168" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I shared some of my annoyances with the new setup process with a friend of mine and we talked about possible ways of solving these problems. He mentioned a solution he developed for himself after several years of switching machines because he had very precise settings he liked to keep. Hint: it involved Version Control.&lt;/p&gt;

&lt;p&gt;Version Control allows you to track and store different states of a file, with an option to store these versions in a remote repository. This answers the 3 questions from earlier, but brings with it, its own question: "How?"&lt;/p&gt;

&lt;p&gt;There are two ways of doing this with git:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make the location of the &lt;code&gt;.zshrc&lt;/code&gt; a git repo.&lt;/li&gt;
&lt;li&gt;Have the content of the &lt;code&gt;.zshrc&lt;/code&gt; file in a different location (which will be a git repo), and then point to that location.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first approach seems good at first glance. However, the usual location for the &lt;code&gt;.zshrc&lt;/code&gt; file is in the home directory, which also has lots of files. To achieve the desired result, you would need a &lt;code&gt;.gitignore&lt;/code&gt; file in place to ignore everything else &lt;strong&gt;except&lt;/strong&gt; the pertinent config file. The drawback with this is that you could somehow end up committing something new (and sensitive) or even damage something in your home directory while setting up. It's a high-risk low-reward process.&lt;/p&gt;

&lt;p&gt;The second approach, on the other hand, might seem a bit confusing to do at first, but is safer. The trick to achieving this is knowing how to point files to other files, similar to how variables work in programming languages. This is where &lt;a href="http://en.wikipedia.org/wiki/Symbolic_link" rel="noopener noreferrer"&gt;Symbolic links&lt;/a&gt; (symlinks) come in. A symlink is a file that points to another file. Beneath the hood, it's simply a file that contains a text string file path, in a format that the operating system understands to follow up with. Essentially, we could write the configs in a safe (or even public) location, and have their counterparts in the home directory point to them instead. This way, you could make a change in the new location, and everything would reflect as expected. Best of all, you could commit changes without fear of damaging anything, and replicate the process for as many different machines as you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Process
&lt;/h2&gt;

&lt;p&gt;The first step would be knowing how to create a symlink. For POSIX, the shell command to do this is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &amp;lt;target-path&amp;gt; &amp;lt;link-path&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where the &lt;code&gt;&amp;lt;target-path&amp;gt;&lt;/code&gt; is what you want to point to, and the &lt;code&gt;&amp;lt;link-path&amp;gt;&lt;/code&gt; is the copy that points to it. Let's say you have a file &lt;code&gt;a&lt;/code&gt; in the &lt;code&gt;documents&lt;/code&gt; directory and you want to make it read-only and accessible from the &lt;code&gt;desktop&lt;/code&gt; directory, the command would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; path/to/documents/a path/to/desktop/a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;path/to/desktop/a&lt;/code&gt; will be created if no such file exists, but the &lt;code&gt;path/to/documents/a&lt;/code&gt; should ideally exist, else there would be nothing to be linked to.&lt;/p&gt;

&lt;p&gt;For my symlink, I created my new &lt;code&gt;.zshrc&lt;/code&gt; file in the following path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Documents/Code/Scripts/dotfiles/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterward, I initialized the &lt;code&gt;Scripts&lt;/code&gt; folder into a git repo and pushed it to my GitHub remote. Having done this, I edited the &lt;code&gt;.zshrc&lt;/code&gt; and filled it with the copied-over commands from my findings.&lt;/p&gt;

&lt;p&gt;By default, shell terminals look for configuration files in the home directory (&lt;code&gt;~/&lt;/code&gt;). My next step was to link one to the target I created in the git repo. To do this, I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; Documents/Code/Scripts/dotfiles/.zshrc ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I reloaded my terminal afterward, and all my new terminal configurations worked. To be sure changes would reflect, I appended some random characters to my target file and ran &lt;code&gt;cat ~/.zshrc&lt;/code&gt; to see if the characters would reflect, and that worked. Essentially, problem solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;With this technique, I can persist not only shell configs, but also other text file settings I would like to carry along to other machines, and it would take me only one terminal command to get everything working as intended.&lt;/p&gt;

&lt;p&gt;Going even further, I could have this for multiple types of files, and then have a &lt;code&gt;script.sh&lt;/code&gt; that I could run to set up everything automatically. For example, the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; path/to/target/.zshrc ~./zshrc
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; path/to/target/.zshrc_profile ~./zshrc_profile
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; path/to/target/.zshrc_completion ~./zshrc_completion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;would make &lt;code&gt;./script.sh&lt;/code&gt; a convenient script to run, especially on a new machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Things to Note
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The target file and link file do not need to have the same name. It just helps for recognition.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.zshrc&lt;/code&gt; file would work similarly to &lt;code&gt;.bashrc&lt;/code&gt;, depending on the shell terminal you're using. The principles still hold.&lt;/li&gt;
&lt;li&gt;These methods are not limited to just configurations and will work in pretty much any situation where you need to have something in a location you would rather not turn into a repo, but would like to keep for future usage.&lt;/li&gt;
&lt;li&gt;There are different types of config files. I used the one I was most familiar with.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credit
&lt;/h2&gt;

&lt;p&gt;Special thanks to my friend &lt;a href="https://github.com/rec" rel="noopener noreferrer"&gt;Tom&lt;/a&gt; for pointers on how to solve the problem.&lt;/p&gt;

&lt;p&gt;Finally, thank you for reading.&lt;/p&gt;

&lt;p&gt;Cheers. 🥂 &lt;/p&gt;

</description>
      <category>bash</category>
      <category>programming</category>
      <category>git</category>
      <category>shell</category>
    </item>
    <item>
      <title>How a Lottery Quest Led Me to The Powers of PyTorch</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Sat, 28 Dec 2024 22:15:56 +0000</pubDate>
      <link>https://dev.to/chiemezuo/how-a-lottery-quest-led-me-to-the-powers-of-pytorch-46c1</link>
      <guid>https://dev.to/chiemezuo/how-a-lottery-quest-led-me-to-the-powers-of-pytorch-46c1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;At some point in time, you have probably heard that your chances of winning &lt;strong&gt;a&lt;/strong&gt; lottery are very slim. As with all things related to probability, multiple trials might tilt an outcome in your favor. Now, if you were to participate in many lotteries, your chances of winning one would be a bit better, depending on how many more lotteries you participated in. This is still by no means a guarantee that you will eventually win, but with &lt;a href="https://www.investopedia.com/terms/u/uniform-distribution.asp" rel="noopener noreferrer"&gt;uniform distribution&lt;/a&gt;, and going by the &lt;a href="https://en.wikipedia.org/wiki/Law_of_large_numbers" rel="noopener noreferrer"&gt;law of large numbers&lt;/a&gt; (in this case meaning a large number of lotteries), we can arrive at relatively more likely possibilities.&lt;/p&gt;

&lt;p&gt;It's important to understand that each new lottery is independent of any other, and the same lottery "ticket number" could win many different lotteries (following the law of large numbers). You could also be unlucky and pick the wrong number in every lottery, regardless of how many times you tried. You have two options now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can try a random number every time.&lt;/li&gt;
&lt;li&gt;You can try the &lt;strong&gt;same&lt;/strong&gt; number every time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Theoretically (and mathematically), both scenarios have an equal likelihood of occurring. However, scenario 2 will give you a slight edge. As the number of times approaches infinity, every number will eventually be selected. The problem is that with scenario 1, you need to try more times with the hope that the number you've picked at the time matches the number that wins. With scenario 2, you're sure that as the trials tend to infinity, your number will at some point "win". For this blog post, we will use scenario 2.&lt;/p&gt;

&lt;p&gt;So, do you think you can answer this question before I tell you the answer?&lt;/p&gt;

&lt;p&gt;"If all lotteries around you had slots for exactly 1 million people and you picked the same ticket [x] for everyone you played, how many lotteries would you have to play to finally be a winner?" (Feel free to comment on what your initial answer was)&lt;/p&gt;

&lt;p&gt;The answer is...&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhuk1zxw9jf9cim3m3lq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhuk1zxw9jf9cim3m3lq.gif" alt="GIF of a man in a suit doing drum rolls" width="384" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;About &lt;strong&gt;14.4 million times&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The rest of this blog post will be about how I arrived at that value, how the simulations were done, and some caveats. Things will get more technical from here.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3z1ue7w858f2e56zeub.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3z1ue7w858f2e56zeub.gif" alt="" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Logic
&lt;/h2&gt;

&lt;p&gt;The ticket numbers of a lottery of 1 million people would range from 1 - 1,000,000 (or 0 - 999,999). Players can only pick a number within that range for each lottery, and the winning ticket can only be from that range. Essentially, we can say we will have a set of 1 million numbers.&lt;/p&gt;

&lt;p&gt;Accounting for the fact that a user can pick any number within that range, we need to satisfy the condition of every item in the set being hit at least once. This is because if every number has been called at least once, it would cover any possible ticket number a player could have picked. This also means we do not care about how many times each number runs, making a "set" the ideal Python data structure to use for our simulation. We will start with an empty set, and populate it with a randomly generated number at each iteration until the set contains every number within the specified range. Since Python sets do not repeat numbers, we do not have to worry about ensuring uniqueness.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_lottery_chances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lottery_players_count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;number_set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;lottery_players_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;gen_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lottery_players_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;number_set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a lottery of 1,000,000 people, the function call would look like: &lt;code&gt;calculate_lottery_chances(1000000)&lt;/code&gt;, and it would return the number of lottery attempts before winning. Arranging the code in this manner makes it very extendable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh5cmlqw1veoz1uyfyirs.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh5cmlqw1veoz1uyfyirs.gif" alt="GIF of a man in a suit, uttering the words " width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;In a nutshell, the root cause of the problem is "variation". The first time I ran the function, I got "13.1 million" times as my value. I reran it, and got something along the lines of 13.9 million. I did this even more times and got widely varying answers -at some point, I got 15 million. It was clear that I would need to do this and find an average. Following the existing pattern so far, I figured that as the number of iterations to average it by tended towards infinity, I would be closer to having &lt;strong&gt;one&lt;/strong&gt; reliable answer. There was a need for something that could do this, and do it fast, and that led me to write this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;average_over_n_times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;function_arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
  This returns the average of the returned value of a function
  when it is called n times, with its (one) arg
  &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
  &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;function_arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Subsequently, everything would then be patched up as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="n"&gt;num_of_trials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;average_over_n_times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calculate_lottery_chances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lottery_players_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where "n" would represent the number of times to average results by. This, however, brings up another problem that will be discussed in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should "n" be?
&lt;/h2&gt;

&lt;p&gt;The larger the value of n, the closer to an "average-case" result. However, considering that there are still no absolutes or certainties, performing this series of tasks too many times stops being productive. I say this for the following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time is not infinite, and we cannot perform these calculations indefinitely, meaning that there will always be a variation (no matter how little) each time it is run, defeating the idea of an "absolute".&lt;/li&gt;
&lt;li&gt;Computational resources are finite.&lt;/li&gt;
&lt;li&gt;One of the assumptions of this experiment is that "randomness" as generated by computers can accurately mimic reality.&lt;/li&gt;
&lt;li&gt;Just as with algorithm runtimes, smaller magnitudes stop being as important as the greater ones. A variation of about 100,000 wouldn't be as significant when dealing with values greater than 13,000,000.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping these in mind, I tested "n" with the values: 10, 20, 30, 50, 100, 1000, and 5000 times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does PyTorch come in?
&lt;/h2&gt;

&lt;p&gt;At this point, you're probably wondering why the word "PyTorch" in the title of the blog post hasn't even been mentioned. Well, although I mentioned testing n with different values, it wasn't the same code I used for all the tests.&lt;/p&gt;

&lt;p&gt;These were computationally heavy experiments, and my CPU had a word with me. The code snippets I shared earlier were written in one file that had zero external package dependencies, and the file was run in the bash shell with the &lt;code&gt;time&lt;/code&gt; command prepended to track execution times. Here's what the execution times looked like when using just the CPU:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Time (min and sec)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1m34.494s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;3m2.591s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;5m19.903s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;10m58.844s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;14m56.157s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At 1000, I couldn't get the program to work anymore. I wasn't sure if it broke halfway and failed to stop the execution, but I canceled it after 4 hours and 57 minutes. There are a few factors I feel influenced this, which I will discuss in the "caveats" section. Anyway, my fan noise was blaring, and I knew I may have pushed my laptop's modestly powered CPU a bit too much. I refused to accept defeat, and while thinking of what I could do to at least run 4-digit iterations, I remembered something a friend of mine who worked with PyTorch told me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"GPUs are generally more efficient at computationally intensive tasks than CPUs"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PyTorch uses the GPU, making it the perfect tool for the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactoring
&lt;/h2&gt;

&lt;p&gt;PyTorch would be used for calculations for our purposes, so refactoring the existing &lt;code&gt;calculate_lottery_chances()&lt;/code&gt; code would mean changing CPU-reliant numerical operations and switching to suitable PyTorch data structures. In a nutshell:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Python &lt;code&gt;set()&lt;/code&gt; data type would no longer suffice.&lt;/li&gt;
&lt;li&gt;The Python &lt;code&gt;randint()&lt;/code&gt; function would be swapped for its PyTorch equivalent.&lt;/li&gt;
&lt;li&gt;Seeing as the &lt;code&gt;set()&lt;/code&gt; data type would be insufficient, there would be a switch to generating a tensor of zeros that matches the size of the &lt;code&gt;lottery_players_count&lt;/code&gt;, with a boolean to indicate whether or not a number had previously won.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The refactor of &lt;code&gt;calculate_lottery_chances&lt;/code&gt; would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_lottery_chances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lottery_players_count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

  &lt;span class="n"&gt;seen_numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lottery_players_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;xpu&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;batch_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;
  &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seen_numbers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Generate a batch of random numbers
&lt;/span&gt;    &lt;span class="n"&gt;random_numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lottery_players_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
      &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;xpu&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;seen_numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;random_numbers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;   

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I set my device as "xpu" because my computer uses an Intel Graphics GPU, which &lt;a href="https://pytorch.org/docs/stable/notes/get_start_xpu.html" rel="noopener noreferrer"&gt;PyTorch supports&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Output
&lt;/h2&gt;

&lt;p&gt;To ensure my GPU was used during execution, I opened my Windows task manager and navigated to the "performance" section before running. On running, I saw a noticeable spike in GPU resource usage.&lt;br&gt;
For context, here is a before vs after:&lt;/p&gt;

&lt;p&gt;Before:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6zy28ujtm0w003r5gna.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6zy28ujtm0w003r5gna.png" alt="GPU usage at 1% before execution" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
Notice the GPU usage is at 1%&lt;/p&gt;

&lt;p&gt;After:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlcei25tu3o89ugdc6pi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlcei25tu3o89ugdc6pi.png" alt="GPU usage at 49% after execution" width="800" height="417"&gt;&lt;/a&gt;&lt;br&gt;
Notice the GPU usage is at 49%&lt;/p&gt;

&lt;p&gt;For the runtimes for varying values of n, the GPU was several times faster. It ran values of n below 100 consistently at less than a minute, and was able to calculate for a value of n at &lt;strong&gt;5000&lt;/strong&gt; (five thousand!)&lt;/p&gt;

&lt;p&gt;Here's a table of runtimes using the GPU:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Time (min and sec)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0m13.920s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0m18.797s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;0m24.749s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;0m34.076s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1m12.726s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;16m9.831s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To have a visual sense of how large the performance gap between the GPU and CPU operations was for this experiment, here's a data visualization to think about:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ryk0x2jtozpwoso6s85.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ryk0x2jtozpwoso6s85.png" alt="Data visualization plot showing the differences in CPU vs GPU using the tabulated results from each" width="703" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The x-axis was capped at 100 because I could no longer get realistically "timely" output from the CPU, thus leaving no room for comparison with the GPU. Performing the experiments with numbers within the range of 1000 - 5000 gave me about "14.4 million times" as a result, more often than not. That's how I got the answer from earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;This experiment made assumptions and relied on certain ways of doing things. Additionally, my inexperience with PyTorch potentially means there may have been a more efficient approach. Here are some factors to consider that &lt;strong&gt;may have&lt;/strong&gt; influenced either the accuracy of my findings or the execution times:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I made a subtle assumption that computer-generated randomness mimics randomness in real life (the physical world).&lt;/li&gt;
&lt;li&gt;While I switched a bit of the logic to use PyTorch, the rest of the code still relied on the CPU. For instance, in the &lt;code&gt;average_over_n_times()&lt;/code&gt; function, it's possible that both the addition in the loop and the averaging may have benefitted from PyTorch equivalents. I suspect there would have been a performance boost.&lt;/li&gt;
&lt;li&gt;I'm unsure of the impact of the &lt;code&gt;batch_size&lt;/code&gt; I used on accuracy and performance.&lt;/li&gt;
&lt;li&gt;All CPU and GPU tests were done with my PC plugged in, to allow the machine to work at its best. Running them with a device on battery power may see longer runtimes.&lt;/li&gt;
&lt;li&gt;PyTorch's CUDA might have the edge over "XPU", but my PC doesn't have support for the former. &lt;/li&gt;
&lt;li&gt;I avoided letting my PC "sleep" during the tests. Tests may potentially take longer to run if your computer sleeps.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, I'd like to point out that this was my first time using PyTorch for anything, and I was quite impressed with the performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;When I went down the rabbit hole with this, I didn't expect to see such gains in performance. I learned the idea behind tensors and a few things about the supporting mechanisms behind even more computationally complex tasks. You have the freedom to use, replicate, or modify the code snippets as you please.&lt;/p&gt;

&lt;p&gt;Thank you for indulging me, and I hope you had a fun read.&lt;/p&gt;

&lt;p&gt;Till next time,&lt;/p&gt;

&lt;p&gt;Cheers. 🥂&lt;/p&gt;

</description>
      <category>python</category>
      <category>pytorch</category>
      <category>watercooler</category>
      <category>programming</category>
    </item>
    <item>
      <title>Google Summer of Code '24 Final Submission</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Thu, 22 Aug 2024 15:30:56 +0000</pubDate>
      <link>https://dev.to/chiemezuo/google-summer-of-code-24-final-submission-1661</link>
      <guid>https://dev.to/chiemezuo/google-summer-of-code-24-final-submission-1661</guid>
      <description>&lt;p&gt;I'm so excited to be writing this. Some months ago, I indicated my interest in contributing to the Wagtail CMS project for the 2024 Google Summer of Code program, and my proposal to "&lt;a href="https://summerofcode.withgoogle.com/media/user/82efa2ed9e6f/proposal/gAAAAABmxK6EFd-XpO45rsc03lRanrUfqQ8yFXedJsDM3HBuQUmsl8H1lsYW3Wezjp8yHbQC8jJadTGLqxAuX3ayFz66VSUSL-52Qvxew7p7mIN0aLltSHE=.pdf" rel="noopener noreferrer"&gt;improve alt text capabilities&lt;/a&gt;" was accepted. &lt;br&gt;
Story time!!&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;In the Wagtail CMS, "blocks" are the simplest units for storing content. They work similarly to Django's model fields and forms. For every type of data you would want to store in Wagtail, there is a variety of blocks. &lt;code&gt;CharBlock&lt;/code&gt; for characters, &lt;code&gt;DateBlock&lt;/code&gt; for dates, &lt;code&gt;IntegerBlock&lt;/code&gt; for integers, and so much more for types that their prefixed names represent. &lt;/p&gt;

&lt;p&gt;Apart from these primary blocks, Wagtail has some more advanced blocks such as &lt;code&gt;PageChooserBlock&lt;/code&gt; for "choosing" page objects, and &lt;code&gt;ImageChooserBlock&lt;/code&gt; for choosing images from the gallery of already-uploaded ones or for uploading and then selecting. Here's a &lt;a href="https://docs.wagtail.org/en/latest/reference/streamfield/blocks.html" rel="noopener noreferrer"&gt;full list of blocks in Wagtail&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My project involved working with the &lt;code&gt;ImageChooserBlock&lt;/code&gt; that looked like the following in an empty state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwo8v2puhd19ruuw85z2d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwo8v2puhd19ruuw85z2d.png" alt="ImageChooserBlock in the blank state with no selected image, with a prompt to choose an image" width="657" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When an image gets chosen, the &lt;code&gt;ImageChooserBlock&lt;/code&gt; looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitcl0yjxpt46tz0yle1h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitcl0yjxpt46tz0yle1h.png" alt="ImageChooserBlock after an image has been selected, with a thumbnail of a freshly baked round bread, with the title of 'Breads 6'" width="622" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wagtail leaves the implementation of alt text mechanisms in the hands of the developers that build with it, and the only fallback for missing alt texts is the image's title. The title field gets auto-populated with the image's filename on the computer. This field is editable but is often ignored by editors. Here's a &lt;a href="https://docs.google.com/spreadsheets/d/1MgHVWYuKjdLkqiCOJzli7wQKjmjpeHjLL1naJYHeeDw/edit?gid=1796758131#gid=1796758131" rel="noopener noreferrer"&gt;survey carried out on Wagtail sites&lt;/a&gt; where alt texts were graded on a scale of 0-5. About 54% of images on the web pages examined had poor alt texts, and 22% had completely meaningless alt text. Among the 22% were mostly alt texts that looked like computer defaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Goals
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;To provide a new and improved &lt;code&gt;ImageBlock&lt;/code&gt; with support for handling contextual image alt texts.&lt;/li&gt;
&lt;li&gt;To add a new &lt;code&gt;description&lt;/code&gt; field to the image model.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I did
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;I created an &lt;a href="https://github.com/wagtail/rfcs/pull/97" rel="noopener noreferrer"&gt;RFC to communicate my proposed changes&lt;/a&gt; in technical detail with the Wagtail core team.&lt;/li&gt;
&lt;li&gt;I created a &lt;a href="https://github.com/wagtail/wagtail/pull/11791" rel="noopener noreferrer"&gt;new ImageBlock&lt;/a&gt; with full backward compatibility with the &lt;code&gt;ImageChooserBlock&lt;/code&gt;, hence removing the need for data migration.&lt;/li&gt;
&lt;li&gt;I created a &lt;a href="https://github.com/wagtail/wagtail/pull/11792" rel="noopener noreferrer"&gt;new description property&lt;/a&gt; for the &lt;code&gt;AbstractImage&lt;/code&gt; class.&lt;/li&gt;
&lt;li&gt;I made documentation efforts on both.&lt;/li&gt;
&lt;li&gt;I wrote tests for all of the new features I introduced (included in their PRs).&lt;/li&gt;
&lt;li&gt;I updated the official &lt;a href="https://github.com/wagtail/bakerydemo/pull/492" rel="noopener noreferrer"&gt;demo site's repository&lt;/a&gt; to replace the &lt;code&gt;ImageChooserBlock&lt;/code&gt; with the newer and more recommended &lt;code&gt;ImageBlock&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I collaborated with my mentors to develop a &lt;a href="https://github.com/wagtail/rfcs/blob/main/text/097-alt-text-capabilities.md#proposed-alt-flow" rel="noopener noreferrer"&gt;proposed image alt text flowchart for Wagtail.&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Current State
&lt;/h2&gt;

&lt;p&gt;At the point of my writing this, the RFC has been merged, while the &lt;code&gt;ImageBlock&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; field PRs are still being reviewed. Feedback on those two PRs looks good but needs more reviews before a merge to the codebase. The expectation is that these changes will feature in Wagtail's 6.3 release, which should be on the 1st of November, 2024. &lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;Among some of the challenges I faced with the project were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scope change: Certain unexpected things came up, and adjustments were made. I was consequently also unable to work on the project's stretch goals.&lt;/li&gt;
&lt;li&gt;Community requirements: My mentors and I always carried the community along in all the decisions we were to make. It came at a bit of a cost because feedback wasn't as rapid, and in some cases, the community opted for a different approach from what we had in mind. Regardless, the synergy was worth it.&lt;/li&gt;
&lt;li&gt;Codebase internals: Some of the fixes involved deeper parts of Wagtail's internal code, and I found myself frequently reaching out to maintainers for more clarity and advice.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;p&gt;I learned a lot about collaboration, time management, working as a community, and reaching out for help.&lt;br&gt;
I also significantly strengthened my understanding of git, after frequently damaging my commit histories.&lt;/p&gt;

&lt;p&gt;Overall, I learned much more about Wagtail and the magic behind what it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Potential Future Modifications
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;As is, the &lt;code&gt;ImageBlock&lt;/code&gt; can be modified to have a suggestions UI. This suggestion UI can prompt the editor to re-use the image's description and then populate the alt text field with it.&lt;/li&gt;
&lt;li&gt;A possibility of using AI models to help generate alt text for the images, and then prompting with the suggestion UI. AI models perform quite well with image alt texts in general, and suggestions from them would be of great help in nudging editors to be more thorough (and creative) with alt texts. For a proof of concept, I built a loosely coupled &lt;a href="https://github.com/chiemezuo/wa11y" rel="noopener noreferrer"&gt;Chromium-based browser extension&lt;/a&gt; to show how AI models excel with alt text.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;It took other people's additional efforts to achieve this project's objectives. I was privileged to work with the following people/groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My mentors &lt;a href="https://stormheg.co/" rel="noopener noreferrer"&gt;Storm Heg&lt;/a&gt; and &lt;a href="https://saptaks.website/" rel="noopener noreferrer"&gt;Saptak Sengupta&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Wagtail's GSoC coordinator &lt;a href="https://torchbox.com/about/team/thibaud-colas/" rel="noopener noreferrer"&gt;Thibaud Colas&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Wagtail's lead developer &lt;a href="https://torchbox.com/about/team/matthew-westcott/" rel="noopener noreferrer"&gt;Matthew Westcott&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lb.ee/" rel="noopener noreferrer"&gt;LB&lt;/a&gt;, &lt;a href="https://github.com/tm-kn" rel="noopener noreferrer"&gt;Tomasz&lt;/a&gt;, and &lt;a href="https://github.com/vsalvino" rel="noopener noreferrer"&gt;Vince Salvino&lt;/a&gt; for their reviews of my RFC.&lt;/li&gt;
&lt;li&gt;Wagtail's Accessibility team.&lt;/li&gt;
&lt;li&gt;Wagtail's Core team.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>GSoC Week 12</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Mon, 19 Aug 2024 22:28:00 +0000</pubDate>
      <link>https://dev.to/chiemezuo/gsoc-week-12-eik</link>
      <guid>https://dev.to/chiemezuo/gsoc-week-12-eik</guid>
      <description>&lt;p&gt;This was my penultimate week (going by the timeline for the final submission), so there was less new code being added, and more thorough testing of the code I'd already written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekly Check-in
&lt;/h2&gt;

&lt;p&gt;We had no meeting because Storm was on holiday and Saptak had a clashing meeting. There wasn't much to discuss, so we opted to catch up on Slack. The Slack discussions circled around making decisions on some of the questions that Thibaud raised when reviewing my pull requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;I probably unlocked superpowers because I didn't quite hit many stumbling blocks. I suppose that's one of the perks of working on a codebase for several weeks.&lt;br&gt;
What I did find a bit challenging, however, was fixing up the &lt;code&gt;bulk_to_python&lt;/code&gt; method in my &lt;code&gt;ImageBlock&lt;/code&gt;. The implementation I'd had up to that point defeated the purpose of the bulk method because in the case where the received data previously belonged to an &lt;code&gt;ImageChooserBlock&lt;/code&gt;, it ran a database query for each individual ID contained in the list elements. Fixing this required my going through the methods in the parent classes to get a sense of how things ran so that I could take advantage of the parent class' method. Afterwards, I would have to convert the format to something the &lt;code&gt;ImageBlock&lt;/code&gt; could work with. It was simple, but it sure wasn't easy.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;I kept hitting some errors that made progress more difficult for me. I ended up running to Chat-GPT for some answers, and I made a nice finding. The following two code blocks might look the same but are very different.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for value in values:
            if isinstance(value, int):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if all(isinstance(value, int) for value in values):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was stuck trying to get the latter to work because it looked "fancier" and more concise than the former, and this was because it was inherently wrong, although seemingly correct.&lt;/p&gt;

&lt;p&gt;The first block of code goes through a list and checks each item in the list for a condition. The second block might look like it does the same thing, but I realized that the part after the &lt;code&gt;if&lt;/code&gt; was a "generator expression" (as explained by ChatGPT). This meant that it would always evaluate to &lt;code&gt;True&lt;/code&gt; because it was interpreted as "if there's a generator expression" rather than "if the output of this generator expression is true". Switching to the former solved the problems I had, however, it took a lot of debugging for me to find out that the error was actually from the &lt;code&gt;if&lt;/code&gt; statement. I spent so much time logging every layer of the &lt;code&gt;ImageBlock&lt;/code&gt;, the values for the methods, and the values in the parent class methods before making my findings.&lt;/p&gt;

&lt;p&gt;After fixing the error though, I tested as thoroughly as I could and pushed my changes. All that was left were a few more comments that I thought I would resolve in my weekly meetings with Thibaud. However, he was unable to attend the meetings, so I pushed the final touches for the following week. I also asked my mentor to look into a CI error I was getting all of a sudden. We weren't able to get to the bottom of it and decided we'd face it squarely in the last week. This week, more than ever, I was so confident about finishing my Summer of Code program on a high note.&lt;/p&gt;

&lt;p&gt;Thank you for reading this far, and kudos to you if you actually somehow followed up on this series.&lt;/p&gt;

&lt;p&gt;Till next time.&lt;br&gt;
Cheers. 🥂&lt;/p&gt;

</description>
      <category>googlesummerofcode</category>
      <category>gsoc</category>
      <category>opensource</category>
      <category>wagtail</category>
    </item>
    <item>
      <title>GSoC Week 11</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Mon, 19 Aug 2024 00:22:45 +0000</pubDate>
      <link>https://dev.to/chiemezuo/gsoc-week-11-14g9</link>
      <guid>https://dev.to/chiemezuo/gsoc-week-11-14g9</guid>
      <description>&lt;p&gt;I apologize once again from releasing this quite late, but in preps for concluding my Summer of Code program, I've been working for longer hours trying to patch certain things up, and the toll of the program has been somewhat heavy. The good news is that things are looking quite good from my perspective, and I'm sure I'll be on track for the final submission period. This summary of the week will be brief because I spent most of the time reading reviews of my PRs and making fixes to things as requested by the reviewers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekly Check-in
&lt;/h2&gt;

&lt;p&gt;My mentors and I touched on a few topics, one of which was about the fact that this project's work would likely feature in Wagtail's version 6.3 release. This would mean a lot more time to absolutely fire-proof the code, while still giving us a good timeline for the GSoC final submission.&lt;/p&gt;

&lt;p&gt;We talked about the overall progress report of the project and the fact that my &lt;a href="https://github.com/wagtail/rfcs/pull/97" rel="noopener noreferrer"&gt;RFC&lt;/a&gt; had finally been merged!&lt;/p&gt;

&lt;p&gt;I discussed my Internet Connectivity issues which I'll explain later, and Storm hinted at me updating the fixtures file for the demo project repo to reflect the changes I had made in the admin interface.&lt;/p&gt;

&lt;p&gt;It was more or less a very straightforward meeting, and there was a mutual understanding of the week's tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;I couldn't do as much as I would have normally done for the week because I had a big setback: internet connectivity. There was a nationwide protest in my country (Nigeria), and in a reactionary measure (possibly as instructed by government agencies), major Internet Service Providers shut down internet services. I couldn't even load simple web pages anymore. All my meetings lagged, my GitHub refused to connect, and I couldn't even install packages for a proof of concept I was trying to do for the next major iteration of this project (outside the scope of GSoC). This internet outage happened in the middle of the week, so things got unproductive quickly. To try to catch up, I moved my working hours to nighttime because the Internet was a little bit better at night, long after the protest hours. I had to do this for about 3 days, although it was quite tough getting my body to adapt to the sudden change in timing.&lt;/p&gt;

&lt;p&gt;The next challenge I'd say I had was with some reviews that had open-ended questions. This was not a bad thing in itself, but it left me in a position where I wasn't entirely sure which of the options would be best for any given process. I did push for some more experienced Wagtail contributors to weigh in on the questions and comments. &lt;/p&gt;

&lt;p&gt;Again, because I had bad internet problems, I wasn't able to do as much, and therefore didn't have too many challenges.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;Most of all, I'd say that for the week, I learned about the inevitability of unforeseen circumstances. I felt deeply helpless and embarrassed when my Internet went off. I also found myself wondering about just how much of my life revolved around computers and the internet. It felt like a sign to go and touch a bit of grass.&lt;/p&gt;

&lt;p&gt;I also saw the importance of having multiple backups for essentials like the Internet (although the backups I had also failed). Overall, sometimes things really are beyond our absolute control, but that's okay. It's life, and life has a way of humbling us all.&lt;/p&gt;

&lt;p&gt;Thank you for reading this far.&lt;/p&gt;

&lt;p&gt;Till next time.&lt;br&gt;
Cheers. 🥂&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>googlesummerofcode</category>
      <category>wagtail</category>
      <category>opensource</category>
    </item>
    <item>
      <title>GSoC Week 10</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Wed, 07 Aug 2024 09:11:33 +0000</pubDate>
      <link>https://dev.to/chiemezuo/gsoc-week-10-5kc</link>
      <guid>https://dev.to/chiemezuo/gsoc-week-10-5kc</guid>
      <description>&lt;p&gt;In any project, it's expected that a couple of things will change a little bit, often because there is more information now at one's disposal. Some other times, it's because of an extra creativity that comes when a person is moving at a good enough pace. Regardless of the situation, a mental compass of the initial direction is still needed not to derail too far. We, once again, made some design alterations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekly Check-in
&lt;/h2&gt;

&lt;p&gt;My mentors and I walked through the previous week's challenges and successes and reviewed the progress so far. I also mentioned reaching out to people higher up in Wagtail for help with another issue I'd encountered with the Suggestion UI in the previous week.&lt;/p&gt;

&lt;p&gt;Storm talked about his holidays and discussed his availability during that period. Saptak didn't plan on going on any holidays right away so he would still be available till the end of my GSoC program for the most part. I mentioned how Thibaud offered to take some time from his own holidays to help me round up my GSoC project.&lt;/p&gt;

&lt;p&gt;Finally, my mentors told me to get started with preps for the blog post I would have to make. I would first have to start with an overall idea of what I would like it to look like, and then build upon it.&lt;/p&gt;

&lt;p&gt;Here's some extra good news: My RFC has been approved and is currently awaiting a merge upon completion of the "waiting time".&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;The problem with the Suggestion UI is that there was no "readily" supported way of retrieving Python database values from the Javascript side of the logic, and those values couldn't be sent from Python to the form because the expected initial data would always be &lt;code&gt;None&lt;/code&gt; or "empty". I discussed with Matt (Wagtail's lead dev) and he mentioned that there were some things in the &lt;code&gt;Telepath.js&lt;/code&gt; library that could potentially be changed, but that it would involve some level of complexity. Nearing the completion of my GSoC program, additional levels of complexity, especially on internal tooling that I had never worked with before felt like a lot of effort in the wrong direction. I discussed it with Thibaud, and he mentioned that he would look at it, but that if it felt like a lot of extra unplanned work, then it could be worked upon iteratively &lt;em&gt;after&lt;/em&gt; my GSoC program by either any Wagtail dev or me. I was largely in support of moving it aside because it was a blocker for the rest of the projects.&lt;/p&gt;

&lt;p&gt;However, the biggest challenge I faced in the previous week was the "undocumented" series of problems that happened as a result of the &lt;a href="https://www.bbc.com/news/articles/crgeexzdl4wo" rel="noopener noreferrer"&gt;nationwide protest in my country&lt;/a&gt;. The government shut down some parts of our Internet access and electricity system. This made it particularly difficult for me to do any work, and at some point in time it was impossible to even access Git and GitHub. For two working days in the previous week, I couldn't do a single thing except clean up code offline (I couldn't even push). This meant I had to work during the weekend to account for time wasted beyond my control. I also noticed that Internet access was more bearable at night time, so I had to tweak my working schedule. It did take a physical toll on me, but it's the only way I was able to get work done. &lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;I realized that the more I had to explain the code I had (and why it did what it did), the more my fundamental understanding of Wagtail blocks got better. I have become much better at spotting properties and attributes of models in Wagtail. I also now know how to confidently access the docstrings at will. In one of the reviews, Thibaud pointed out how I could check code test coverage, and Storm mentioned modifying fixtures for the &lt;a href="https://github.com/wagtail/bakerydemo" rel="noopener noreferrer"&gt;bakerydemo setup project&lt;/a&gt;. I hadn't previously known about them in Django, but I got to do some reading about them in this &lt;a href="https://docs.djangoproject.com/en/5.0/topics/db/fixtures/" rel="noopener noreferrer"&gt;article about fixtures&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once again, I fried my commit history when I was trying to revert some changes I did somewhere else. It took me on another rollercoaster where certain commits were doubled, but this time, it was on one of the primary branches I was working on. I panicked at first, but after some time (and lots of effort), I decided that I would be able to start over if it got damaged beyond repair. In my adventure battling this problem, I learned the powerful "drop" keyword when rebasing, and thoroughly vetted what I was dropping with help from GitHub's interface to verify the hashes. Much to my happiness, it did work out just fine. It's safe to say I'm developing superpowers with Git.&lt;/p&gt;

&lt;p&gt;The week had its ups and downs, but few things move linearly, and I'll take the wins and lessons from the losses.&lt;/p&gt;

&lt;p&gt;Thanks again for reading this far.&lt;/p&gt;

&lt;p&gt;Till next time.&lt;br&gt;
Cheers. 🥂&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>googlesummerofcode</category>
      <category>wagtail</category>
      <category>opensource</category>
    </item>
    <item>
      <title>GSoC Week 9</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Thu, 01 Aug 2024 23:41:14 +0000</pubDate>
      <link>https://dev.to/chiemezuo/gsoc-week-9-329o</link>
      <guid>https://dev.to/chiemezuo/gsoc-week-9-329o</guid>
      <description>&lt;p&gt;Compared to the previous weeks, this week had little "excitement", but I was particularly grateful for that. Sometimes, stability beats excitement, because one can only keep on for so long. Let's dive right in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekly Check-in
&lt;/h2&gt;

&lt;p&gt;My mentors and I went through the previous week's work. My tasks from the last week were mostly successful, so there was positive news on my end. I also had to open up to my mentors that given the situation of things, the stretch goal of using AI to generate alt text was looking less feasible. They understood, but we agreed to discuss more about it in the coming week.&lt;/p&gt;

&lt;p&gt;We particularly talked about pending tasks that were stalling the completion of the PRs. I drafted a work outline of how I planned to approach completing the changes provided I did not encounter any esoteric Wagtail internals that would require special assistance. Storm mentioned his holiday starting in the coming week, so I mentioned that I'd work a bit faster so I could make the most out of his current availability before the holiday time comes.&lt;/p&gt;

&lt;p&gt;Thibaud and I agreed to bi-weekly (twice a week) sessions where we could address bottlenecks and blockers. His own holiday would be starting on the 1st of August, and he promised to dedicate some of his time to helping me out, especially in the would-be absence of my primary mentor Storm. &lt;/p&gt;

&lt;p&gt;Finally, the weekly check-in ended with my mentors notifying me about an internship post I would have to make towards the end of GSoC. Storm told me I would probably receive the official email soon, but he wanted to give me a heads-up so I could brace myself for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;I faced some really tough challenges trying to render a custom form template for my new block. Of course, I eventually had to reach out to Wagtail's lead dev for help towards the end of the week because I was hitting a brick wall. He was kind enough to give me some history about Wagtail, and why a certain development choice they made interfered with the default process I was trying to account for. He also gave me something to look at, and some helpful snippets of patch code I could write for this. This did work, but led me to another challenge which I would move to the following week, as it was already the weekend, and I had spent so much time trying to fix things.&lt;/p&gt;

&lt;p&gt;That was the most challenging problem I faced, as everything else I was to do felt very familiar.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Among my technical tasks for the week, I had to update the &lt;a href="https://github.com/wagtail/bakerydemo" rel="noopener noreferrer"&gt;bakeryDemo&lt;/a&gt; examples to use the new Block as the Wagtail standard. This then prompted me to read the contributing docs for the &lt;code&gt;bakerydemo&lt;/code&gt; project, and consequently, it prompted me to look deeper into Django &lt;a href="https://docs.djangoproject.com/en/5.0/topics/db/fixtures/" rel="noopener noreferrer"&gt;fixtures&lt;/a&gt;. It's on my list of things to read, and I just might write about what I've learned from it.&lt;/p&gt;

&lt;p&gt;Exploring the form template for the &lt;code&gt;ImageBlock&lt;/code&gt; also exposed me to the crafty way Wagtail mixes a bit of server-side rendering with client-side rendering, and by extension, the &lt;a href="https://github.com/wagtail/telepath" rel="noopener noreferrer"&gt;Telepath.js&lt;/a&gt; library. That was/is a part of Wagtail I had barely ever touched, so my curiosity got the better of me.&lt;/p&gt;

&lt;p&gt;Finally, I had to move the release notes because these new features would no longer be candidates for version &lt;code&gt;6.2&lt;/code&gt;. I tweaked them and temporarily moved them to a Google Doc, to be shared when it's time to make the changes. &lt;/p&gt;

&lt;p&gt;As a side fun fact, I think I'm gradually getting the hang of the versioning systems.&lt;/p&gt;

&lt;p&gt;Thank you once again for reading this far.&lt;/p&gt;

&lt;p&gt;Till next time.&lt;br&gt;
Cheers 🥂&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>gsoc</category>
      <category>googlesummerofcode</category>
      <category>wagtail</category>
    </item>
    <item>
      <title>GSoC Week 8</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Fri, 26 Jul 2024 18:04:30 +0000</pubDate>
      <link>https://dev.to/chiemezuo/gsoc-week-8-c42</link>
      <guid>https://dev.to/chiemezuo/gsoc-week-8-c42</guid>
      <description>&lt;p&gt;I initially lacked the zeal to write about this week, but decided I had to, because I promised myself I'd write about my GSoC on a weekly experience. Also, I'd be annoyed with myself if I chickened out. My lack of zeal was reflected in the fact that this blog post was overdue for several days. The week didn't quite go as I had planned, but it was good, and afterward, I spent the weekend away from my computer. This was the week my mentors and I finally decided to change some parts of the new functionality we'd spent weeks working on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekly Check-in
&lt;/h2&gt;

&lt;p&gt;This wasn't like our regular weekly check-in meetings. It started about an hour later than usual because we had to accommodate Thibaud's schedule so he could walk Storm through the changes and they could deliberate on them. It was a somewhat lengthy session because we all had valid concerns. On the one hand, Thibaud wanted upgrades with the least friction for users, even if it meant compromising the initial goal. On the other hand, Storm and I wanted to do things the "right" way: making sure that the usability of alt text was not left to the chance that editors would modify their image title fields.&lt;/p&gt;

&lt;p&gt;At the same time, it would be fair to say that we all had our biases. Thibaud was speaking as a developer and editor who already had accessibility in mind in his daily work, while also working in a company where other people followed best practices. Storm was speaking as a developer who frequently worked with editors and had seen lots of people not bother to follow best practices. On my end, I was speaking as a developer from the perspective of a newcomer, who could easily see all the ways to do things "faster" without any extra overhead (even if the overhead would be a better way to do things). &lt;/p&gt;

&lt;p&gt;After going through the flowchart together and looking for what was best for our existing users, we decided to leave the defaults as they initially were. This would mean I would spend the week undoing changes I made in a previous week. It didn't sound exciting, but the work had to be done, nonetheless. &lt;/p&gt;

&lt;p&gt;This reminds me of Matthew Wescott's (Lead Wagtail Dev) video on &lt;a href="https://www.youtube.com/watch?v=v3KEaMTfKg0" rel="noopener noreferrer"&gt;the impossible art of pleasing everyone&lt;/a&gt;. The compromise wasn't exactly something I was absolutely sold on, but sometimes these things are out of our control, and the consequences of our actions would mean a good or bad experience for editors who use Wagtail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;I could write an entire book about all the challenges I faced, but for now I'll write a couple of paragraphs instead. I had an absolute nightmare with git. I also learned about a misconception I'd had about git reverts.&lt;/p&gt;

&lt;p&gt;Before I started the GSoC project, Wagtail's default to fallback to the title field in the absence of any alt text mechanism. There were also a series of tests to ensure this behaviour. However, because my mentors agreed earlier on that this would change, I edited all the pertinent tests to remove this behaviour. Several weeks later, I would have to bring them back. There were two ways I could do this: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I could look at the git diff from the current state of things and manually edit things.&lt;/li&gt;
&lt;li&gt;I could just "go back in time" and "stop myself" from making those changes (but to appear as an activity done in the future)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Believe it or not, the first process was easier, but I felt there would be a lot to learn from step 2 so I opted for that instead. This is where the chaos started. All along, I'd always thought that &lt;code&gt;git revert&lt;/code&gt; would alter things in the commit history so I deliberately avoided doing that. I had the very bright idea(you probably understand the sarcasm) of checking out to the older commit hash and manipulating things from there. This was uncharted territory for me so I decided to do all of this in a copy branch of the branch I was working on. That sole activity saved me.&lt;/p&gt;

&lt;p&gt;I checked out to the branch and then to the particular commit hash. I tried to undo a few changes and commit, but then I somehow messed with the history. I'm not even sure I can entirely explain what went wrong, but I kept getting a series of merge conflicts. At some point I made a mistake in one of the merges and had to undo that. Then I tried rebasing to fix that issue. At this point, I went from trying to get a small change done, to tirelessly bombarding ChatGPT with prompts, because I could no longer understand what I was doing. I ended up with about 13 commits for one simple task, and several merge conflicts that would always pop up any time I did a git pull. After hours on that &lt;a href="https://dictionary.cambridge.org/us/dictionary/english/hamster-wheel" rel="noopener noreferrer"&gt;hamster wheel&lt;/a&gt;, I decided to start afresh because I had made such an inexplicable mess of things. &lt;/p&gt;

&lt;p&gt;This time around, I started by outrightly asking ChatGPT what to do. It mentioned "reverting". This put me on high alert, but I decided to do some extra study and read more about git reverts. After reading a bunch of articles, it looked very okay (much to my surprise), and I decided to try it in a different branch. I deleted the one I had messed up and started again. I can tell you for a fact that I was surprised at how smooth everything went. It worked well, and I had zero issues. This, however, was only one part of the equation. The other part was to merge it to the actual branch I needed it to be on. I said a quiet prayer with the hopes that nothing would go wrong (because that was essentially several weeks of progress). Again, things worked, and I finally pushed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;In a nutshell, I learned a lot about how reverts, soft resets, and hard resets work. Let's just say I won't forget when to use them in a hurry. Experience really is the best teacher.&lt;/p&gt;

&lt;p&gt;I also learned the importance of always opening different branches to try out new things. There's a good chance I could have damaged my commit history if I didn't create that sandbox-like environment for myself. In a sense, that's the whole point of git: the power to break things to your heart's content without actually damaging anything. &lt;/p&gt;

&lt;p&gt;Finally, I'd say I learned to question my preconceived notions more often. Why did I feel so strongly about reverts when my knowledge was wrong? I can't quite say, but going forward, I'll be more open-minded about things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It was a chaotic week, but some level of chaos is required for some level of growth. Apologies if you religiously follow-up with this series and expected this to drop sooner. I'll do a much better job next time.&lt;/p&gt;

&lt;p&gt;Thank you for reading it this far.&lt;/p&gt;

&lt;p&gt;Till next time.&lt;br&gt;
Cheers. 🥂&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>gsoc</category>
      <category>googlesummerofcode</category>
      <category>wagtail</category>
    </item>
    <item>
      <title>GSoC Midterm Evaluation</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Mon, 15 Jul 2024 00:00:21 +0000</pubDate>
      <link>https://dev.to/chiemezuo/gsoc-midterm-evaluation-3i5o</link>
      <guid>https://dev.to/chiemezuo/gsoc-midterm-evaluation-3i5o</guid>
      <description>&lt;p&gt;This week was the week for both mentors and contributors to turn in their evaluations. Mentors would give feedback on the experience with mentees, and mentees would do the same for mentors. Spoiler alert: I passed mine at the end of the week. 🎉&lt;br&gt;
Let's get right to my summary of the week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekly Check-in
&lt;/h2&gt;

&lt;p&gt;My lead mentor (Storm) was on holiday, so Saptak led the session. We started with a rundown of the state of the project so far. I mentioned that the PR for the new &lt;code&gt;ImageBlock&lt;/code&gt; was in good shape, I was making some good findings on the AI stretch goal, the &lt;code&gt;image_description&lt;/code&gt; still needed extra feedback, and my documentation (as well as the release notes) were also coming up nicely.&lt;/p&gt;

&lt;p&gt;Saptak mentioned wanting to get the RFC moving faster so that we could get it approved/accepted by the core team, to meet the GSoC timeline. He mentioned he'd try to push it across to some other people to get more comments on it.&lt;/p&gt;

&lt;p&gt;We agreed that for the rest of the sprint, I would ideally try to get the PR ready for review again, and I would also get a proof of concept ready for the AI stretch. One of the most exciting parts of my Summer of Code was about to commence. The agreed progression was to start the AI project as a regular Django project, and then modify it to suit Wagtail's needs iteratively. I was asked not to worry too much about the UI, but to get the basics up and running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility Team Meeting
&lt;/h2&gt;

&lt;p&gt;A few minutes before the Accessibility team meeting, I got a message from &lt;a href="https://torchbox.com/about/team/thibaud-colas/" rel="noopener noreferrer"&gt;Thibaud&lt;/a&gt;. He had some concerns with the RFC and mentioned that he felt it required a bit more work. He wanted the RFC to be more detailed in covering the behavior for various use cases of the block, as well as how the changes would affect existing usage even when the block wasn't used. He mentioned the idea of developing a flowchart, which he eventually did with me after the Accessibility meeting, over a call. It was quite a long call, but I appreciated the time he put in, and we had a good discussion on what I could do to communicate this flowchart better in the RFC itself. It did mean I would have to go back to the drawing board, though.&lt;/p&gt;

&lt;p&gt;What was particularly a debating point (which still hasn't been resolved) was whether Wagtail should keep the existing behavior (of defaulting to image titles) in the absence of any form of alt text. Image titles are meant to be brief, and are usually whatever the image was saved as in the computer (unless the user changes them). In lots of cases, though, the titles make very bad alt text. With no alt text, Wagtail's Accessibility Checker would easily flag a problem. With bad alt text, the Accessibility Checker wouldn't find it. At the point of my writing this, there are modifications in the works to improve the Accessibility Checker's ability to spot bad alt text patterns, but I'm working in parallel because I'm not sure how soon it will be ready.&lt;/p&gt;

&lt;p&gt;Thibaud is of the opinion of falling back to titles, but my mentors and I are not. We will take it up with the accessibility team again, as well as the core team to gather overall thoughts. We will conclude in the coming weeks, though. &lt;/p&gt;

&lt;p&gt;The meeting ended with some potential UI features we could integrate if the GSoC timeline permits. It also meant that I wouldn't be able to mark the PR as finally ready for a review till the pain points were settled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;My biggest challenge came from trying to get the AI project prototype to work. This was largely because I didn't have any OpenAI access keys. I couldn't seem to get one from Thibaud earlier in the week so it halted my progress greatly. I set up the project template from the OpenAI docs I had read and tried to make sure that the only delays were from access keys.&lt;/p&gt;

&lt;p&gt;I was able to get access keys from a friend of mine, but it had to be during his office break hours before invalidation. It was better than nothing, and I used this up until I got the keys from Thibaud during the Accessibility meeting. That was my major challenge of the week. The next challenge was (and still is) trying to account for the discussions in the Accessibility team meeting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;I learned how to use OpenAI's API to take advantage of the multimodal features, I learned about some more use cases of images in models, and I learned how OpenAI works with images.&lt;/p&gt;

&lt;p&gt;I'd say my best learning moment came from drafting the Flowchart with Thibaud. It showed me my lapses in knowledge of Wagtail's image management, and it also tested how confident I was in the work I had done so far. These things make all the meaning to me, and I'm glad to be able to learn more (even if it means constantly going back to the drawing board).&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluation
&lt;/h2&gt;

&lt;p&gt;It was another great GSoC week, and I passed my midterm evaluations. I got great feedback, and I am excited about the remaining stretch of the program.&lt;/p&gt;

&lt;p&gt;Here's my feedback:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpi4exc0ks21mg6txtit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpi4exc0ks21mg6txtit.png" alt="Chiemezuo's Evaluation Feedback" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

&lt;p&gt;Cheers. 🥂&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>googlesummerofcode</category>
      <category>wagtail</category>
      <category>opensource</category>
    </item>
    <item>
      <title>GSoC Week 6</title>
      <dc:creator>Chiemezuo</dc:creator>
      <pubDate>Mon, 08 Jul 2024 17:34:36 +0000</pubDate>
      <link>https://dev.to/chiemezuo/gsoc-week-6-389d</link>
      <guid>https://dev.to/chiemezuo/gsoc-week-6-389d</guid>
      <description>&lt;p&gt;Documentation is one of the pillars of good software. We often forget to do it because it comes in as a 'secondary' consideration. My theme for week 6 was 'documenting my features properly', and it was what I did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekly Check-in
&lt;/h2&gt;

&lt;p&gt;We had a lengthy check-in session, especially because my lead mentor Storm was starting a small holiday this week. The topics, however, revolved around putting the finishing touches on pending tasks so that we could progress to the stretch goal (AI). We agreed on a follow-up meeting to discuss the AI requirements on another day.&lt;/p&gt;

&lt;p&gt;Among some of the finishing touches discussed were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Drafting the upgrade considerations that would added to the release notes for whichever new version of Wagtail the feature would fall under.&lt;/li&gt;
&lt;li&gt;Updating Wagtail's documentation. In the examples, I would have to replace instances of &lt;code&gt;ImageChooserBlock&lt;/code&gt; with &lt;code&gt;ImageBlock&lt;/code&gt;, and I would also have to update the &lt;code&gt;StreamField&lt;/code&gt; reference guide to show the new block.&lt;/li&gt;
&lt;li&gt;Following up with my feedback request on the &lt;code&gt;image description&lt;/code&gt; field warning labels.&lt;/li&gt;
&lt;li&gt;Making the social media RFC post.&lt;/li&gt;
&lt;li&gt;Iterating to make &lt;code&gt;ImageBlock&lt;/code&gt; perfectly swappable where &lt;code&gt;ImageChooserBlock&lt;/code&gt; has already been used, without any data migrations.&lt;/li&gt;
&lt;li&gt;Going through fresh comments on the RFC.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We structured the next sprint from the outcomes of our meeting discussions.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Follow-up Meeting
&lt;/h2&gt;

&lt;p&gt;On Wednesday, we discussed the rough (proof of concept) implementation and how to get started. We agreed that our first AI model would be the same as what we used for our research on alt text: OpenAI. We discussed what the contract should look like between our service and the APIs we would use. &lt;/p&gt;

&lt;p&gt;My mentors were of the idea that I would start the implementation as a simple Python project, and after getting things to work, we would progress to modifying it to be an extension of Wagtail. The primary focus, however, was on getting the feature set to work. They both gave me some pointers on things to look out for and things to try. We also realized some more questions that would pop up much later in the Wagtail implementation, such as whether to generate many different results with the same prompts at once vs just one result (with the option to trigger for more). There was also the question of whether we would make a UI option to allow editors to modify the existing prompts, or whether to let it be site-wide.&lt;/p&gt;

&lt;p&gt;There was also the question of an RFC for the new feature, but we agreed that since we weren't sure of the direction or the complete implementation, we would build iteratively until we had enough knowledge of what to include/exclude from the RFC, as well as questions that may come up later on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;I had a couple of challenges, but I scaled through them like a superhero. The first challenge was that I wanted &lt;code&gt;ImageBlock&lt;/code&gt; to be a perfect replacement for &lt;code&gt;ImageChooserBlock&lt;/code&gt;. In Wagtail, when you want to change the block used, you will very often have to do a data migration. It can be an annoying step, and in some cases might prevent people from upgrading right away, or from changing to the new way of doing things (especially since &lt;code&gt;ImageChooserBlock&lt;/code&gt; wouldn't be deprecated). The all-encompassing goal of my GSoC project is to get Wagtail editors to follow best image accessibility practices, and the easier I can make it, the better the chances. I had the ambitious goal of erasing the data migration step.&lt;/p&gt;

&lt;p&gt;To do this, I had to modify the new &lt;code&gt;ImageBlock&lt;/code&gt;'s &lt;code&gt;to_python&lt;/code&gt; and &lt;code&gt;bulk_to_python&lt;/code&gt; to be able to receive data in the same format that &lt;code&gt;ImageChooserBlock&lt;/code&gt; would have received it. The solution involved me adding some conditionals to the methods, and they would check if the received value was an instance of &lt;code&gt;int&lt;/code&gt; (&lt;code&gt;image_id&lt;/code&gt;). If it were, I would retrieve the image object, and then I would create a &lt;code&gt;dict&lt;/code&gt; with the image property, and two other fields for &lt;code&gt;decorative&lt;/code&gt; and &lt;code&gt;alt_text&lt;/code&gt; and default them to &lt;code&gt;False&lt;/code&gt; and an empty string respectively, before calling the next method. Writing the tests for this was another big of warfare, but I got things working.&lt;/p&gt;

&lt;p&gt;My last notable challenge was trying to draft upgrade considerations and update the documentation. I had never written upgrade considerations and therefore had to go through some from previous release notes. I did this as a Google Doc draft and got Storm to review it. Afterward, I reached out to Wagtail's lead dev (I'll get permission to post his name here soon) for guidance on what part of the Pull Request to introduce it into, and he gave me feedback. I also asked him if it was okay for me to replace &lt;code&gt;ImageChooserBlock&lt;/code&gt; with &lt;code&gt;ImageBlock&lt;/code&gt; in the documentation examples it appeared in, as well as any extra pointers to look for. He was perfectly fine with it, and I spent the rest of my week working on docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;I once again found myself in a situation where I wasn't entirely sure of the expected data to be received from a block, but I used the &lt;em&gt;SQLite Browser&lt;/em&gt; to inspect what it would look like. Another block was added to my list of competencies. 😅&lt;/p&gt;

&lt;p&gt;I learned the process of prepping documentation for features, as well as some little bits of the &lt;code&gt;sphinx&lt;/code&gt; tool for documentation in Python projects. I'll also add that modifying the &lt;code&gt;to_python&lt;/code&gt; and &lt;code&gt;bulk_to_python&lt;/code&gt; methods was a huge learning experience for me, especially concerning how it handled expected data.&lt;/p&gt;

&lt;p&gt;I'm getting to the point where I have an idea of where anything I'm looking for in the codebase would probably be found. &lt;/p&gt;

&lt;p&gt;This was another great week, and a big thanks to you for reading thus far.&lt;/p&gt;

&lt;p&gt;Cheers. 🥂&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>googlesummerofcode</category>
      <category>wagtail</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
