<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Programming My Life</title><link href="https://programmingmylife.com/" rel="alternate"></link><link href="https://programmingmylife.com/feeds/all.atom.xml" rel="self"></link><id>https://programmingmylife.com/</id><updated>2025-03-07T08:40:06+00:00</updated><entry><title>Django Form Basics</title><link href="https://programmingmylife.com/2025-03-07-django-form-basics.html" rel="alternate"></link><published>2025-03-07T08:40:06+00:00</published><updated>2025-03-07T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2025-03-07:/2025-03-07-django-form-basics.html</id><summary type="html">&lt;p&gt;Despite using Django for a number of years, I haven’t really worked with Django’s Forms until this week. I needed to create a form to handle a file upload, which has an associated category. The form also had to allow users to create new categories from within the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Despite using Django for a number of years, I haven’t really worked with Django’s Forms until this week. I needed to create a form to handle a file upload, which has an associated category. The form also had to allow users to create new categories from within the file. When creating a new category, we have some extra data we want associated with the new category. I learned a few things trying to set this up.&lt;/p&gt;
&lt;p&gt;First, &lt;a href="https://docs.djangoproject.com/en/5.1/topics/forms/modelforms/"&gt;forms.ModelForm&lt;/a&gt;: The fields on this form don't have to map 1-1 to the model. The upload model has a category field, but we need some extra fields on the form when we create new categories that don't exist on the upload model. No problem! I thought I might have to fall back to a standard form since I needed extra fields, but I was able to add them to the form and assign them to the category in the view.&lt;/p&gt;
&lt;p&gt;Also, to break up and style different fields in a form, you can individually identify the fields in the template file and style them. So instead of&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;formid&lt;/span&gt;&lt;span class="err"&gt;=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;id_upload_form&lt;/span&gt;&lt;span class="err"&gt;&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{% url &amp;#39;app_name:upload_file&amp;#39; %}&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;post&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;enctype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;multipart/form-data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  {% csrf_token %}
  {{ form|crispy }}
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;submit&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;idupload_form_submit&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;btn btn-primary mt-4 disabled&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Upload&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We have:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;formid&lt;/span&gt;&lt;span class="err"&gt;=&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;id_upload_form&lt;/span&gt;&lt;span class="err"&gt;&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{% url &amp;#39;app_name:upload_file&amp;#39; %}&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;post&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;enctype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;multipart/form-data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  {% csrf_token %}

  {{ form.file|as_crispy_field }}
  {{ form.category|as_crispy_field }}
  {{ form.new_category_sources|as_crispy_field }}
  {{ form.new_category_display_name|as_crispy_field }}

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;submit&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;idupload_form_submit&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;btn btn-primary mt-4 disabled&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Upload&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This allowed me to wrap the bottom two fields in a &lt;a href="https://getbootstrap.com/docs/5.0/components/accordion/"&gt;Boostrap accordion&lt;/a&gt; in order to hide them from users who are updating a category instead of creating one.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;accordion&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;accordionExample&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;accordion-item&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;accordion-header&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;headingOne&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;accordion-button collapsed&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;button&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;data-bs-toggle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;collapse&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;data-bs-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#collapseOne&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;aria-expanded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;false&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;aria-controls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;collapseOne&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Create New Category
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;collapseOne&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;accordion-collapse collapse&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;headingOne&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;data-bs-parent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#accordionExample&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;accordion-body&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        {{ form.new_category_sources|as_crispy_field }}
        {{ form.new_category_display_name|as_crispy_field }}
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We already have bootstrap as a dependency, so I like this solution better than adding JavaScript, which I also saw as a way to solve this problem.&lt;/p&gt;</content><category term="django"></category><category term="django"></category></entry><entry><title>Pytest, A Practical Guide</title><link href="https://programmingmylife.com/2025-02-07-pytest-a-practical-guide.html" rel="alternate"></link><published>2025-02-07T08:40:06+00:00</published><updated>2025-02-07T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2025-02-07:/2025-02-07-pytest-a-practical-guide.html</id><summary type="html">&lt;p&gt;At a local Python meetup, there were several attendees that weren't familiar with &lt;code&gt;pytest&lt;/code&gt;, which gave me the idea to make this post as a primer on how I use &lt;code&gt;pytest&lt;/code&gt;. This isn't going to be a comprehensive review of &lt;code&gt;pytest&lt;/code&gt; and how to use it. For that, pick up …&lt;/p&gt;</summary><content type="html">&lt;p&gt;At a local Python meetup, there were several attendees that weren't familiar with &lt;code&gt;pytest&lt;/code&gt;, which gave me the idea to make this post as a primer on how I use &lt;code&gt;pytest&lt;/code&gt;. This isn't going to be a comprehensive review of &lt;code&gt;pytest&lt;/code&gt; and how to use it. For that, pick up &lt;a href="https://pythontest.com/pytest-book/"&gt;Brian Okken's book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This post is for someone with knowledge of Python who is maybe just starting to use &lt;code&gt;pytest&lt;/code&gt;, or has used it for a while to run tests but hasn't dug into its features. What I want to cover is some of the features of &lt;code&gt;pytest&lt;/code&gt; that can help improve your tests and running them. I also want to talk about how to think about &lt;code&gt;pytest&lt;/code&gt; because it has some features that are very powerful, but can be confusing if you don't know where to look.&lt;/p&gt;
&lt;h3&gt;Fixtures&lt;/h3&gt;
&lt;p&gt;These are a great feature of &lt;code&gt;pytest&lt;/code&gt; that can cause some initial confusion.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;first_entry&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_first_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_entry&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;first_entry&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;@pytest.fixture&lt;/code&gt; is a marker (more about that below) that identifies the function &lt;code&gt;first_entry&lt;/code&gt; as a fixture. It is imported into &lt;code&gt;test_first_entry&lt;/code&gt; as a parameter by name.&lt;/p&gt;
&lt;p&gt;This is an oversimplified example to illustrate how fixtures work. I've simplified here because what you might find in a real world project is several fixtures being imported into a test where some are defined a few hundred lines above the function, and some might even be defined in another file (see next section). The power of fixtures comes when you need to define variables that will be reused across a number of tests, either in a single file or across the application.&lt;/p&gt;
&lt;p&gt;A lot more about fixtures are in the &lt;a href="https://docs.pytest.org/en/7.1.x/how-to/fixtures.html"&gt;docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;conftest.py&lt;/h2&gt;
&lt;p&gt;This is a common filename you will see in projects using &lt;code&gt;pytest&lt;/code&gt;. It's a great place to store fixtures and other configuration that will be reused across an entire application (or a section of the app). It's a very helpful pattern, but I've found that it can lead to debugging trouble in larger projects. How so? Two ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If you aren't aware that &lt;code&gt;conftest.py&lt;/code&gt; exists, you might not be able to track down where something (usually a fixture) is defined. &lt;/li&gt;
&lt;li&gt;If you have more than one &lt;code&gt;conftest.py&lt;/code&gt; (this is possible and can be a good idea in larger projects), it can also be hard to track down which file things are defined in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The other reason these are problems is that not all IDEs are able to figure out where fixtures are defined, which means you can't right-click and view their definition, like you can with most objects.&lt;/p&gt;
&lt;p&gt;The way I work around this is to use simple project wide string search (&lt;code&gt;ctrl-shift-f&lt;/code&gt; or &lt;code&gt;cmd-shift-f&lt;/code&gt; in vscode) when I'm looking for fixtures.&lt;/p&gt;
&lt;h2&gt;Flags&lt;/h2&gt;
&lt;p&gt;Executing &lt;code&gt;pytest&lt;/code&gt; on the command line will run your entire test suite, but I almost never use that command alone. Instead, I use a range of different flags depending on how I am running my tests. Here are the flags I use most often and when I use them.&lt;/p&gt;
&lt;h4&gt;-n auto&lt;/h4&gt;
&lt;p&gt;By default, &lt;code&gt;pytest&lt;/code&gt; runs on a single core. If you install &lt;code&gt;pytest-xdist&lt;/code&gt; alongside &lt;code&gt;pytest&lt;/code&gt; and execute &lt;code&gt;pytest -n auto&lt;/code&gt;, it runs on all of the cores on your machine. I use this almost every time I run &lt;code&gt;pytest&lt;/code&gt;. The only exception is when I'm running a few tests because the overhead isn't worth it in those cases. You can replace &lt;code&gt;auto&lt;/code&gt; with a specific number of cores, but I've never had a good reason to do that. Another advantage of running tests this way is catching some flaky tests that might not have cleaned up properly or are not properly isolated.&lt;/p&gt;
&lt;h4&gt;-x&lt;/h4&gt;
&lt;p&gt;This stops the test run on the first failure. I use this when I'm running more than one test and I think there is a likelihood that one or more might fail (usually when working through refactoring). Or, if I'm having an issue with my setup, and I think there is a possibility of an error causing many tests to fail. I want the test suite to stop on the first one so I can more easily read the output.&lt;/p&gt;
&lt;h4&gt;--lf&lt;/h4&gt;
&lt;p&gt;This runs your last failed tests. I use this when I'm fixing a series of tests and I don't want to wait for the rest of the test suite to run. It can also be helpful if you've broken a handful of tests to work through each one until none fail.&lt;/p&gt;
&lt;h4&gt;-k name_of_test&lt;/h4&gt;
&lt;p&gt;This allows you to select a subset of tests. I often use this to run a single test or test file. Note that in larger test suites (thousands, not hundreds of tests) this can take (significant) time to collect. In that case, it's good to use &lt;code&gt;-k&lt;/code&gt; to find the test once, then if you are re-running the test a bunch of times, use the full path to the test. You can find the full path by forcing a failure in the test. I do this sometimes when I forget the path syntax which is &lt;code&gt;/path/to/test.py::test_name&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;-s&lt;/h4&gt;
&lt;p&gt;This stops &lt;code&gt;pytest&lt;/code&gt; from capturing standard output. It's useful if I need to print something in a test. I try to not do that in favor of using a breakpoint, but sometimes I find this useful.&lt;/p&gt;
&lt;h4&gt;--pdb&lt;/h4&gt;
&lt;p&gt;This opens a debugger at the line where a test fails. I should probably use this more often than I do, but I tend to use &lt;code&gt;-x&lt;/code&gt;, then introduce a &lt;code&gt;breakpoint()&lt;/code&gt;. I did find this useful recently, though. It can be helpful when you're having trouble knowing where a test is going to fail and you know you'll need to poke around at some values when it does.&lt;/p&gt;
&lt;h4&gt;--reuse-db&lt;/h4&gt;
&lt;p&gt;This is Django specific (I think) and is great for speeding up your tests in most test runs, but I've run into issues when new migrations are introduced. This is partially specific to my setup because I added this to my &lt;code&gt;pytest.ini&lt;/code&gt;, forgot about it, then realized the errors I was getting was because the test database was in a bad state. Once you've done this a few times, you won't forget! This can also happen if you use it by default. If you start getting unexpected testing errors, try running without it. I tend to not use it the first time I run the test suite after pulling down changes, then use it throughout the day that I'm running my tests.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://adamj.eu/tech/2019/10/03/my-most-used-pytest-commandline-flags/"&gt;Adam Johnson wrote about his most used flags&lt;/a&gt;, which has some similarities and differences with my list.&lt;/p&gt;
&lt;h2&gt;Related Concepts&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;coverage.py&lt;/code&gt; - this is a great package to use with &lt;code&gt;pytest&lt;/code&gt; that tells you how much of your code is covered by tests. This can be very helpful when getting started with a test suite, or when developing new code to ensure you have tested all possibilities. Get started by installing &lt;code&gt;pytest-cov&lt;/code&gt; then run with &lt;code&gt;pytest --cov appname&lt;/code&gt;. On some projects, I add coverage to my defaults so I don't have to remember to use all of the flags. On larger projects, it can be a bit too much output and I use &lt;a href="https://github.com/marketplace/actions/python-coverage-comment"&gt;this Github Action&lt;/a&gt; instead.&lt;/p&gt;
&lt;p&gt;Markers - we've already seen these with &lt;code&gt;@pytest.fixture&lt;/code&gt; above. These are decorators unique to &lt;code&gt;pytest&lt;/code&gt;. There are some good &lt;a href="https://docs.pytest.org/en/stable/reference/reference.html#marks"&gt;built in markers&lt;/a&gt;, and you can also &lt;a href="https://docs.pytest.org/en/stable/example/markers.html"&gt;define your own&lt;/a&gt;, which can be helpful for separating different types of tests (slower vs faster, for example). &lt;/p&gt;
&lt;p&gt;&lt;code&gt;@pytest.mark.xfail&lt;/code&gt; - A good marker for bugs you know you need to fix. I have also used it for failures we knew would be fixed in future versions of external libraries. That way, we'd see the test pass with the new version, which would show up as a failure in the test run, notifying us to remove the mark, and make any necessary code changes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@pytest.mark.skip&lt;/code&gt; - Another useful marker. This one is a bit like commenting out code, but can be useful for keeping tests around for a short time until they can be updated to no longer require skipping. As such, I use this sparingly.&lt;/p&gt;
&lt;p&gt;parametrization - using a marker, you can run a single test with a number of input parameters. This is very helpful for not repeating test code when the only difference is the input. More information in the &lt;a href="https://docs.pytest.org/en/7.1.x/how-to/parametrize.html#parametrize-basics"&gt;docs&lt;/a&gt;. Unfortunately, there is not a built in way to parametrize fixtures. Fortunately, I have a &lt;a href="https://programmingmylife.com/2024-02-08-til-using-fixtures-with-pytest-parametrize.html"&gt;blog post about how to do it&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;With Django&lt;/h3&gt;
&lt;p&gt;If you are developing a Django application, &lt;code&gt;pytest-django&lt;/code&gt; has a lot of nice features for using &lt;code&gt;pytest&lt;/code&gt; with Django.&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;I hope this has been helpful in highlighting some useful features of &lt;code&gt;pytest&lt;/code&gt; as well as some notable places that you might trip up while using it. Let me know if you learned something new!&lt;/p&gt;</content><category term="python"></category><category term="python"></category><category term="pytest"></category><category term="django"></category></entry><entry><title>Coffee</title><link href="https://programmingmylife.com/2025-01-28-coffee.html" rel="alternate"></link><published>2025-01-28T08:40:06+00:00</published><updated>2025-01-28T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2025-01-28:/2025-01-28-coffee.html</id><summary type="html">&lt;p&gt;I've been a big fan of coffee for many years, but it's only in the last few that I've really started diving deeper on coffee and its preparation. I don't have an espresso machine, so most of my consumption at home is prepared as a pour over.&lt;/p&gt;
&lt;p&gt;I've almost always …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been a big fan of coffee for many years, but it's only in the last few that I've really started diving deeper on coffee and its preparation. I don't have an espresso machine, so most of my consumption at home is prepared as a pour over.&lt;/p&gt;
&lt;p&gt;I've almost always stuck to two cups per day: one in the morning, and one just after lunch. To make my coffee, I've used a wide range of coffee devices, from a Keurig, to a Chemex, to a Moka Pot, to a standard drip brewing machine (and more). For the past couple of years, though, I've used a &lt;a href="https://www.hario-usa.com/products/v60-ceramic-coffee-dripper-02-classic"&gt;Hario V60&lt;/a&gt; for most of my coffee brewing. I made and bought some cold brew in the summer, but most days I use my V60 to make both of my cups. I also have an &lt;a href="https://www.orea.uk/orea-brewer-v3"&gt;Orea V3&lt;/a&gt;, which highlights slightly different flavors than the V60.&lt;/p&gt;
&lt;p&gt;Now, when I get a new bag of beans I tend to try it with the V60 for a cup or two, then the Orea for a cup or two (or vice versa, the order is random). It has been fun to try new coffees in both brewers and see which I prefer. Then I'll change my grind size or temperature a bit to dial it in (if I think it's necessary). This has really given me a better appreciation of coffee!&lt;/p&gt;
&lt;h3&gt;Coffee flavor preferences&lt;/h3&gt;
&lt;p&gt;My favorite cups are light roasted, washed coffees with some sweetness and fruit flavor. One of my recent favorites was from grower Jose Salazer roasted by &lt;a href="https://passengercoffee.com"&gt;Passenger Coffee&lt;/a&gt; which tastes a bit like peach tea. It has a great balance of sweetness and coffee flavor. I've also recently found some naturally processed beans I really enjoyed from &lt;a href="https://littlewaves.coffee"&gt;Little Waves Coffee&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Similar to changing the brewer above, I've found that experimenting with grind size and water temperature really enhanced my enjoyment of naturally processed coffees. When using too fine a grind and too high temperature (both more suitable for lighter roasted, washed coffees), I was getting too much of the fermented/boozy flavors common to naturals. Using a courser grind and lower temperature water yields great fruit flavors with less of those fermented notes I don't like as much.&lt;/p&gt;
&lt;p&gt;I tend to not prefer coffees with floral tasting notes. If a coffee comes highly recommended and has floral tasting notes that are supposed to be subtle, I'll try it. But if a coffee is dominated by floral notes, I tend to avoid them. The exception to this is a good Gesha. I don't get Gesha beans often because they are expensive, but I've had a couple that are very good, interesting and complex.&lt;/p&gt;
&lt;p&gt;If you want to chat coffee, please reach out to me!&lt;/p&gt;</content><category term="coffee"></category><category term="coffee"></category></entry><entry><title>[TIL] Killing Long Running Postgres Queries</title><link href="https://programmingmylife.com/2025-01-17-til-killing-long-running-postgres-queries.html" rel="alternate"></link><published>2025-01-17T08:40:06+00:00</published><updated>2025-01-17T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2025-01-17:/2025-01-17-til-killing-long-running-postgres-queries.html</id><summary type="html">&lt;p&gt;I recently had issues with long running postgres operations and found these two commands, one for finding long running queries, and one for killing them. My issue turned out to be with an &lt;code&gt;UPDATE&lt;/code&gt; instead of a query, but I'm noting this for future use. Note that the interval can …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently had issues with long running postgres operations and found these two commands, one for finding long running queries, and one for killing them. My issue turned out to be with an &lt;code&gt;UPDATE&lt;/code&gt; instead of a query, but I'm noting this for future use. Note that the interval can be updated in this query to be more flexible for shorter/longer time frames.&lt;/p&gt;
&lt;h3&gt;Finding Long Running Queries&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;usename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;datname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;running_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;active&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;interval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;5 minutes&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;running_time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Killing Long Running Queries&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_terminate_backend&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;pid&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="postgres"></category><category term="postgres"></category><category term="til"></category></entry><entry><title>[TIL] Shell Alias for Failing Commands</title><link href="https://programmingmylife.com/2024-05-03-til-shell-alias-for-failing-commands.html" rel="alternate"></link><published>2024-05-03T08:40:06+00:00</published><updated>2024-05-03T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2024-05-03:/2024-05-03-til-shell-alias-for-failing-commands.html</id><summary type="html">&lt;p&gt;TL;DR: Instead of &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; use &lt;code&gt;;&lt;/code&gt; after a command that might have errors.&lt;/p&gt;
&lt;p&gt;My main project at work has a nice Make command for getting a new truncated version of our main database. Unfortunately, I can never remember the exact command, and it requires a second command (&lt;code&gt;pg_restore&lt;/code&gt;) to update my …&lt;/p&gt;</summary><content type="html">&lt;p&gt;TL;DR: Instead of &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; use &lt;code&gt;;&lt;/code&gt; after a command that might have errors.&lt;/p&gt;
&lt;p&gt;My main project at work has a nice Make command for getting a new truncated version of our main database. Unfortunately, I can never remember the exact command, and it requires a second command (&lt;code&gt;pg_restore&lt;/code&gt;) to update my local database. It occurred to me that an alias would go a long way to making this easier for me.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;new_db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;make get_new_db &amp;amp;&amp;amp; pg_restore other_args&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This was already a big win because now I don't have to wait for the database to download before executing the &lt;code&gt;pg_restore&lt;/code&gt; command, which also takes some time. Now I can call the alias and let it run until I have an updated database with no more input.&lt;/p&gt;
&lt;p&gt;Recently, I also realized that we have a nice management command that resets all local passwords, so you can set a simpler password locally than for production, so I tried to add that to my alias:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;new_db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;make get_new_db &amp;amp;&amp;amp; pg_restore other_args &amp;amp;&amp;amp; python manage.py set_pw&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Executing &lt;code&gt;new_db&lt;/code&gt; still works, but the final command doesn't run. I failed to find a solution with some basic searching, but a coworker pointed out that the &lt;code&gt;pg_restore&lt;/code&gt; command usually has some (expected) errors. The &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; operator expects the previous command to have an exit code of 0, which this does not. In order to run a command after command with errors, you can use the &lt;code&gt;;&lt;/code&gt; operator so that command becomes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;new_db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;make get_new_db &amp;amp;&amp;amp; pg_restore other_args ; python manage.py set_pw&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And now all of it runs!&lt;/p&gt;</content><category term="Shell"></category><category term="bash"></category><category term="til"></category></entry><entry><title>TIL: Docker Commands</title><link href="https://programmingmylife.com/2024-04-25-til-docker-commands.html" rel="alternate"></link><published>2024-04-25T08:40:06+00:00</published><updated>2024-04-25T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2024-04-25:/2024-04-25-til-docker-commands.html</id><summary type="html">&lt;p&gt;I set up my main project at work to utilize &lt;a href="https://www.localstack.cloud/"&gt;LocalStack&lt;/a&gt; this week. I wasn't able to get it working using a virtual environment with Python, but another project at work has it set up through Docker. A coworker that is much more familiar with Docker than I am helped …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I set up my main project at work to utilize &lt;a href="https://www.localstack.cloud/"&gt;LocalStack&lt;/a&gt; this week. I wasn't able to get it working using a virtual environment with Python, but another project at work has it set up through Docker. A coworker that is much more familiar with Docker than I am helped me get set up. Along the way, I saved these Docker commands that have already served me pretty well in debugging/figuring out what's going on in my containers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;compose&lt;span class="w"&gt; &lt;/span&gt;logs&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;localstack&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# see the logs for the localstack container&lt;/span&gt;

docker&lt;span class="w"&gt; &lt;/span&gt;compose&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;localstack&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# -d flag is for daemon which runs in the background. Can be useful to omit to see if something is failing on startup&lt;/span&gt;

docker&lt;span class="w"&gt; &lt;/span&gt;compose&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;--build&lt;span class="w"&gt; &lt;/span&gt;web&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# force a rebuild&lt;/span&gt;

docker&lt;span class="w"&gt; &lt;/span&gt;compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;web&lt;span class="w"&gt; &lt;/span&gt;bash&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# go into shell on the web docker container&lt;/span&gt;

docker&lt;span class="w"&gt; &lt;/span&gt;compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;localstack&lt;span class="w"&gt; &lt;/span&gt;awslocal&lt;span class="w"&gt; &lt;/span&gt;s3&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;s3://localstack-s3&lt;span class="w"&gt; &lt;/span&gt;--recursive&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# view files in the bucket `s3://localstack-s3` in the `localstack` container, recursively.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="TIL"></category><category term="til"></category><category term="python"></category><category term="docker"></category><category term="aws"></category></entry><entry><title>Mocking in Python tests</title><link href="https://programmingmylife.com/2024-03-25-mocking-in-python-tests.html" rel="alternate"></link><published>2024-03-25T08:40:06+00:00</published><updated>2024-03-25T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2024-03-25:/2024-03-25-mocking-in-python-tests.html</id><summary type="html">&lt;p&gt;For a while now, I've felt unsure about mocking in Python. It has helped me test in the past, but I've also seen a lot of smart people talk or write about its pitfalls.&lt;/p&gt;
&lt;p&gt;Recently, I had to test some functionality around calls to Twilio, so I decided to investigate …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For a while now, I've felt unsure about mocking in Python. It has helped me test in the past, but I've also seen a lot of smart people talk or write about its pitfalls.&lt;/p&gt;
&lt;p&gt;Recently, I had to test some functionality around calls to Twilio, so I decided to investigate to see if I could get a better understanding of the tradeoffs involved.&lt;/p&gt;
&lt;h2&gt;Blog posts and Conference Talks&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.cosmicpython.com/blog/2020-01-25-testing_external_api_calls.html"&gt;This post&lt;/a&gt; really clarified things for me. Particularly this part following a section setting up some mocks for testing a couple of API calls (emphasis mine):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;And you can imagine adding a few more tests, perhaps one that checks that we do the date-to-isoformat conversion correctly, maybe one that checks we can handle multiple lines. Three tests, one mock each, we’re ok.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The trouble is that it never stays quite that simple does it?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It hit me that the confusion I had with all of these blog posts is that the times I've used mocks, it &lt;em&gt;has&lt;/em&gt; stayed that simple (I've also used them in other cases that provide value, but more on that later). This isn't to say that post is wrong. In fact, I agree with the author's point that when things get more complicated, mocks can cause trouble.&lt;/p&gt;
&lt;p&gt;Another great example of this is an &lt;a href="https://www.youtube.com/watch?v=Xu5EhKVZdV8"&gt;old (but not outdated) talk from Pycon 2012&lt;/a&gt; in which two engineers from Google talk about how they evolved their codebase to deal with issues caused by large numbers of mocks spread across a large codebase. Ned Batchelder has a good summary of the talk &lt;a href="https://nedbatchelder.com/blog/201206/tldw_stop_mocking_start_testing.html"&gt;here&lt;/a&gt;. If you are having trouble with mocks already, or the number of mocks in your test suite is growing, this is a great talk to see some possible solutions.&lt;/p&gt;
&lt;p&gt;Hynek Schlawack has an &lt;a href="https://hynek.me/articles/what-to-mock-in-5-mins/"&gt;interesting post&lt;/a&gt; about mocking (with a lot of great links for further reading) that covers another pattern I've used: instead of mocking the service you are calling, wrap the call(s) to that service in a function so you can more easily control it in tests. I've found this pattern especially helpful if you are calling a service with different sets of parameters.&lt;/p&gt;
&lt;p&gt;I &lt;a href="https://programmingmylife.com/2019-04-13-aws-presigned-urls-in-django.html"&gt;wrote about AWS S3's presigned URL API&lt;/a&gt; a few years ago. Back then I only had one way to call the function, as you can see in the example code. Since then, I've added a few files to download, so I'm now calling that API with a few different parameters. I wrapped that API call in a helper function to make testing easier and more robust.&lt;/p&gt;
&lt;h2&gt;Conclusion for External Web APIs&lt;/h2&gt;
&lt;p&gt;What I've taken away from all of this is that mocks are like most solutions in technology: they are not a panacea. But they also aren't worthless or always bad. I think Microservices are an appropriate analogy here. They can solve some interesting problems that come with scaling (headcount as well as requests), but their existence doesn't make a monolithic codebase a bad choice. So the next time you are looking to mock object(s) in Python, consider how much you need to mock. Depending on your answer to that, mocks might be helpful, or you might want to look into one of the solutions above.&lt;/p&gt;
&lt;h2&gt;Use Cases Beyond Web APIs&lt;/h2&gt;
&lt;p&gt;A lot of the discussion I see around mocks is about mocking external services so you aren't reaching out to a third party service in your tests. But there is another use case I found a lot of value in: skipping long running algorithms to test the code around it. In a previous job, I helped write a library for machine learning functions, a lot of which were machine learning algorithms that took significant time to run (especially compared to the time we'd like for a test to run). Mocking these allowed us to separate our test suite into unit tests that allowed us to test the code around these long running functions (that we ran on CI) and longer running tests that we ran locally and in nightly test runs. This is another good use case for mocks.&lt;/p&gt;</content><category term="Python"></category><category term="python"></category><category term="testing"></category></entry><entry><title>Django Tasks - Cronjobs vs Celery</title><link href="https://programmingmylife.com/2024-03-01-django-tasks-cronjobs-vs-celery.html" rel="alternate"></link><published>2024-03-01T08:40:06+00:00</published><updated>2024-03-01T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2024-03-01:/2024-03-01-django-tasks-cronjobs-vs-celery.html</id><summary type="html">&lt;p&gt;Like &lt;a href="https://programmingmylife.com/2023-06-15-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-django-vs-django-rest-framework-drf.html"&gt;my previous vs post&lt;/a&gt;, this isn't a showdown. Instead, this post is about matching the solution to the problem. The tl;dr is that Celery is great at what it does, but it requires more infrastructure and setup than I needed to solve my problem. &lt;/p&gt;
&lt;h3&gt;The Problem to Solve …&lt;/h3&gt;</summary><content type="html">&lt;p&gt;Like &lt;a href="https://programmingmylife.com/2023-06-15-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-django-vs-django-rest-framework-drf.html"&gt;my previous vs post&lt;/a&gt;, this isn't a showdown. Instead, this post is about matching the solution to the problem. The tl;dr is that Celery is great at what it does, but it requires more infrastructure and setup than I needed to solve my problem. &lt;/p&gt;
&lt;h3&gt;The Problem to Solve&lt;/h3&gt;
&lt;p&gt;I needed to sync data from my database to &lt;a href="https://www.instructure.com/canvas"&gt;Canvas&lt;/a&gt;, a learning management system where instructors store grades. If I had a year to do this, I would send the data directly to Canvas as it comes to my server. Unfortunately, I had a much more limited time frame to get this done, and that solution would require a lot more testing and robust error handling in cases where the data does not go through.&lt;/p&gt;
&lt;p&gt;Before starting, I knew Celery was a common solution for task based workflows in Django, but I wanted to see what other options there were and how they stack up. I did some research and posted &lt;a href="https://fosstodon.org/@programmylife/109501517569062606"&gt;this&lt;/a&gt; to see if there was anything I missed in my research, which got some helpful responses.&lt;/p&gt;
&lt;p&gt;Based on those responses, I started looking into setting up cron jobs to sync the data. I decided that Celery seems to be a great product that handles a lot of complexity for tasks (long and short running), but it was more effort to set up and maintain than I needed. &lt;/p&gt;
&lt;h3&gt;Management Commands and Cron Jobs&lt;/h3&gt;
&lt;p&gt;I created management commands to nail down the code while I looked into libraries for setting up cron jobs with Django. Several were no longer supported, and I didn't find any that I felt confident in. I began looking into how to set the cron jobs up myself in the packer scripts I already had for deployments. Fortunately, it was pretty easy. I created a shell script for each cron job that activates the proper environment on the server (to get the appropriate context to connect to my database and Django application) then runs a management command. These are copied by packer onto the server instance. I referenced those scripts in &lt;code&gt;cronjob.sh&lt;/code&gt; which &lt;code&gt;packer&lt;/code&gt; executes while setting up the server to set up the cron jobs. It looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;...
&lt;span class="o"&gt;(&lt;/span&gt;crontab&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;11 * * * * /opt/website/cron_canvas_user_sync.sh &amp;gt;&amp;gt; /opt/website/logs/cron_users.log 2&amp;gt;&amp;amp;1&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;crontab&lt;span class="w"&gt; &lt;/span&gt;-
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Explaining cron syntax is outside the scope of this post, mostly because I don't entirely understand it. &lt;a href="https://crontab.guru/examples.html"&gt;Crontab Guru&lt;/a&gt; was incredibly helpful in setting these up.&lt;/p&gt;
&lt;p&gt;The only (minor) difficulty was having to split my packer script into two so that the cron jobs wouldn't be set up on my staging server.&lt;/p&gt;
&lt;p&gt;This has worked great! Initially I had three separate cron jobs running: &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One to sync account names on our service with those in Canvas.&lt;/li&gt;
&lt;li&gt;One for quiz grades.&lt;/li&gt;
&lt;li&gt;One for game progress grades.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first has since been scoped down a bit, but I still need it for a specific case. I recently combined the last two because they were happening simultaneously and sending data to the same endpoint. I hadn't realized that when I first created them.&lt;/p&gt;
&lt;p&gt;In the future, I'd like to set up our server to send the grades directly to Canvas when they come in, but for now, this is working well. &lt;/p&gt;
&lt;p&gt;Another alternative to these solutions is Django Q2, which &lt;a href="https://micro.webology.dev/2024/02/27/using-django-q.html"&gt;Jeff Triplett has written about&lt;/a&gt;. It wasn't as established when I was considering what to adopt, but it's worth considering now.&lt;/p&gt;</content><category term="Django"></category><category term="django"></category></entry><entry><title>ramblr</title><link href="https://programmingmylife.com/2024-02-23-ramblr.html" rel="alternate"></link><published>2024-02-23T08:40:06+00:00</published><updated>2024-02-23T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2024-02-23:/2024-02-23-ramblr.html</id><summary type="html">&lt;p&gt;In writing for this blog, there are some posts I'd like to write that I don't think quite fit the theme of this blog. For example, I want to write short book reviews to link to from &lt;a href="../pages/books.html"&gt;my books page&lt;/a&gt;, but I don't want them showing up on the index …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In writing for this blog, there are some posts I'd like to write that I don't think quite fit the theme of this blog. For example, I want to write short book reviews to link to from &lt;a href="../pages/books.html"&gt;my books page&lt;/a&gt;, but I don't want them showing up on the index page and archives. I like having those pages be focused on more technical topics, at least for now.&lt;/p&gt;
&lt;p&gt;Introducing &lt;a href="../ramblr.html"&gt;ramblr&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;One piece of inspiration for this is the blog &lt;a href="https://www.youmightfindyourself.com/"&gt;You Might Find Yourself&lt;/a&gt;. I want somewhere I can put comments about links, book reviews, and (some) posts that aren't my usual technical writeups. Currently, I have a good backlog of posts I want to finish. That isn't always the case, though, and I'm hoping these less technical posts keep me in the habit of writing and posting.&lt;/p&gt;
&lt;p&gt;I am using &lt;a href="https://getpelican.com/"&gt;Pelican's&lt;/a&gt; &lt;code&gt;hidden_articles&lt;/code&gt; feature to hide these articles from the index page and archives. I created a new page, &lt;code&gt;ramblr.html&lt;/code&gt; to act as a secondary index page for them. This code is a heavily edited version of what was at &lt;code&gt;index.html&lt;/code&gt; in my theme.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ramblr.html&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{% extends &amp;quot;base.html&amp;quot; %}
{% block content_title %}
{% endblock %}
{% block content %}

{% if hidden_articles %}
{% for article in hidden_articles %}

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;content&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;body&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ol&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;posts-list&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hfeed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;article&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hentry&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;entry-title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ SITEURL }}/{{ article.url }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ article.title }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;entry-content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    {% include &amp;#39;article_infos.html&amp;#39; %}
                    {{ article.summary }}
                    {% include &amp;#39;comments.html&amp;#39; %}
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!-- /.entry-content --&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;article&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!-- /#content --&amp;gt;&lt;/span&gt;

{% endfor %}

{% endif %}
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I also had to update &lt;code&gt;pelicanconf.py&lt;/code&gt; with:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DIRECT_TEMPLATES = ['index', 'authors', 'categories', 'tags', 'archives', 'ramblr']&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Without this, &lt;code&gt;ramblr.html&lt;/code&gt; doesn't generate. If I just add &lt;code&gt;ramblr&lt;/code&gt;, all of the other templates don't generate.&lt;/p&gt;
&lt;p&gt;Eventually, I might move my ramblr posts to a category or tag, but that requires a lot of fiddly excluding on the index and archives templates and rewriting the categories or tags page to not inherit from the index (or to only inherit for the other categories/tags). I also am not as consistent or structured as I would like to be about categories and tags so I'd like to get those more settled before moving ramblr to one of those.&lt;/p&gt;</content><category term="ramblr"></category><category term="pelican"></category></entry><entry><title>Keyboards</title><link href="https://programmingmylife.com/2024-02-17-keyboards.html" rel="alternate"></link><published>2024-02-17T08:40:06+00:00</published><updated>2024-02-17T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2024-02-17:/2024-02-17-keyboards.html</id><summary type="html">&lt;p&gt;I &lt;s&gt;recently&lt;/s&gt;* read &lt;a href="http://www.screamingatmyscreen.com/building-a-keyboard-to-last/"&gt;Timo Zimmermann's post&lt;/a&gt; about buying a 'keyboard to last' and it made me realize I haven't posted about my keyboard journey yet.  If you don't know much about mechanical keyboards, &lt;a href="https://snarky.ca/the-many-shapes-and-sizes-of-keyboards/"&gt;Brett Cannon's post&lt;/a&gt; about deciding on a new keyboard has a lot of great info. It also …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I &lt;s&gt;recently&lt;/s&gt;* read &lt;a href="http://www.screamingatmyscreen.com/building-a-keyboard-to-last/"&gt;Timo Zimmermann's post&lt;/a&gt; about buying a 'keyboard to last' and it made me realize I haven't posted about my keyboard journey yet.  If you don't know much about mechanical keyboards, &lt;a href="https://snarky.ca/the-many-shapes-and-sizes-of-keyboards/"&gt;Brett Cannon's post&lt;/a&gt; about deciding on a new keyboard has a lot of great info. It also has some recommendations for ergonomic split keyboards.&lt;/p&gt;
&lt;p&gt;I had regular keyboards (the kind that come with a PC or are cheap at Best Buy) for more than a decade of my computing life. At some point, I started hearing more people talk (mostly regarding gaming) about mechanical keyboards and why they were so great. If I remember correctly, a friend let me try his keyboard with Cherry MX Black switches. I liked that well enough so I bought a &lt;a href="https://www.amazon.com/gp/product/B007VDFSG0/"&gt;CM Storm QuickFire Rapid&lt;/a&gt; keyboard with Black switches. Rapid is the brand's tenkeyless version, or keyboard without a numpad. Black switches are on the heavier side (require more force to push down) and linear, see Brett's post above for more about these differences. A few years later, I decided I wanted to switch to MX Brown switches and bought the same keyboard with those. They are tactile switches (not linear) and are lighter than the black switches.&lt;/p&gt;
&lt;p&gt;I had that keyboard for a few years while I was trying to figure out what I wanted in a keyboard upgrade. I thought I liked the MX Clear switches when they came out, but I used a coworker's keyboard with those switches and found they were heavier than I liked for longer typing sessions. Eventually, I bought a &lt;a href="https://vortexgear.store/products/the-new-pok3r-rgb"&gt;Vortex Pok3r&lt;/a&gt;, which is a 60% keyboard (just the letters and modifier keys, no F or arrows keys), but I really missed the arrow keys, so I returned it.&lt;/p&gt;
&lt;p&gt;I bought a few keycap sets for my QuickFire while I researched a other layouts, switch types, etc. A few years after I bought the Vortex, another friend of mine was kind enough to let me borrow a couple of keyboards, both with split layouts:&lt;/p&gt;
&lt;p&gt;A &lt;a href="https://keeb.io/collections/nyquist-keyboard-collection/products/nyquist-keyboard-pre-built"&gt;Nyquist&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Nyquist keyboard" src="https://programmingmylife.com/images/keyboard/Nyquist.png"&gt;&lt;/p&gt;
&lt;p&gt;and an &lt;a href="https://keeb.io/collections/iris-split-ergonomic-keyboard"&gt;Iris&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Iris keyboard" src="https://programmingmylife.com/images/keyboard/Iris.png"&gt;&lt;/p&gt;
&lt;p&gt;I started with the Nyquist and decided to really give it a try by using some typing websites to relearn how to touch type using the split orthographic layout (meaning the keys are in a grid rather than offset), which has helped with my overall typing. I tried the Iris a bit, but I preferred the orthographic layout of the Nyquist.&lt;/p&gt;
&lt;p&gt;After I returned those keyboards, I started looking for something that fit a few functional and aesthetic criteria. I liked the layout of the Nyquist, but I didn't like the available cases. Eventually I found the &lt;a href="https://www.littlekeyboards.com/products/helix-hotswap-pcb-kit?_pos=2&amp;amp;_sid=9178daa9d&amp;amp;_ss=r"&gt;Helix&lt;/a&gt;. I really like the wood case I was able to get, and the owner of that website provided great customer service.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Helix keyboard" src="https://programmingmylife.com/images/keyboard/Helix.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I also still really liked tactile switches so I went with the (rather expensive) &lt;a href="https://zealpc.net/products/zilent?variant=5894817710118"&gt;Zeal Zilent 67g&lt;/a&gt; switches, which are quite nice. After using those for a couple of years, I wanted to try some linear switches again. I mostly was curious how they would compare to the tactile switches for gaming.&lt;/p&gt;
&lt;p&gt;I picked up a set of &lt;a href="https://zealpc.net/products/sakurio-roselio?variant=14010040746047"&gt;Zeal Sakurios&lt;/a&gt; (also quite expensive) and I ended up switching to them full time. I'd love to test drive a range of other switches, but I don't have a way to do that without spending a lot of money. Maybe over time I'll get around to buying and testing more.&lt;/p&gt;
&lt;p&gt;I really love the split keyboard. If I need to use an older standard layout keyboard, I feel like I'm not able to sit as comfortably, and I fumble around with a non-orthographic layout. For some reason, I have fewer issues with my laptop keyboard even though the layout is the same as a standard keyboard.&lt;/p&gt;
&lt;p&gt;Another downside to a split orthographic keyboard is the lack of good keycap options to cover every key. I keep seeing sets that don't have 1u (the size of a letter key on a standard keyboard, as opposed to the larger keys like tab, shift, etc.) keys for all of keys I need (such as tab, two shift keys, backspace, raise, lower, etc.).&lt;/p&gt;
&lt;p&gt;I've considered getting a second keyboard for gaming, but I do so little gaming with a keyboard and mouse anymore that I think that's unlikely. If I were to buy a gaming keyboard, I'd probably go with the &lt;a href="https://wooting.io/wooting-60he"&gt;Wooting 60HE&lt;/a&gt; for the rapid triggers.&lt;/p&gt;
&lt;p&gt;If mine dies or I decide to buy a new keyboard at some point, my current plan is a &lt;a href="https://keeb.io/collections/nyquist-keyboard-collection/products/nyquist-keyboard-pre-built"&gt;Nyquist from keeb.io&lt;/a&gt;, but I might find something else that interests me more before I get to that point. For now, I love my Helix.&lt;/p&gt;
&lt;p&gt;*this one took longer than I expected with the holidays!&lt;/p&gt;</content><category term="2024"></category></entry><entry><title>[TIL] Using Fixtures with pytest `parametrize`</title><link href="https://programmingmylife.com/2024-02-08-til-using-fixtures-with-pytest-parametrize.html" rel="alternate"></link><published>2024-02-08T08:40:06+00:00</published><updated>2024-02-08T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2024-02-08:/2024-02-08-til-using-fixtures-with-pytest-parametrize.html</id><summary type="html">&lt;p&gt;For longer than I care to admit, I was running my end to end tests in only Firefox. I thought it would be trivial to add a fixture to have my tests run using multiple browsers. Unfortunately, there isn't an easy way in &lt;code&gt;pytest&lt;/code&gt; to use fixtures as parametrized values …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For longer than I care to admit, I was running my end to end tests in only Firefox. I thought it would be trivial to add a fixture to have my tests run using multiple browsers. Unfortunately, there isn't an easy way in &lt;code&gt;pytest&lt;/code&gt; to use fixtures as parametrized values*. Here is the solution I landed on:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;session&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;firefox&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Firefox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;session&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;browser&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;firefox&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;chrome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_e2e_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getfixturevalue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;# required to use firefox and chrome fixtures&lt;/span&gt;
        &lt;span class="n"&gt;browser&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We are passing string values (["firefox", "chrome"]) into the test with &lt;code&gt;parametrize&lt;/code&gt;, then we retrieve the fixture (object) with that name using &lt;code&gt;request.getfixturevalue(browser)&lt;/code&gt; built into &lt;code&gt;pytest&lt;/code&gt; (&lt;a href="https://engineeringfordatascience.com/posts/pytest_fixtures_with_parameterize/#using-requestgetfixturevalue-"&gt;link that helped me find this&lt;/a&gt;). It's hacky and I don't like it because an IDE won't be able to trace it, but it works!&lt;/p&gt;
&lt;p&gt;Related: This doubled the number of tests I was running which started giving me failures because too many connections were open. Running my local server with &lt;code&gt;python manage.py runserver --nothreading&lt;/code&gt; stopped those errors from popping up.&lt;/p&gt;
&lt;p&gt;*H/T to &lt;a href="https://fosstodon.org/@webology@mastodon.social"&gt;Jeff Triplett&lt;/a&gt; for suggesting &lt;a href="https://pypi.org/project/pytest-lazy-fixture/"&gt;&lt;code&gt;pytest-lazy-fixture&lt;/code&gt;&lt;/a&gt; as an alernative. If I end up doing this in more places in the future, I'd consider using this library. Update: Jeff &lt;a href="https://micro.webology.dev/2024/03/07/how-to-test.html"&gt;posted an example&lt;/a&gt; of this recently.&lt;/p&gt;
&lt;p&gt;Josh Thomas has &lt;a href="https://github.com/westerveltco/django-twc-toolbox/blob/2e8bc161815188006f523e4751392b07de8018d4/tests/test_paginator.py#L96-L112"&gt;another way of doing this&lt;/a&gt;. Similar to the above, this solution uses strings for the parametrize arguments, but instead of using &lt;code&gt;getfixturevalue&lt;/code&gt;, it imports the fixtures as you normally would in a function then uses conditionals (I suppose you could also use a switch statement if you have more parameters) to match the string and the fixture.&lt;/p&gt;</content><category term="Django"></category><category term="python"></category><category term="django"></category><category term="testing"></category><category term="til"></category></entry><entry><title>Setting up pre-commit to work with WSL and Fork on Windows</title><link href="https://programmingmylife.com/2024-02-02-setting-up-pre-commit-to-work-with-wsl-and-fork-on-windows.html" rel="alternate"></link><published>2024-02-02T08:40:06+00:00</published><updated>2024-02-02T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2024-02-02:/2024-02-02-setting-up-pre-commit-to-work-with-wsl-and-fork-on-windows.html</id><summary type="html">&lt;p&gt;For the tl;dr instructions, feel free to skip to the last two paragraphs.&lt;/p&gt;
&lt;p&gt;Years ago, I used SourceTree for visualizing my git graph. At some point, SourceTree became bloated and I really didn't like the direction it went, so I looked around for alternatives. I forget if I settled …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For the tl;dr instructions, feel free to skip to the last two paragraphs.&lt;/p&gt;
&lt;p&gt;Years ago, I used SourceTree for visualizing my git graph. At some point, SourceTree became bloated and I really didn't like the direction it went, so I looked around for alternatives. I forget if I settled on any in between, but for several years now I've been using &lt;a href="https://git-fork.com/"&gt;Fork&lt;/a&gt;. I highly recommend it.&lt;/p&gt;
&lt;p&gt;For my usage, I like to see what files I've changed and review the changes that are highlighted. I know there are ways to do this in my terminal, but I like this workflow as it gives me a chance to review my changes before pushing in a different window, which helps me take a new perspective.&lt;/p&gt;
&lt;p&gt;In a previous job, I got used to using pre-commit with my Python projects to require running black, flake8, and isort. However, I was using a Mac at that job and my personal environment is on Windows, primarily using WSL. I wasn't able to get pre-commit working smoothly between WSL and Windows (where Fork is installed as there isn't a Linux version). At some point, I stopped trying and was running black, flake8, and isort manually. This meant I wasn't running them at all for some commits. &lt;/p&gt;
&lt;p&gt;I finally got some time to dive into this and found a way to get everything set up:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pre-commit.com/#installation"&gt;Install pre-commit&lt;/a&gt; in Windows (not WSL). I did this through git-bash, but you should be able to do it from any terminal (or command line). You may need to install Python first if you haven't already. After installing pre-commit, you will likely have to add the install location to your path (system, not user). If you haven't set your project up to use pre-commit, you may want to manually do that in WSL first as the logs in Fork might make this step annoying if you have to clean up more than one or two issues.&lt;/p&gt;
&lt;p&gt;You may need to restart Fork, but now you should be all set!&lt;/p&gt;</content><category term="Python"></category><category term="python"></category><category term="pre-commit"></category></entry><entry><title>[TIL] Mocking Chained Calls</title><link href="https://programmingmylife.com/2024-01-29-til-mocking-chained-calls.html" rel="alternate"></link><published>2024-01-29T08:40:06+00:00</published><updated>2024-01-29T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2024-01-29:/2024-01-29-til-mocking-chained-calls.html</id><summary type="html">&lt;p&gt;At work last week, I was asked to write tests for code that reaches out to Twilio's verify service.  Here is what that code looks like (from the &lt;a href="https://www.twilio.com/docs/verify/api"&gt;Twilio docs&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;verification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt; \
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt; \
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;VAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verifications&lt;/span&gt; \
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+15017122661&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sms&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I was able to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;At work last week, I was asked to write tests for code that reaches out to Twilio's verify service.  Here is what that code looks like (from the &lt;a href="https://www.twilio.com/docs/verify/api"&gt;Twilio docs&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;verification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt; \
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt; \
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;VAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verifications&lt;/span&gt; \
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+15017122661&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sms&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I was able to use &lt;code&gt;@mock.patch(module.Client)&lt;/code&gt;* to mock the client, but I tried a few different ways, unsuccessfully, to patch the return value (or raise an exception) of &lt;code&gt;create()&lt;/code&gt; all the way down that chain. After a couple breaks and different incantations entered into Google, I finally stumbled upon what I needed in the docs for &lt;code&gt;unittest.mock&lt;/code&gt;, &lt;a href="https://docs.python.org/3/library/unittest.mock-examples.html#mocking-chained-calls"&gt;mocking chained calls&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I threw in a bunch of &lt;code&gt;.return_value&lt;/code&gt;s into my function call, and thought I had solved it, but that unfortunately didn't work. What I realized is that you only need those after the function calls (ones with parenthesis), not the properties (like &lt;code&gt;verifications&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;, etc.). So the final product looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nd"&gt;@mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;your_module&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mocked_client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;mocked_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verifications&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;return value that I want&amp;#39;&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="c1"&gt;# call the function&lt;/span&gt;
    &lt;span class="c1"&gt;# assert some things&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you want to raise an exception:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio.base.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TwilioRestException&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="nd"&gt;@mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;your_module&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mocked_client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;mocked_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verifications&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;side_effect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TwilioRestException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="c1"&gt;# call the function&lt;/span&gt;
    &lt;span class="c1"&gt;# assert some things&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;*Don't forget to mock the Client in &lt;em&gt;your&lt;/em&gt; module, not the library itself in case other tests need the client.&lt;/p&gt;</content><category term="Django"></category><category term="django"></category><category term="python"></category><category term="pytest"></category><category term="mocking"></category><category term="til"></category></entry><entry><title>Strategies for a Long Running Side Project</title><link href="https://programmingmylife.com/2023-12-19-strategies-for-a-long-running-side-project.html" rel="alternate"></link><published>2023-12-19T08:40:06+00:00</published><updated>2023-12-19T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-12-19:/2023-12-19-strategies-for-a-long-running-side-project.html</id><summary type="html">&lt;p&gt;At DjangoConUS 2023, I gave a lighting talk with this title. It was a lot of fun, and I'd suggest you try to give a lightning talk if you haven't before. It's a great way to try giving a talk at a conference without having to commit to a full …&lt;/p&gt;</summary><content type="html">&lt;p&gt;At DjangoConUS 2023, I gave a lighting talk with this title. It was a lot of fun, and I'd suggest you try to give a lightning talk if you haven't before. It's a great way to try giving a talk at a conference without having to commit to a full 25 or 45 minute talk.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=COMrb4jkdS8&amp;amp;t=836s"&gt;Here is a link to the talk&lt;/a&gt;, if you'd prefer to watch it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Lightning Talk" src="https://programmingmylife.com/images/lightning_talk/lightning_talk.jpg"&gt;&lt;/p&gt;
&lt;p&gt;With that intro out of the way, here are my strategies:&lt;/p&gt;
&lt;h2&gt;Doc&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Doc" src="https://programmingmylife.com/images/lightning_talk/doc.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;You&lt;/h2&gt;
&lt;p&gt;&lt;img alt="You" src="https://programmingmylife.com/images/lightning_talk/you.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Men&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Men" src="https://programmingmylife.com/images/lightning_talk/men.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Tay&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Tay" src="https://programmingmylife.com/images/lightning_talk/tay.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Shun&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Shun" src="https://programmingmylife.com/images/lightning_talk/shun.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Documentation!&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Documentation" src="https://programmingmylife.com/images/lightning_talk/documentation.png"&gt;&lt;/p&gt;
&lt;p&gt;That's right, I'm saying you should write more documentation for your side project. Why? Context! If you need to stop working on your project, or if you are so deep in the authentication that you haven't touched the business logic in months (for example), you may forget a lot of the context around why your wrote the code the way you did, or what comes next. &lt;/p&gt;
&lt;p&gt;But long form docs like these aren't the only kind of documentation you should write. Others include:&lt;/p&gt;
&lt;h2&gt;Tests&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Tests" src="https://programmingmylife.com/images/lightning_talk/tests.png"&gt;&lt;/p&gt;
&lt;p&gt;They document how you want your code to work. And how you don't want it to work (failure states you anticipate, bugs that you've found and written a regression test for, etc.). Reading tests can help provide context for the related code. They also mean that if you start writing code that touches a system you forgot about, it could catch an error in your new code. Write more tests! If you'd like to read more about my thoughts on testing, I wrote about that &lt;a href="https://programmingmylife.com/2023-06-29-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-testing.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Github Issues&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Issues" src="https://programmingmylife.com/images/lightning_talk/issues.png"&gt;&lt;/p&gt;
&lt;p&gt;Instead of keeping a plaintext TODO list, I like to use issues to lay out the work, prioritize, and sometimes have discussions with myself about how the work is progressing and what to do next. Simon Willison has a great blog post and talk with more depth about this strategy: &lt;a href="https://simonwillison.net/2022/Oct/29/the-perfect-commit/"&gt;The Perfect Commit&lt;/a&gt;. In terms of prioritization, I tend to use a Github project to shuffle the issues around which helps me think through longer term planning.&lt;/p&gt;
&lt;h2&gt;Code comments&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Code Comments" src="https://programmingmylife.com/images/lightning_talk/codecomments.png"&gt;&lt;/p&gt;
&lt;p&gt;I find good comments to be incredibly helpful. Function, method, parameter, variable, etc. names should be self documenting. Comments aren't a substitute for those. But sometimes there is a hack or dense bit of code that needs explaining. Sometimes it's helpful to explain why a particular algorithm was chosen as in the example image. Sometimes this is (or should be) temporary, but sometimes I just can't figure out a way to do the thing that doesn't require at least a little explanation. I like to add comments so the next reader understands that context.&lt;/p&gt;
&lt;p&gt;One way I find myself writing comments is that I write some (obviously brilliant) code, then come back to it after a few days | weeks | months and realize it isn't so obviously brilliant. When I remember why I did it that way, I put a comment so future me, or some other (un)lucky developer won't have to wonder.&lt;/p&gt;
&lt;h2&gt;Side Projects?&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Thinky Face" src="https://programmingmylife.com/images/lightning_talk/thinky_face.png"&gt;&lt;/p&gt;
&lt;p&gt;But, Andrew, aren’t these great strategies for any project? Yes, but I'm highlighting them for side projects in this post because I think people tend to underestimate how helpful they can be in the long run.&lt;/p&gt;
&lt;h2&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;Any strategies I missed, or any thoughts you have about any of these? Let me know! Email the site domain name [at] gmail.com, or reach out to me &lt;a href="https://fosstodon.org/@programmylife"&gt;@programmylife@fosstodon.org&lt;/a&gt;&lt;/p&gt;</content><category term="Django"></category><category term="django"></category><category term="python"></category></entry><entry><title>Using SQLite for a Django application on fly.io</title><link href="https://programmingmylife.com/2023-11-06-using-sqlite-for-a-django-application-on-flyio.html" rel="alternate"></link><published>2023-11-06T08:40:06+00:00</published><updated>2023-11-06T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-11-06:/2023-11-06-using-sqlite-for-a-django-application-on-flyio.html</id><summary type="html">&lt;p&gt;I'd like to run a few small apps I've been developing for learning purposes (to do list, etc.) on a cheap or free platform as a service (PaaS) provider so I don't have to worry about a &lt;a href="https://programmingmylife.com/2023-06-22-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-infrastructure-and-deployment.html"&gt;full deployment setup&lt;/a&gt; like I have on my &lt;a href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html"&gt;main project&lt;/a&gt;. These projects aren't …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'd like to run a few small apps I've been developing for learning purposes (to do list, etc.) on a cheap or free platform as a service (PaaS) provider so I don't have to worry about a &lt;a href="https://programmingmylife.com/2023-06-22-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-infrastructure-and-deployment.html"&gt;full deployment setup&lt;/a&gt; like I have on my &lt;a href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html"&gt;main project&lt;/a&gt;. These projects aren't going to be used by many people (likely just me), and the data is not important. Since managed databases are usually a significant cost, I wanted a cheaper and simpler solution.&lt;/p&gt;
&lt;p&gt;After reading about &lt;a href="https://fly.io/blog/all-in-on-sqlite-litestream/"&gt;SQLite from fly.io&lt;/a&gt;, and seeing some other folks discussing using SQLite for real workloads like &lt;a href="https://youtu.be/yTicYJDT1zE?si=aTu8BdV0puPQ3pyv"&gt;this talk from DjangoCon Europe&lt;/a&gt; and &lt;a href="https://simonwillison.net/2022/Oct/23/datasette-gunicorn/"&gt;Simon Willison discussing it at DjangoCon US last year&lt;/a&gt;, I thought it would be interesting to use SQLite as a database for a few small apps. With that blog post from &lt;a href="fly.io"&gt;fly.io&lt;/a&gt;, I assumed it would be easy to get up and running, but it was...not.&lt;/p&gt;
&lt;p&gt;I recognize that the &lt;a href="fly.io"&gt;fly.io&lt;/a&gt; solution for some of this is &lt;a href="https://fly.io/docs/litefs/"&gt;LiteFS&lt;/a&gt;, but for a small app, I'd rather stick with a simpler (and cheaper) solution. I do want to try that eventually and I'll write that up if I find something interesting. &lt;a href="https://usher.dev/posts/django-on-flyio-with-litestream-litefs/"&gt;Here is a great write up on Litestream and LiteFS on fly if you want to start there&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Setting up the app server&lt;/h2&gt;
&lt;p&gt;The first issue I ran into is that &lt;a href="fly.io"&gt;fly.io&lt;/a&gt; defaults to spinning up two app servers, which is the right thing to do for a real production site to avoid downtime. But that means we can't use a single local SQLite file. I think there is a way to share permanent storage between two servers on fly, but the other factor is that I just don't need the redundancy (or complexity) of multiple servers for these apps.&lt;/p&gt;
&lt;p&gt;If you already have an app deployed, you can run &lt;code&gt;fly scale count 1&lt;/code&gt; to scale down to a single machine. If you haven't deployed yet, you can use &lt;code&gt;fly launch --ha=false&lt;/code&gt; to start with a single app machine. I would prefer if there were a way to add this setting to fly.toml, but I didn't see any way to do that.&lt;/p&gt;
&lt;h2&gt;Attached Volume Storage&lt;/h2&gt;
&lt;p&gt;Now that we have a single server, we need to create permanent storage so that we don't lose the database every time we deploy. &lt;a href="https://fly.io/docs/apps/volume-storage/"&gt;Here is how you can do that if you have a new app&lt;/a&gt;, but remember to add &lt;code&gt;--ha=false&lt;/code&gt; to the deploy step or you'll end up with two volumes and have to delete the volumes and app and start over. Since I already had the app set up I &lt;a href="https://fly.io/docs/apps/volume-storage/#add-volumes-to-an-existing-app"&gt;followed these instructions&lt;/a&gt;. I added the following to &lt;code&gt;fly.toml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[mounts]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;myapp_data&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/data&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And ran&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fly volumes create myapp_data -s 1 -r xxx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;where xxx is the region code. and &lt;code&gt;-s 1&lt;/code&gt; is to make the volume 1 GB instead of the default 3 GB. I'm doing this because my application data is going to be very small. You can decide if you'd prefer less or more storage. For some reason if you just add &lt;code&gt;[mounts]&lt;/code&gt; to &lt;code&gt;fly.toml&lt;/code&gt; and let deploy create the volume for you, it defaults to 1 GB. Also, if you think your app might grow beyond 1 GB, but it will start small, you can always &lt;a href="https://fly.io/docs/flyctl/volumes-extend/"&gt;extend your volume with the fly CLI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once you create the volume, you'll have to deploy again to get the volume to attach to your server.&lt;/p&gt;
&lt;p&gt;Important clarification that I didn't understand immediately from the docs: "myapp_data" is the name of the volume/persistant storage created on &lt;a href="fly.io"&gt;fly.io&lt;/a&gt;. "/data" is where that volume will be mounted on your machine. That means if you run &lt;code&gt;fly ssh console&lt;/code&gt;, you can navigate to your new volume by running &lt;code&gt;cd /data&lt;/code&gt;. If you use different names for those settings, remember them for the future commands in this post and refer back here for which is which.&lt;/p&gt;
&lt;p&gt;A note on redundancy: Snapshots of permanent volumes are taken once per day (not guaranteed to be at the same time each day) and kept for 5 days. That's good enough for me for these kinds of projects. You can restore a snapshot with the instructions &lt;a href="https://fly.io/docs/apps/volume-manage/#restore-a-volume-from-a-snapshot"&gt;here&lt;/a&gt;. If you want a little more backup, you can backup the file locally using &lt;a href="https://fly.io/docs/flyctl/ssh-sftp-get/"&gt;fly's sftp command&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;flyctl ssh sftp get /data/db.sqlite3 ./db_y_m_d.sqlite3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you really want to get creative, you could write a cron job to run that command then ship the file to S3/whatever other blob storage. The &lt;a href="https://litestream.io/alternatives/cron/"&gt;Litestream docs have some good tips for that&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;SQLite&lt;/h2&gt;
&lt;p&gt;Ok, we have persistant storage that we can access from our server. Now we have to make a few tweaks to use that storage with SQLite properly.&lt;/p&gt;
&lt;p&gt;First, we need to tell Django to use our volume storage for the database. For this project I am using &lt;code&gt;environs&lt;/code&gt;, so my database setting looks like the following in &lt;code&gt;settings.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;DATABASES = {
    &amp;quot;default&amp;quot;: env.dj_db_url(&amp;quot;DATABASE_URL&amp;quot;, default=&amp;quot;sqlite:///db.sqlite3&amp;quot;),
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I'd recommend being able to test locally with a SQLite database, so we want to keep the default setting as is. We'll need to update the environment variable in fly by running &lt;code&gt;fly secrets set DATABASE_URL=sqlite:////data/db.sqlite3&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Migrations&lt;/h2&gt;
&lt;p&gt;Great! One last set of updates. We need to set up the &lt;code&gt;migrate&lt;/code&gt; step for our deployment. &lt;/p&gt;
&lt;p&gt;If you try to run deploy now, you might get an error &lt;code&gt;django.db.utils.OperationalError: unable to open database file&lt;/code&gt; because of this in &lt;code&gt;fly.toml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[deploy]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;release_command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;python manage.py migrate&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I had this in one project I created but not another. If this isn't in fly.toml, don't worry and create &lt;code&gt;startup.sh&lt;/code&gt; as described in the next paragraph.&lt;/p&gt;
&lt;p&gt;This runs when the machine does not have access to the persistent volume we created, so we need a way to run migrate later in the process. I removed the line &lt;code&gt;release_command = "python manage.py migrate"&lt;/code&gt; in &lt;code&gt;fly.toml&lt;/code&gt; and moved the &lt;code&gt;migrate&lt;/code&gt; step into my Dockerfile. To do this, I created a shell script at the top level of  my project folder (next to Dockerfile, fly.toml, etc.) called &lt;code&gt;startup.sh&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate
sqlite3&lt;span class="w"&gt; &lt;/span&gt;/data/db.sqlite3&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;PRAGMA journal_mode=WAL;&amp;#39;&lt;/span&gt;
sqlite3&lt;span class="w"&gt; &lt;/span&gt;/data/db.sqlite3&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;PRAGMA synchronous=1;&amp;#39;&lt;/span&gt;
gunicorn&lt;span class="w"&gt; &lt;/span&gt;--bind&lt;span class="w"&gt; &lt;/span&gt;:8000&lt;span class="w"&gt; &lt;/span&gt;--workers&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;config.wsgi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note: the last line assumes that &lt;code&gt;wsgi.py&lt;/code&gt; is in a folder named 'config'. If you ran &lt;code&gt;django-admin startproject&lt;/code&gt; with some other name, substitute that for config.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;sqlite3&lt;/code&gt; lines are from the &lt;a href="https://youtu.be/yTicYJDT1zE?si=aTu8BdV0puPQ3pyv"&gt;previously mentioned talk from DjangoCon Europe&lt;/a&gt; and require installing sqlite3 and running &lt;code&gt;startup.sh&lt;/code&gt; in the &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;rf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lists&lt;/span&gt;&lt;span class="o"&gt;/*&lt;/span&gt;

&lt;span class="n"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;collectstatic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;noinput&lt;/span&gt;
&lt;span class="n"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chmod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;startup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt;
&lt;span class="n"&gt;EXPOSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;
&lt;span class="n"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./startup.sh&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note: If you have a line in your &lt;code&gt;Dockerfile&lt;/code&gt; like &lt;code&gt;CMD ["gunicorn", "--bind", ":8000",...]&lt;/code&gt;, you'll need to remove it because it is handled in the shell script.&lt;/p&gt;
&lt;p&gt;That should be it. Now we can run &lt;code&gt;fly deploy&lt;/code&gt; and our app should be using a SQLite file on our permanent volume storage and running migrate each time we deploy!&lt;/p&gt;
&lt;p&gt;If you want to make sure everything is set up properly, add some data to your database and run &lt;code&gt;fly deploy&lt;/code&gt; again. If you aren't using permanent storage, you're database will be reset. If you are, everything should still be there.&lt;/p&gt;
&lt;p&gt;P.S. Thanks to &lt;code&gt;billy&lt;/code&gt; for their help on the &lt;a href="https://community.fly.io/t/using-sqlite-from-persistent-volume-for-django-application/16206"&gt;fly.io community forum&lt;/a&gt; for helping me fix a few of the issues I ran into trying to get this all working.&lt;/p&gt;</content><category term="Django"></category><category term="django"></category><category term="python"></category></entry><entry><title>Moving from Django Microframework to Django</title><link href="https://programmingmylife.com/2023-10-30-moving-from-django-microframework-to-django.html" rel="alternate"></link><published>2023-10-30T08:40:06+00:00</published><updated>2023-10-30T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-10-30:/2023-10-30-moving-from-django-microframework-to-django.html</id><summary type="html">&lt;p&gt;At DjangoConUS 2019, &lt;a href="https://noumenal.es/"&gt;Carlton Gibson&lt;/a&gt; gave a talk called &lt;a href="https://www.youtube.com/watch?v=w9cYEovduWI"&gt;Using Django as a Micro-Framework&lt;/a&gt;, where he demonstrated a single file implementation of "Hello, World!" in Django.&lt;/p&gt;
&lt;p&gt;At the sprints during DjangoConUS 2023, Paolo Melchiorre showed off &lt;a href="https://www.paulox.net/2023/10/26/udjango_micro_django/"&gt;an even more minimal version&lt;/a&gt; that sparked some great discussion. Specifically, the usage of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;At DjangoConUS 2019, &lt;a href="https://noumenal.es/"&gt;Carlton Gibson&lt;/a&gt; gave a talk called &lt;a href="https://www.youtube.com/watch?v=w9cYEovduWI"&gt;Using Django as a Micro-Framework&lt;/a&gt;, where he demonstrated a single file implementation of "Hello, World!" in Django.&lt;/p&gt;
&lt;p&gt;At the sprints during DjangoConUS 2023, Paolo Melchiorre showed off &lt;a href="https://www.paulox.net/2023/10/26/udjango_micro_django/"&gt;an even more minimal version&lt;/a&gt; that sparked some great discussion. Specifically, the usage of lambda &lt;a href="https://github.com/wsvincent/django-microframework/pull/14/files#diff-230b1976bfefcc902ea0385cfebb1d53bae26ca18a13b2f35eb195207717995cR12"&gt;in this initial version of Paolo's effort&lt;/a&gt; made me realize that using the original function based version, you could build from that single file to what an early Django project typically looks like. After a great discussion over lunch with &lt;a href="https://mostlypython.substack.com/"&gt;Eric Matthes&lt;/a&gt; about this, I decided to write this post to show just that: going from a single file to a project where a user has run &lt;code&gt;startproject&lt;/code&gt; and &lt;code&gt;startapp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I hope this demystifies some of the 'magic' behind Django and we can learn some things in the process. Note that we are using Django 4.2.X for this example.&lt;/p&gt;
&lt;h1&gt;Getting Started&lt;/h1&gt;
&lt;p&gt;I forked &lt;a href="https://learndjango.com/"&gt;Will Vincent's&lt;/a&gt; repo for &lt;a href="https://github.com/wsvincent/django-microframework"&gt;django-microframework&lt;/a&gt; and have made a few modifications to get us started. First, I renamed &lt;code&gt;hello_django1.py&lt;/code&gt; to &lt;code&gt;hello_django.py&lt;/code&gt; because we only need one file, and this one was closer to what I wanted to build from. In &lt;a href="https://github.com/programmylife/django-microframework"&gt;my fork&lt;/a&gt;, the &lt;code&gt;main&lt;/code&gt; branch will have the final version of this code, but I've also created branches that start at the beginning and mark important steps in our journey. &lt;a href="https://github.com/programmylife/django-microframework/tree/1_initial_version_for_blog"&gt;Here is the first branch&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Writing Blog Posts&lt;/h1&gt;
&lt;p&gt;We are going to be writing a blog, so let's start with some 'blog posts' in &lt;code&gt;hello_django.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.handlers.wsgi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WSGIHandler&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.management&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ROOT_URLCONF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello_world&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, Django!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;second_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;This is my second blog post. I&amp;#39;m still figuring this whole thing out.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;third_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;This is my third blog post. I&amp;#39;m feeling more comfortable with this whole thing, and I&amp;#39;m going to try to write a bit more. &amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fourth_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Hot dogs are not a sandwich. In this blog post I will first define the meaning of sandwich, then I will ???? which will conclusively prove that hot dogs are indeed not sandwiches.&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hello-world&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hello_world&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;second-post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second_post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;third-post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;third_post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fourth-post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fourth_post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WSGIHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/programmylife/django-microframework/tree/2_create_blog_posts"&gt;Here is the branch for that code&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Now that we have those, &lt;code&gt;hello_django.py&lt;/code&gt; is getting a bit long, and we need to focus on our writing. What can we do to simplify our code so we can do that? Let's move some of the code from &lt;code&gt;hello_django.py&lt;/code&gt; into a few other files.&lt;/p&gt;
&lt;h1&gt;manage.py&lt;/h1&gt;
&lt;p&gt;First, we can move the last couple of lines (and the associated import) from &lt;code&gt;hello_django.py&lt;/code&gt; to &lt;code&gt;manage.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.management&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But that yields:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImproperlyConfigured&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Requested&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;configured&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;must&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;either&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DJANGO_SETTINGS_MODULE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accessing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So we define &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; and now we have a working &lt;code&gt;manage.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.management&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DJANGO_SETTINGS_MODULE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;hello_django.settings&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I'd like to pause here to highlight that a lot of &lt;s&gt;magic&lt;/s&gt; misdirection happens when we run &lt;code&gt;python manage.py runserver&lt;/code&gt;. In order to understand what is happening, though, we only need to go to the &lt;a href="https://github.com/django/django/blob/stable/5.0.x/django/core/management/__init__.py#L439C22-L439C22"&gt;Django source code&lt;/a&gt; for &lt;code&gt;execute_from_command_line()&lt;/code&gt;. This function handles whatever command you call after &lt;code&gt;python manage.py&lt;/code&gt;, such as &lt;code&gt;runserver&lt;/code&gt;, &lt;code&gt;makemigrations&lt;/code&gt;, and &lt;code&gt;migrate&lt;/code&gt;. Walking through these is a post (maybe a series) in and of itself, but at least now we know where to find what happens when we call a command using &lt;code&gt;python manage.py&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Back to our Blog&lt;/h1&gt;
&lt;p&gt;We still have a few more lines that we can move out of &lt;code&gt;hello_django.py&lt;/code&gt; to really focus on our posts. Let's move everything that isn't the posts themselves into their own files starting from the top, moving &lt;code&gt;settings.configure()&lt;/code&gt; into &lt;code&gt;settings.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;

&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ROOT_URLCONF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;urls&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, we can match the change we made to the location of our URLs in &lt;code&gt;settings.py&lt;/code&gt; by creating &lt;code&gt;urls.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;hello_django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hello_world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second_post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;third_post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fourth_post&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hello-world&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hello_world&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;second-post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second_post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;third-post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;third_post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fourth-post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fourth_post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, let's create &lt;code&gt;wsgi.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.handlers.wsgi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WSGIHandler&lt;/span&gt;

&lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WSGIHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Awesome. Now &lt;code&gt;hello_django.py&lt;/code&gt; contains only the blog posts. &lt;/p&gt;
&lt;p&gt;But we've done something else here, too.&lt;/p&gt;
&lt;p&gt;Let's take one final step to see what else we've accomplished. We can make a folder called &lt;code&gt;config&lt;/code&gt; (the name isn't important, so you can call this what you want) and move our new files there: &lt;code&gt;wsgi.py&lt;/code&gt;, &lt;code&gt;settings.py&lt;/code&gt;, and &lt;code&gt;urls.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now we have to match our paths so we update &lt;code&gt;manage.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.management&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DJANGO_SETTINGS_MODULE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;config.settings.settings&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and &lt;code&gt;settings.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;

&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ROOT_URLCONF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;config.urls&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here is &lt;a href="https://github.com/programmylife/django-microframework/tree/3_startproject"&gt;a link to the third branch&lt;/a&gt; with the current state of our code.&lt;/p&gt;
&lt;p&gt;We've just done (almost) everything that happens when you create a new django project and execute:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;django-admin startproject config .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Two notable exceptions:&lt;/p&gt;
&lt;p&gt;First, we have not created &lt;code&gt;asgi.py&lt;/code&gt;, which gets created with new projects as of Django 3.0. This isn't an asynchronous project, so we can leave it out.&lt;/p&gt;
&lt;p&gt;Second, you may notice our &lt;code&gt;manage.py&lt;/code&gt; has less code than the version that Django creates and that we set our &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; environment variable in &lt;code&gt;manage.py&lt;/code&gt; rather than in &lt;code&gt;wsgi.py&lt;/code&gt;. I'm choosing to leave these as is for this exercise to simplify moving from the original code to our destination. I wanted to mention it in case it confuses any readers looking at those files in their own projects, but I don't want to slow us down by diving into every difference between this project and a project where you've run &lt;code&gt;startproject&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;After startproject&lt;/h1&gt;
&lt;p&gt;You may notice that &lt;code&gt;hello_django.py&lt;/code&gt; now looks an awful lot like &lt;code&gt;views.py&lt;/code&gt; that you're likely familiar with from any other Django project, so let's rename it to reflect that.&lt;/p&gt;
&lt;p&gt;Since we're going to want to write longer blog posts than we currently have, it doesn't make much sense to hard code our blog entries. Let's store those in a database. For that, we'll need to set up &lt;code&gt;models.py&lt;/code&gt; and add &lt;code&gt;DATABASES&lt;/code&gt; to &lt;code&gt;settings.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;

&lt;span class="c1"&gt;# Build paths inside the project like this: BASE_DIR / &amp;#39;subdir&amp;#39;.&lt;/span&gt;
&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;

&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ROOT_URLCONF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;config.urls&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DATABASES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;ENGINE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;django.db.backends.sqlite3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;NAME&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;db.sqlite3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For the sake of simplicity, here is a minimal &lt;code&gt;models.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We've got our model set up, but how can we move our posts into our database? Well, to start with, we can set up our Django admin panel for this project. This requires several changes. First, I added only the required settings here by adding &lt;code&gt;django.contrib.admin&lt;/code&gt; to &lt;code&gt;INSTALLED_APPS&lt;/code&gt;. then I added each app/middleware entry to address an error message until &lt;code&gt;python manage.py runserver&lt;/code&gt; successfully ran without errors.&lt;/p&gt;
&lt;p&gt;After those additions, here is &lt;code&gt;settings.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;

&lt;span class="c1"&gt;# Build paths inside the project like this: BASE_DIR / &amp;#39;subdir&amp;#39;.&lt;/span&gt;
&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;

&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ROOT_URLCONF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;config.urls&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DATABASES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;ENGINE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;django.db.backends.sqlite3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;NAME&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;db.sqlite3&amp;quot;&lt;/span&gt;&lt;span class="p"&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;INSTALLED_APPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;django.contrib.admin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;django.contrib.auth&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;django.contrib.contenttypes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;django.contrib.messages&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;django.contrib.sessions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;django.contrib.sessions.middleware.SessionMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;django.contrib.auth.middleware.AuthenticationMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;django.contrib.messages.middleware.MessageMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;django-insecure-4@r)20xhni#*+xcl*8u0t9b)85djrm+3(^0(7pzbb#7+l337*r&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TEMPLATES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;BACKEND&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;django.template.backends.django.DjangoTemplates&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;DIRS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;APP_DIRS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;OPTIONS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;context_processors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;django.template.context_processors.debug&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;django.template.context_processors.request&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;django.contrib.auth.context_processors.auth&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;django.contrib.messages.context_processors.messages&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Installed Apps and Middleware&lt;/h1&gt;
&lt;p&gt;Before we get to the admin, I want to focus on &lt;code&gt;INSTALLED_APPS&lt;/code&gt; and &lt;code&gt;MIDDLEWARE&lt;/code&gt;. Similar to running commands with &lt;code&gt;manage.py&lt;/code&gt;, these hide a lot of complexity. The upside is that this is where a lot of the 'batteries' are stored that people refer to when saying Django is a batteries included framework. To elaborate, let's start with &lt;code&gt;django.contrib.admin&lt;/code&gt; in &lt;code&gt;INSTALLED_APPS&lt;/code&gt;. While this is a string in our file, it's actually a reference to &lt;a href="https://github.com/django/django/tree/stable/5.0.x/django/contrib/admin"&gt;code in Django itself&lt;/a&gt;. Similarly, you can find the other &lt;code&gt;INSTALLED_APPS&lt;/code&gt; by navigating the code there.&lt;/p&gt;
&lt;p&gt;Similarly, the strings in the &lt;code&gt;MIDDLEWARE&lt;/code&gt; list all refer to classes in the Django codebase that you can navigate to by seeing each element in the string (separated by a period) as a folder in the Django codebase (until the last which is the PascalCase class name). Example: &lt;a href="https://github.com/django/django/blob/stable/5.0.x/django/contrib/sessions/middleware.py#L12"&gt;django.contrib.sessions.middleware.SessionMiddleware&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Admin&lt;/h1&gt;
&lt;p&gt;We are now ready for &lt;code&gt;admin.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;

&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;startapp&lt;/h1&gt;
&lt;p&gt;We've hit a bit of a stopping point because we won't be able to see our new model in the admin until we've created and run the migration. In order to do that, we need an installed application for &lt;code&gt;python manage.py makemigrations&lt;/code&gt; to pick up the new model. We could run &lt;code&gt;python manage.py startapp&lt;/code&gt; to create these files, but we're already most of the way there, so let's create the folder ourselves (call it &lt;code&gt;blog&lt;/code&gt;) and move &lt;code&gt;models.py&lt;/code&gt;, &lt;code&gt;admin.py&lt;/code&gt;, and &lt;code&gt;views.py&lt;/code&gt; into it.&lt;/p&gt;
&lt;p&gt;We need to update the &lt;code&gt;views&lt;/code&gt; import, and add the &lt;code&gt;admin&lt;/code&gt; route in &lt;code&gt;urls.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;blog.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hello_world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second_post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;third_post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fourth_post&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hello-world&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hello_world&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;second-post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second_post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;third-post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;third_post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fourth-post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fourth_post&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;admin/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Make and run migrations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;makemigrations
python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We have a functioning admin!&lt;/p&gt;
&lt;p&gt;But, in order to log in, we'll have to create a super user:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;createsuperuser
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ok, now we can move our blog posts into the database. Go to &lt;a href="http://127.0.0.1:8000/admin"&gt;your local admin&lt;/a&gt; and log in. Click 'Add' down by 'Posts', and add some text to the field labeled 'Body'. Click 'Save and Add Another' and repeat at least four times.&lt;/p&gt;
&lt;p&gt;After we've added all four posts, we can update &lt;code&gt;views.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;blog.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello_world&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;second_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;third_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fourth_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Run &lt;code&gt;python manage.py runserver&lt;/code&gt; and navigate to one of the post URLs (&lt;a href="http://127.0.0.1:8000/hello-world"&gt;here's our first one&lt;/a&gt;), and you can see that we're now getting the text from our database. Hooray!&lt;/p&gt;
&lt;p&gt;We're almost done, but since we're good software developers, we should add a test for this project in &lt;code&gt;tests.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlogTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUpTestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;post text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_post_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;post text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Run the test.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blog.tests
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, for the sake of completion, and to get rid of that warning you've probably been seeing if you're coding along, we'll create &lt;code&gt;blog/apps.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.apps&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AppConfig&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlogConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;default_auto_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;django.db.models.BigAutoField&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;blog&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And that's it! Our project now matches a project that has run &lt;code&gt;python manage.py startapp blog&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;What have we learned?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;When we run &lt;code&gt;django-admin startproject config .&lt;/code&gt;, we are creating &lt;code&gt;wsgi.py&lt;/code&gt;, &lt;code&gt;settings.py&lt;/code&gt;, &lt;code&gt;urls.py&lt;/code&gt;, and &lt;code&gt;manage.py&lt;/code&gt; in the folder &lt;code&gt;config&lt;/code&gt;. Now we know what these do and contain.&lt;/li&gt;
&lt;li&gt;If we want to know more about what a &lt;code&gt;manage.py&lt;/code&gt; command does, we know we can look into the &lt;a href="https://github.com/django/django/blob/stable/5.0.x/django/core/management/__init__.py#L439C22-L439C22"&gt;Django source code&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;We can also visit the &lt;a href="https://github.com/django/django/tree/stable/5.0.x/django/contrib/admin"&gt;source code&lt;/a&gt; to better understand what any section of &lt;code&gt;INSTALLED_APPS&lt;/code&gt; and &lt;code&gt;MIDDLEWARE&lt;/code&gt; do.&lt;/li&gt;
&lt;li&gt;When we run &lt;code&gt;python manage.py startapp appname&lt;/code&gt;, the files &lt;code&gt;apps.py&lt;/code&gt;, &lt;code&gt;models.py&lt;/code&gt;, &lt;code&gt;admin.py&lt;/code&gt;, &lt;code&gt;tests.py&lt;/code&gt; and &lt;code&gt;views.py&lt;/code&gt; are created in the folder &lt;code&gt;appname&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;</content><category term="Django"></category><category term="django"></category></entry><entry><title>Aliases, pip, and dotfiles</title><link href="https://programmingmylife.com/2023-10-13-aliases-pip-and-dotfiles.html" rel="alternate"></link><published>2023-10-13T08:40:06+00:00</published><updated>2023-10-13T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-10-13:/2023-10-13-aliases-pip-and-dotfiles.html</id><summary type="html">&lt;p&gt;Since my &lt;a href="https://programmingmylife.com/2023-01-24-bash-aliases.html"&gt;last post on aliases&lt;/a&gt;, I've added a few new aliases. Two are from &lt;a href="https://adamchainz.gumroad.com/l/byddx"&gt;Boost Your Django DX&lt;/a&gt;, which I'd highly recommend. The first is a straight cut from the book:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;alias pip=&amp;quot;python -m pip&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Using &lt;code&gt;python -m pip&lt;/code&gt; instead of &lt;code&gt;pip&lt;/code&gt; ensures you are using the version …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Since my &lt;a href="https://programmingmylife.com/2023-01-24-bash-aliases.html"&gt;last post on aliases&lt;/a&gt;, I've added a few new aliases. Two are from &lt;a href="https://adamchainz.gumroad.com/l/byddx"&gt;Boost Your Django DX&lt;/a&gt;, which I'd highly recommend. The first is a straight cut from the book:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;alias pip=&amp;quot;python -m pip&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Using &lt;code&gt;python -m pip&lt;/code&gt; instead of &lt;code&gt;pip&lt;/code&gt; ensures you are using the version of &lt;code&gt;pip&lt;/code&gt; for your local python. I've definitely had times where using it instead of just &lt;code&gt;pip&lt;/code&gt; fixed issues for me. &lt;/p&gt;
&lt;p&gt;The second is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newenv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python3.11 -m venv --prompt . .venv &amp;amp;&amp;amp; .venv/bin/pip install -U pip setuptools wheel&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This isn't recommended as an alias in the book, but the pattern is recommended. I set this up as an alias because I've been playing around with a few small projects lately, which has caused me to create and recreate environments repeatedly. This makes it much simpler. The big win for me here is that I really don't like another recommended pattern I see in other places which is &lt;code&gt;python3.11 -m venv venv&lt;/code&gt;. The problem with this is that your prompt always looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;(venv)computername:~/home
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;How do you know which environment is active? I rarely have issues related to this since I usually use separate terminals for separate projects, but it provides me peace of mind to know I'm in the correct environment. This brings me to my third new alias:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;alias act=&amp;quot;source .venv/bin/activate&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Again, I just got tired of typing all of that. And by using --prompt in the previous alias, I can have my prompt match the project while the virtual environment folder name is consistent across all projects. This allows me to run &lt;code&gt;act&lt;/code&gt; in any folder where I have a virtual environment and activate it!&lt;/p&gt;
&lt;p&gt;In addition to adding these to my development desktop, I also created a dotfiles repo. Now, whenever I set up a new machine (or use my laptop for travel), I can have the same aliases everywhere. Here are two great examples of dotfiles repos: &lt;a href="https://github.com/nedbat/dot"&gt;Ned Batchelder&lt;/a&gt; and &lt;a href="https://github.com/mblayman/dotfiles"&gt;Matt Layman&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Finally, another tip regarding &lt;code&gt;pip&lt;/code&gt;. A &lt;code&gt;pip&lt;/code&gt; tip, if you will: Set up &lt;code&gt;pip&lt;/code&gt; to require an active virtual environment. There are several ways to do this, mentioned in &lt;a href="https://waylonwalker.com/pip-require-virtualenv/"&gt;this blog post&lt;/a&gt;. If you want to save a click, execute &lt;code&gt;pip config set global.require-virtualenv True&lt;/code&gt;. It will set the config file for you automatically. If you want to read more, click through.&lt;/p&gt;
&lt;p&gt;The common use case this prevents is accidentally installing a bunch of packages (usually with &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;) to your global &lt;code&gt;pip&lt;/code&gt;. However, I just ran into another nice use case for this today. I had moved a virtual environment from a subfolder to the main folder of a project (I was experimenting with different ways to create the project so I had a few projects in a single repo). When I tried to use the environment again, &lt;code&gt;pip&lt;/code&gt; told me there was no virtual environment active even though I had activated it. I'm not exactly sure what caused the issue, but I wouldn't be surprised if it would have caused more issues down the line. Instead, I just deleted the virtual environment, remade and activated it with my handy new aliases!&lt;/p&gt;</content><category term="python"></category><category term="python"></category><category term="django"></category><category term="pip"></category></entry><entry><title>Testing Stripe Integration in Django</title><link href="https://programmingmylife.com/2023-07-12-testing-stripe-integration-in-django.html" rel="alternate"></link><published>2023-07-12T08:40:06+00:00</published><updated>2023-07-12T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-07-12:/2023-07-12-testing-stripe-integration-in-django.html</id><summary type="html">&lt;p&gt;I have been using &lt;a href="https://stripe.com/"&gt;Stripe&lt;/a&gt; to process payments for my company &lt;a href="https://theastroventure.com"&gt;AstroVenture&lt;/a&gt; (see more context in a previous post &lt;a href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html"&gt;here&lt;/a&gt;) for a couple of years now, and it has worked great. One thing that took me quite a while to figure out was how to test that we were properly …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have been using &lt;a href="https://stripe.com/"&gt;Stripe&lt;/a&gt; to process payments for my company &lt;a href="https://theastroventure.com"&gt;AstroVenture&lt;/a&gt; (see more context in a previous post &lt;a href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html"&gt;here&lt;/a&gt;) for a couple of years now, and it has worked great. One thing that took me quite a while to figure out was how to test that we were properly authenticated with the Stripe API without executing a payment.&lt;/p&gt;
&lt;p&gt;I've had two instances that caused issues with purchasing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One release, I broke the server's local environment so things seemed to be working, but our API keys were not read in correctly. Most things seemed to work, but purchases did not.&lt;/li&gt;
&lt;li&gt;Another time I made changes to help secure our servers, but one of the rules prevented most outbound traffic including to Stripe's API!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I wanted a way to test for scenarios like this, but I couldn't find any good solutions until recently. I was searching the API docs for Stripe when I realized I could use any stripe API method that requires authentication to ensure that we are able to access Stripe. If we don't supply the keys or something else goes wrong, the test (below) will fail with an error. I only run this as part of my end to end tests &lt;a href="https://programmingmylife.com/2023-06-29-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-testing.html"&gt;which you can read more about here&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;I added an endpoint to my Django application that looks like the following (I removed the authentication I do at this endpoint since it isn't the point of the example, and of course, don't put your API key directly in your code, read it from a secret store or somewhere else secure):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripeTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    This endpoint exists so that we can run an end to end test&lt;/span&gt;
&lt;span class="sd"&gt;    for the environment to ensure that we are able to access &lt;/span&gt;
&lt;span class="sd"&gt;    the Stripe API after we have deployed. &lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_400_BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And in my end to end tests I have the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_validate_stripe_keys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    By calling the Stripe API and not receiving an error,&lt;/span&gt;
&lt;span class="sd"&gt;    we validate that the keys are properly loaded and that&lt;/span&gt;
&lt;span class="sd"&gt;    we can access the API. We only care if we get an error,&lt;/span&gt;
&lt;span class="sd"&gt;    so we ignore the return value.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;stripe-test-api-is-working/&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_200_OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Do you have a better way of testing that you can access Stripe after a deployment? Let me know!&lt;/p&gt;</content><category term="Django"></category><category term="stripe"></category><category term="python"></category><category term="django"></category><category term="testing"></category></entry><entry><title>Lessons Learned Teaching Undergraduate Astronomy with a Video Game - Testing</title><link href="https://programmingmylife.com/2023-06-29-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-testing.html" rel="alternate"></link><published>2023-06-29T08:40:06+00:00</published><updated>2023-06-29T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-06-29:/2023-06-29-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-testing.html</id><summary type="html">&lt;p&gt;This is the fourth and final installment of the series breaking down my &lt;a href="https://www.youtube.com/watch?v=V2l57CnjakE"&gt;talk from DjangoConUS 2022&lt;/a&gt;. The first entry covered &lt;a href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html"&gt;background information about the project&lt;/a&gt;, the second was about using &lt;a href="https://programmingmylife.com/2023-06-15-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-django-vs-django-rest-framework-drf.html"&gt;Django Rest Framework&lt;/a&gt;, and the third was about &lt;a href="https://programmingmylife.com/2023-06-22-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-infrastructure-and-deployment.html"&gt;infrastructure and deployment&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before diving in, I'd like to emphasize that …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is the fourth and final installment of the series breaking down my &lt;a href="https://www.youtube.com/watch?v=V2l57CnjakE"&gt;talk from DjangoConUS 2022&lt;/a&gt;. The first entry covered &lt;a href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html"&gt;background information about the project&lt;/a&gt;, the second was about using &lt;a href="https://programmingmylife.com/2023-06-15-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-django-vs-django-rest-framework-drf.html"&gt;Django Rest Framework&lt;/a&gt;, and the third was about &lt;a href="https://programmingmylife.com/2023-06-22-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-infrastructure-and-deployment.html"&gt;infrastructure and deployment&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before diving in, I'd like to emphasize that my testing philosophy isn't the end state for a project. It's a guiding principal for getting started and staying motivated with testing. If you have an established application with a large team, you likely already have rules and processes to follow for testing. What I want to discuss here is how I think about testing as I'm building a project. This isn't limited to small/side projects, but it's much more important earlier in a project's lifecycle. &lt;/p&gt;
&lt;p&gt;So what is my philosophy? &lt;strong&gt;Test enough to give you confidence to change and deploy your code.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For me, this does not include test driven development (TDD) or a specific coverage number. Why not test driven development? I've tried it, and it doesn't match my preferred way to work in most projects which is to develop my code then write tests. If you find TDD works better for you, definitely do that! I do some development by writing tests first, but I generally write at least some code before I write my tests. And while I do strive for a high coverage percentage, I use that metric more as a guide to see where I might need more tests rather than a bar to meet. That is, I'm more interested in coverage of a given file than I am an overall number.&lt;/p&gt;
&lt;p&gt;Ok so why this philosophy? For me, I find that without tests (either at all, or even with limited tests for a specific section of the code), I'm much less confident about changing code. This means that I take a lot longer to write or change code in untested sections of the codebase. Usually, I have to take extra time to think through edge cases since I can't be confident those were covered previously and, I don't know what they are because they aren't written into any tests. &lt;/p&gt;
&lt;p&gt;There are several terms specific to testing that I'd like to explicitly define since some folks use different definitions and terminology for different classes of tests. I want to make sure I'm clear about what I mean when I'm using these terms.&lt;/p&gt;
&lt;p&gt;First, &lt;strong&gt;unit tests&lt;/strong&gt; test a 'unit' of code, generally meaning as little code as possible (oftentimes a single function) so that you can be sure you are testing each section without depending on other sections, which can introduce complexity into tests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integration tests&lt;/strong&gt; bring together (integrate) two or more 'units' to test their functionality when combined. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;End to end tests&lt;/strong&gt; cover the full functionality of some part of the application. For example, testing a student sending gameplay data to an endpoint and receiving a response, or someone purchasing our game.&lt;/p&gt;
&lt;p&gt;Our test suite has a large number of unit tests, almost no integration tests, and a few end to end tests. I'll explain my reasoning for each of these. &lt;/p&gt;
&lt;p&gt;First, unit tests. I really like having a good suite of unit tests for two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Almost every time I write some code, then write unit tests, I find that I refactor the code as I'm writing the tests. Sometimes this is just a little bit of cleanup. Other times, it's a bigger rewrite. But almost every time, writing unit tests gets me to think about my code a little bit differently, and I uncover something that I want to improve.&lt;/li&gt;
&lt;li&gt;When I'm making a change to some code I haven't touched in a while, I know that my unit tests will tell me if I broke something. This gives me more confidence to dive in than if I didn't have them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Unit tests take some time and effort to write and maintain, but I'll take that overhead on any project for the confidence that they give. One other great use of unit tests is covering a bugfix. Sometimes, I'll find a bug, write a fix, then write a test (or two) that covers the case for that bug so we can avoid it in the future.&lt;/p&gt;
&lt;p&gt;Why no integration tests?&lt;/p&gt;
&lt;p&gt;I think the functionality is covered better by end to end tests &lt;em&gt;for this project&lt;/em&gt;. For other projects I've worked on, I've had a much larger suite of integration tests and fewer end to end tests. This is highly dependent on what your application does. Generally, for larger projects, you'll want more integration tests to ensure parts of your code work together without having to run a larger suite of end to end tests for every change.&lt;/p&gt;
&lt;p&gt;End to end tests can take a long time to write, a long time to run (relative to unit tests), and are more difficult to maintain. That's why I recommend these only cover the most important parts of your code. Even though these were the most difficult to write and maintain, these give me the most confidence when deploying my code. I know that something can still be wrong if these pass, but I at least know the most important parts of the site are mostly working. I wish I would have written these earlier in the project since my first few deployments were much more stressful without them and required some manual testing.&lt;/p&gt;
&lt;p&gt;For our end to end tests, I use Selenium. I've heard a lot of good things about &lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt;, and I'm hoping to have some time to look into it, but I haven't investigated it enough yet to recommend it myself. I also use pytest instead of the built in testing system in Django. I don't have a strong opinion about pytest versus the test runner in Django, but I've used pytest a good bit professionally (with and without Django), so I find it easier to get started with. I also like the features it has for running tests (like flags for running just the last failed test, &lt;code&gt;--lf&lt;/code&gt;, stopping when hitting a failure &lt;code&gt;--x&lt;/code&gt;, etc.) as well as fixtures and parametrized inputs. I'd recommend you give pytest a try if you haven't already. You don't need to worry about the more advanced features when starting with it and you can build up your knowledge as you go.&lt;/p&gt;
&lt;p&gt;Speaking of fixtures, I tend to create a factory for each data type that I need (when I need it for a test) with Factory Boy and make that a fixture if I need to use it in multiple tests. I'll move those fixtures out to a conftest file (this is a file particular to pytest) if I find a need for them across multiple test files. That way, I'm not populating conftest with a large number of fixtures that aren't used or are only used in one or two places, making it easier to read. If you'd like me to write more about how I use fixtures, let me know!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some key advice: write tests early!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This doesn't mean you need 100% coverage on day 1. Or even 10%. But having a test, any test, makes it much easier to write the next one. So start with something as simple as making sure your pages load or if your application is an API, that your endpoints return the correct basic response to a good request. Then, as you are building out your application, keep asking yourself, what sections of the code worry me the most to change, or what do I worry about when we deploy? And write tests in those areas to alleviate your stress. Also, try to get some tests covering your most important code in place as early as you can. In the long run, it'll speed up your development and increase your confidence in deploying your code.&lt;/p&gt;</content><category term="Django"></category><category term="python"></category><category term="django"></category><category term="testing"></category></entry><entry><title>Update for Notarizing Mac Unity Apps</title><link href="https://programmingmylife.com/2023-06-26-update-for-notarizing-mac-unity-apps.html" rel="alternate"></link><published>2023-06-26T08:40:06+00:00</published><updated>2023-06-26T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-06-26:/2023-06-26-update-for-notarizing-mac-unity-apps.html</id><summary type="html">&lt;p&gt;I recently received an email from Apple stating that they are migrating users from &lt;code&gt;altool&lt;/code&gt; to &lt;code&gt;notarytool&lt;/code&gt; to notarize applications. Apple says &lt;code&gt;altool&lt;/code&gt; will no longer work for notarization starting November 1 2023, but that applications notarized before that will still behave as normal. I previously wrote about &lt;a href="https://programmingmylife.com/2023-02-24-signing-and-notarizing-a-unity-app-for-macos.html"&gt;notarizing a …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently received an email from Apple stating that they are migrating users from &lt;code&gt;altool&lt;/code&gt; to &lt;code&gt;notarytool&lt;/code&gt; to notarize applications. Apple says &lt;code&gt;altool&lt;/code&gt; will no longer work for notarization starting November 1 2023, but that applications notarized before that will still behave as normal. I previously wrote about &lt;a href="https://programmingmylife.com/2023-02-24-signing-and-notarizing-a-unity-app-for-macos.html"&gt;notarizing a Unity app on MacOS&lt;/a&gt;, so I wanted to add to that post since the linked scripts will be out of date by the end of the year.&lt;/p&gt;
&lt;p&gt;What I'm about to write about is covered by Apple &lt;a href="https://developer.apple.com/documentation/technotes/tn3147-migrating-to-the-latest-notarization-tool?changes=_3_3"&gt;here&lt;/a&gt;, but I ended up submitting (and waiting and submitting and waiting) several times before figuring out the proper incantations, so I'm gathering the information here.&lt;/p&gt;
&lt;p&gt;First, in the script for notarizing your application, you'll need to change &lt;code&gt;--username&lt;/code&gt; and &lt;code&gt;--asc-provider&lt;/code&gt; to &lt;code&gt;--apple-id&lt;/code&gt; and &lt;code&gt;--team-id&lt;/code&gt; respectively. Then you'll need to remove &lt;code&gt;--primary-bundle-id&lt;/code&gt; (and the value associated with that flag) and &lt;code&gt;--file&lt;/code&gt;. For that last one, remove ONLY &lt;code&gt;--file&lt;/code&gt; and leave the location of the file you want to notarize. Finally, swap &lt;code&gt;altool --notarize-app&lt;/code&gt; for &lt;code&gt;notarytool submit&lt;/code&gt; and add &lt;code&gt;--wait&lt;/code&gt; (the wait flag is optional, but I'll explain it below).&lt;/p&gt;
&lt;p&gt;So you should go from this in the old script:
&lt;code&gt;xcrun altool --notarize-app --username "$USERNAME" --password "$PASSWORD" --asc-provider "$TEAM_ID" --primary-bundle-id "$BUNDLE_ID" --file "$SIGNING_FOLDER/$APP_NAME.zip"&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;to
&lt;code&gt;xcrun notarytool submit --wait --apple-id "$USERNAME" --password "$PASSWORD" --team-id "$TEAM_ID" "$SIGNING_FOLDER/$APP_NAME.zip"&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The new tool has a nice progress text in the terminal for the upload status that looks like &lt;code&gt;XX% (YY MB of ZZ MB)&lt;/code&gt;. The wait flag is nice because it means you can skip the second script (from the link in the previous blog post) for checking the status of the file after you've uploaded it. If you use the wait flag, after the file finishes uploading, you'll see 'In progress ........', while it waits to be notarized. Much nicer than a hanging terminal session with no info!&lt;/p&gt;</content><category term="Unity"></category><category term="Unity"></category></entry><entry><title>Lessons Learned Teaching Undergraduate Astronomy with a Video Game - Infrastructure and Deployment</title><link href="https://programmingmylife.com/2023-06-22-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-infrastructure-and-deployment.html" rel="alternate"></link><published>2023-06-22T08:40:06+00:00</published><updated>2023-06-22T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-06-22:/2023-06-22-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-infrastructure-and-deployment.html</id><summary type="html">&lt;p&gt;This is the third installment of the series breaking down my &lt;a href="https://www.youtube.com/watch?v=V2l57CnjakE"&gt;talk from DjangoConUS 2022&lt;/a&gt;. The first entry covered &lt;a href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html"&gt;background information about the project&lt;/a&gt; and the second was about using &lt;a href="https://programmingmylife.com/2023-06-15-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-django-vs-django-rest-framework-drf.html"&gt;Django Rest Framework&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, some important context: if you are a devops engineer, or have a lot of experience with …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is the third installment of the series breaking down my &lt;a href="https://www.youtube.com/watch?v=V2l57CnjakE"&gt;talk from DjangoConUS 2022&lt;/a&gt;. The first entry covered &lt;a href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html"&gt;background information about the project&lt;/a&gt; and the second was about using &lt;a href="https://programmingmylife.com/2023-06-15-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-django-vs-django-rest-framework-drf.html"&gt;Django Rest Framework&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, some important context: if you are a devops engineer, or have a lot of experience with AWS/GCP/Azure, this post may not be for you. This post is aimed at folks who would prefer to write Django code than deal with the intracacies of deployment.&lt;/p&gt;
&lt;p&gt;With that being said, this is the section of the talk that made me want to write this series of posts. Specifically, I realized that I focused a lot on the infrastructure setup of &lt;a href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html"&gt;this project&lt;/a&gt;, which I want to outline here. However, I wish I would have spent more time focusing on what I think the goal of any successful deployment strategy should be for a Django project regardless of the infrastructure:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repeatability and confidence in deployments.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are a lot of ways to get to this point. And on day 1 (or even 100 if it's a side project), you likely won't be there. But starting with good process documentation, and moving that toward making your deployments consistent and repeatable is a massive boost in confidence that increases the time you can spend working on your application rather than its infrastructure. It can also significantly lower your stress when it comes to deploying. Importantly, this is all independent of what services you use to deploy your application!&lt;/p&gt;
&lt;p&gt;At AstroVenture, I chose Amazon Web Services (AWS) for our infrastructure early on in founding the company because I had experience with it, and we received free credits for a limited time. At that time, I manually created an EC2 instance for the server (and RDS for the database) and manually installed all of the packages I needed to run the server. I manually installed the app and 'deployments' were done by pulling from GitHub and restarting the gunicorn workers. The 'backup strategy', was the in depth document I wrote with step by step instructions about how I did all of that. I tested it by recreating the server a second time for our production environment and using the first as a test/staging server.&lt;/p&gt;
&lt;p&gt;In the event of a catastrophic server issue, I was likely to be down for several hours, if not an entire day. But having that document gave me the confidence that it wouldn't be more than that, and that I wouldn't have to stress that I would miss a step during that recovery. So if you aren't sure how you would bring your servers back in the event of everything going down, I'd highly recommend going through this exercise for whatever service you are using. It can at least alleviate some of the stress of a deployment going wrong.&lt;/p&gt;
&lt;p&gt;From there, I hired a friend who had more infrastructure experience to write the Packer and Terraform scripts we use now, and to help me make a few architecture decisions that allow us to have ~zero downtime deployments and scaling. I was already using a load balancer, but we added an autoscaling group so that we can spin up new instances if we need.&lt;/p&gt;
&lt;p&gt;The Packer scripts create the server image with the application and all of its dependencies, so if we ever have an issue where we need to redeploy an old stable version, we can do that directly from the image instead of having to recreate it. Luckily, we haven't had to do that yet. We use the Terraform scripts to provision an entirely new server and wait until it is live before swapping it with the previous server (and then terminating that one). There are other tools that handle automating infrastructure and application building that others might prefer, but these have worked well (in combination with some good old fashioned bash scripts) for us.&lt;/p&gt;
&lt;p&gt;We also have end to end tests (more on this in a post coming soon), which I run after every deployment to make sure that the most important parts of the site are functioning correctly.&lt;/p&gt;
&lt;p&gt;What if you don't have a friend with devops experience that can help you, and you don't have that experience yourself?&lt;/p&gt;
&lt;p&gt;There are a number of options of Platform as a Service (PaaS) offerings from companies like &lt;a href="https://render.com/"&gt;Render&lt;/a&gt; and &lt;a href="https://fly.io/"&gt;Fly.io&lt;/a&gt; that a lot of folks in the Django community are using. I'm hoping to try these in the near future along with &lt;a href="https://github.com/ehmatthes/django-simple-deploy"&gt;Django Simple Deploy&lt;/a&gt;. So while I can't give specific recommendations for these platforms, I can tell you that the goal: &lt;em&gt;Repeatability and confidence in deployments&lt;/em&gt; is even easier to achieve on these platforms than it is on AWS (or GCP or Azure). They handle a lot of the work that our Packer and Terraform scripts do so that you can focus on your application code. The tradeoff with these services is that they can be a little bit (to a lot if you scale very large) more expensive than equivalent 'bare metal' servers from AWS, GCP, or Azure. But they can also be cheaper starting out, and the added price can be worth it while you get started.&lt;/p&gt;
&lt;p&gt;No matter what tools you use for hosting and deploying your code, if you are reluctant to deploy because of something in your process you aren't confident about, I strongly recommend you look into ways to address that issue. I found that it was a big relief to stop worrying about deploying once I was able to address the more manual parts of our process.&lt;/p&gt;
&lt;p&gt;Finally, remember, you don't have to do this all at once, and you don't have to be at the point of continuous integration of your code to feel confident with your deployments. Take small steps and work toward the goal of  feeling confident deploying. It'll make coding a lot more fun!&lt;/p&gt;</content><category term="Django"></category><category term="python"></category><category term="django"></category><category term="devops"></category></entry><entry><title>Lessons Learned Teaching Undergraduate Astronomy with a Video Game - Django vs Django Rest Framework (DRF)</title><link href="https://programmingmylife.com/2023-06-15-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-django-vs-django-rest-framework-drf.html" rel="alternate"></link><published>2023-06-15T08:40:06+00:00</published><updated>2023-06-15T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-06-15:/2023-06-15-lessons-learned-teaching-undergraduate-astronomy-with-a-video-game-django-vs-django-rest-framework-drf.html</id><summary type="html">&lt;p&gt;If you've ended up here from somewhere outside of this blog, and are looking for an exhaustive comparison of these two libraries, I regret to inform you, this isn't that. If you're here for the next installment of the series breaking down my &lt;a href="https://www.youtube.com/watch?v=V2l57CnjakE"&gt;talk from DjangoConUS 2022&lt;/a&gt;, welcome back!&lt;/p&gt;
&lt;p&gt;This …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you've ended up here from somewhere outside of this blog, and are looking for an exhaustive comparison of these two libraries, I regret to inform you, this isn't that. If you're here for the next installment of the series breaking down my &lt;a href="https://www.youtube.com/watch?v=V2l57CnjakE"&gt;talk from DjangoConUS 2022&lt;/a&gt;, welcome back!&lt;/p&gt;
&lt;p&gt;This section of the talk outlines &lt;a href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html"&gt;my project's&lt;/a&gt; usage of Django Rest Framework (DRF). For some added context, I wanted to start this project using DRF, but ran into some difficulties because the data coming from the game was largely string based and so it wasn't able to be neatly serialized into the types we had in the database. As a result, I ended up using DRF, but I wasn't able to use some of the built in features like viewsets and generics.&lt;/p&gt;
&lt;p&gt;If you are new to Django, or have never used DRF, one major reason to consider DRF is if you are interested in returning JSON from your application rather than integrating with the Django frontend. Vanilla Django does a great job with integrating the frontend (templates) and backend code. But if you need to send data to a frontend that's not written in Django, or you are developing an API, DRF has a lot of tools to help.&lt;/p&gt;
&lt;p&gt;With that out of the way, there is &lt;a href="https://youtu.be/V2l57CnjakE?t=1100"&gt;one bit of this section of the talk&lt;/a&gt; that I'd like to amend. In that part, I say that if your data model requires a lot of changes to get from hitting your endpoints to your database (like ours did), that you should consider falling back to vanilla Django (versus powering forward with Django Rest Framework).&lt;/p&gt;
&lt;p&gt;What I should have said, is that you can still use DRF, but if you feel like you are fighting with the viewsets or mixins or serializers, you can fall back to the basic &lt;a href="https://www.cdrf.co/3.13/rest_framework.views/APIView.html"&gt;&lt;code&gt;APIView&lt;/code&gt;&lt;/a&gt;, which allows much more prescriptive code rather than the classes built into DRF, which are powerful, but less flexible. If you are more comfortable with vanilla Django, feel free to head back that way, but DRF is capable of handling scenarios where your incoming data don't match your models.&lt;/p&gt;
&lt;p&gt;If you are familiar with DRF, you may be thinking 'isn't that the point of the serializer?' and you would be correct. But, handling too much complexity in the serializer can lead to worse performance. Before I was able to rewrite my incoming data, I tried (with some help from a developer more experienced with DRF) to rewrite our endpoints to use DRF and ended up with more queries than I had before! I needed to update the incoming data before I could improve performance. Otherwise, I needed too many queries to gather everything and return it the way it needed to be to match what our game was expecting. I'm hoping to write up more about how I determined the number of queries, and how I keep my codebase from increasing that number. If that's interesting to you, let me know!&lt;/p&gt;
&lt;p&gt;This gets us to the core message I want to convey: &lt;strong&gt;Don't feel like you have to choose only one way of doing things in a Django project.&lt;/strong&gt; Or that you can't evolve your codebase in the future.&lt;/p&gt;
&lt;p&gt;Yes, consistency can help readability in a project. But just because some of your endpoints use viewsets doesn't mean that every endpoint needs to use viewsets. &lt;em&gt;Use the right tool for the job&lt;/em&gt;. It's ok to mix DRF generic views with viewsets, or DRF viewsets with Django class based views if that better matches what you are trying to accomplish for a given route. And you can always evolve your code to better match the libraries you are using as you learn more about them.&lt;/p&gt;
&lt;p&gt;One piece of advice from this section of the talk I still find myself coming back to is to really focus on understanding serializers and getting to know the inner workings of DRF (by this I mean what it's doing generally behind the scenes to process requests, not necessarily understanding every line of code in the library). After getting over the initial hump of learning about DRF, it can be easy to write some quick views that work. But it can be harder to modify them if you don't understand some of the basics of what DRF is doing under the hood.&lt;/p&gt;
&lt;p&gt;Working with DRF, when I send in data and get an unexpected error, the issue is very often with (what I implemented in) the serializer. Whether that's fields that are missing* (or are not in the model or are misspelled) or I'm trying to do something that isn't possible with a specific serializer class (usually with a serializer that is more specific than what I need). Occasionally, I'll run into an error with a misconfigured URL, or a view that's using the wrong viewset or generic view, but most often, it has to do with the serializer.&lt;/p&gt;
&lt;p&gt;*In fact, just today, I hit an error in the admin locally because I forgot to update my serializer after updating a model field's name.&lt;/p&gt;
&lt;p&gt;So how do we get to know DRF (and serializers) better? If you don't have an app, build one! Either use the &lt;a href="https://www.django-rest-framework.org/tutorial/quickstart/"&gt;DRF Tutorial&lt;/a&gt; or some other tutorial to build an app with DRF. There are many good ones out there (&lt;a href="https://realpython.com/search?q=django+rest+framework"&gt;Real Python&lt;/a&gt; has quite a few). If you have an app already, try using DRF to rebuild endpoints you already have and see how DRF handles them. Or consider adding a few new endpoints. Think about extra data a user may want, or combining data from multiple tables. Anything different from what the endpoints are currently doing will improve your understanding. Even if these changes aren't optimal, you'll see how changes in the data affect the viewsets/generic views and serializers, which will help your understanding a whole lot more than reading another blog post...&lt;/p&gt;
&lt;p&gt;I recently rewrote the majority of the endpoints in &lt;a href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html"&gt;my application&lt;/a&gt; in DRF, and I love how little code there is in my views.py file now! It's also great that I have fewer queries and less processing to do. A lot of that has to do with the aforementioned update to the incoming data, but it's still great to see.&lt;/p&gt;
&lt;p&gt;If you use Django and haven't used DRF (or haven't used it in a while), I highly recommend trying it. There is no need to commit to rewriting your whole application. It can be helpful to simplify endpoints that best match DRF's built in generics or viewsets. And remember that with &lt;code&gt;APIView&lt;/code&gt; you can learn as you go and make things a bit more explicit before diving in entirely with generics and viewsets.&lt;/p&gt;</content><category term="Django"></category><category term="python"></category><category term="django"></category><category term="django-rest-framework"></category></entry><entry><title>AstroVenture and University of Mars</title><link href="https://programmingmylife.com/2023-05-17-astroventure-and-university-of-mars.html" rel="alternate"></link><published>2023-05-17T08:40:06+00:00</published><updated>2023-05-17T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-05-17:/2023-05-17-astroventure-and-university-of-mars.html</id><summary type="html">&lt;p&gt;In this post, I'd like to provide the context for the project I'm currently writing about on this blog. I'm also using this as an opportunity to start a series of blog posts breaking down my &lt;a href="https://www.youtube.com/watch?v=V2l57CnjakE"&gt;recent talk from DjangoConUS 2022&lt;/a&gt; about what I learned related to Django while building …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In this post, I'd like to provide the context for the project I'm currently writing about on this blog. I'm also using this as an opportunity to start a series of blog posts breaking down my &lt;a href="https://www.youtube.com/watch?v=V2l57CnjakE"&gt;recent talk from DjangoConUS 2022&lt;/a&gt; about what I learned related to Django while building this project.&lt;/p&gt;
&lt;p&gt;I've realized since giving the talk there are a few things I'd like to change in/add to it. So instead of a blog post summarizing the talk, I'm opting to revisit the talk and discuss those changes/additions (as well as highlighting the advice I feel holds up). The beginning of the talk covers some of the content of this post. The other sections of the talk will be covered in dedicated posts in the coming weeks.&lt;/p&gt;
&lt;p&gt;In 2018, I founded &lt;a href="https://theastroventure.com"&gt;AstroVenture&lt;/a&gt; with my former colleagues from Penn State University. When I worked there from 2010-2014, we built a videogame that teaches introductory astronomy to undergraduate students that we later named 'University of Mars'. We founded the company in order to sell that game to anyone who wants to learn astronomy. If you'd like to check it out, the first quarter of the game is free as a demo at the link above. If you'd like to play more than that, contact me! There is also a video at the site if you just want to see some gameplay to get a better idea of how it works.&lt;/p&gt;
&lt;p&gt;In the nearly 5 years since we founded the company, this has been a side project that I've worked on actively. It is the project that I really dove into and learned Django with. I'd previously had some professional experience with Django, but I had a lot of other responsibilities on that project, so I didn't have time to dig into Django itself as much as I would have liked. The game was built with Unity3D, which I also learned as I built this project.&lt;/p&gt;
&lt;p&gt;The game uses a story to engage students and set up discussions for each topic. The gameplay loop for each lesson is for the game characters to start discussing the topic of the lesson (ranging from gravity to dark energy), then the student explores some type of interactive minigame on the topic, followed by a quiz. Then we usually lead into the next section's discussion and repeat. &lt;/p&gt;
&lt;p&gt;The servers that I manage for the company handle the website, which has the functionality you would expect from a Django website: login, download the game, view content related to the game (mostly in the form of our static 'encyclopedia' pages that accompany the game). Additionally, those servers also track student progress as they play through the game in order for instructors to give credit to students for finishing the different sections of the game. Currently, instructors can download a CSV file with player progress and quiz scores, but I'm working on integrating with Canvas and Blackboard to make grade syncing more direct.&lt;/p&gt;
&lt;p&gt;The backend code for this player tracking was the first API that I ever wrote and was originally written in PHP hosted in a VPS. When I founded the company, I rewrote the API in Django and moved the hosting to AWS. Since my time was limited, I mostly ported the previous API to Django, faults and all. Fortunately, I had some time to dedicate to this recently and I've just finished updating it to be more efficient and use more of Django Rest Framework's built in features. As for the infrastructure, I chose AWS because I had some experience with it, and I had access to free credits there for a limited time. I'll cover more about the infrastructure in a future post.&lt;/p&gt;
&lt;p&gt;We use Sentry for error handling and Stripe for payment processing, and I highly recommend both.&lt;/p&gt;
&lt;p&gt;The front end of the website is in a separate repo written in Typescript[1] with a bit of jQuery sprinkled in. It also uses Bootstrap for CSS. I chose to not use Django for the front end because I thought I might have someone else working on it who wouldn't have familiarity with Django. If I were to start again today, I might do it differently and have everything in one repo. I'm not exactly sure what I'd settle on, though I'd investigate HTMX, Alpine.js, and Tailwind for CSS as I've heard good things about those.&lt;/p&gt;
&lt;p&gt;If any of these technology choices are interesting to you, I'm happy to discuss them more. See the contact links on the about page, or at the bottom of this page. &lt;/p&gt;
&lt;p&gt;In the upcoming posts, I'll talk more about the other sections of the talk: infrastructure, testing, and Django vs DRF.&lt;/p&gt;
&lt;p&gt;1 - This is almost worth a blog post in itself. When I chose to use Typescript, it was because I was frustrated with JavaScript's lack of typing leading to errors (usually with JSON objects) and not being able to easily debug them in larger codebases. I think Typescript is good, but it was also difficult to find good resources (responses on StackOverflow etc.) dealing with Typescript outside of React or Angular, which made getting started a lot more difficult for me.&lt;/p&gt;</content><category term="Django"></category><category term="python"></category><category term="django"></category><category term="unity"></category></entry><entry><title>[TIL] Django Admin Permissions, etc.</title><link href="https://programmingmylife.com/2023-03-03-til-django-admin-permissions-etc.html" rel="alternate"></link><published>2023-03-03T08:40:06+00:00</published><updated>2023-03-03T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-03-03:/2023-03-03-til-django-admin-permissions-etc.html</id><summary type="html">&lt;p&gt;So far in my main project (more on that to come), the only users of the admin have been myself and my cofounder, so I've made us both superusers. Recently, I had a request to allow an instructor to view and update data from their own courses. This would require …&lt;/p&gt;</summary><content type="html">&lt;p&gt;So far in my main project (more on that to come), the only users of the admin have been myself and my cofounder, so I've made us both superusers. Recently, I had a request to allow an instructor to view and update data from their own courses. This would require me to give them admin access, but in a highly limited view. The good news is that there is a lot of information on how to limit admin access in Django! The bad news is that a lot of it applies to entire models or other use cases that were much less specific than mine. Three major things I learned about:&lt;/p&gt;
&lt;p&gt;First, I'm not sure if this is the best or only way to do this, but I created a group (I could not get this to work as permissions for only one user) and limited the permissions to just a few models (and only the required permissions). I'd be interested if there is a way to do this per individual, but I think I might need the group settings sooner rather later anyway.&lt;/p&gt;
&lt;p&gt;Second, for those models they are able to use, I needed to limit what they could see to only their students. I was able to do that by adding to the relevant models in admin.py:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_queryset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;qs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_queryset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_superuser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;institution&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;their_institution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This returns all data for superusers, but only returns whatever you filter on to other users. In my case, I only have two levels of admin user, so this simple filter works well. You'll have to fill in the filter depending on your model.&lt;/p&gt;
&lt;p&gt;Third, and this was most difficult to track down, I needed to make sure they couldn't see data outside of their scope when adding new students to their course. For the users model, I was able to add a filter to the user admin like above. But for the Course model, I would still see all courses even when using &lt;code&gt;get_queryset&lt;/code&gt; for the CourseAdmin as above. I'm not sure why this is. To fix this, I had to use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;formfield_for_foreignkey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db_field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_superuser&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;db_field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;course&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;queryset&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Course&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;institution&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;institution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;formfield_for_foreignkey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This overwrites the data returned for a foreign key in a dropdown on the 'Add' page, and was exactly what I needed!&lt;/p&gt;</content><category term="Django"></category><category term="django"></category><category term="til"></category></entry><entry><title>Swapping burrs in my Oxo Coffee Grinder</title><link href="https://programmingmylife.com/2023-02-28-swapping-burrs-in-my-oxo-coffee-grinder.html" rel="alternate"></link><published>2023-02-28T08:40:06+00:00</published><updated>2023-02-28T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-02-28:/2023-02-28-swapping-burrs-in-my-oxo-coffee-grinder.html</id><summary type="html">&lt;p&gt;And now for something completely different...&lt;/p&gt;
&lt;p&gt;You may have noticed my favicon is a mug of coffee. I'm a huge coffee fan, and the last few years I've gone further down the rabbit hole with third wave/specialty coffee. &lt;/p&gt;
&lt;p&gt;I recently bought a new coffee grinder to upgrade from my …&lt;/p&gt;</summary><content type="html">&lt;p&gt;And now for something completely different...&lt;/p&gt;
&lt;p&gt;You may have noticed my favicon is a mug of coffee. I'm a huge coffee fan, and the last few years I've gone further down the rabbit hole with third wave/specialty coffee. &lt;/p&gt;
&lt;p&gt;I recently bought a new coffee grinder to upgrade from my ~5 year old Oxo grinder. I try not to be wasteful, so when considering what to do with the old grinder, I decided to search around to see if anyone had recommendations for modifying it. In my (not so exhaustive) search I found &lt;a href="https://www.reddit.com/r/Coffee/comments/wigk9z/oxo_brew_burr_grinder_upgrade_mod_to_lido/"&gt;this recommendation for swapping to a better set of burrs&lt;/a&gt; which I decided to try.&lt;/p&gt;
&lt;p&gt;The instructions were pretty good and the most time consuming part was cleaning out all of the grounds from previous usage (perhaps I should have cleaned it more regularly...). But all in all it was quite an easy swap.&lt;/p&gt;
&lt;p&gt;The retention still isn't great, but it's better and the new burrs work well!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Old dirty burrs" src="https://programmingmylife.com/images/OldBurrsDirty.jpg"&gt;
&lt;img alt="New burrs installed" src="https://programmingmylife.com/images/NewBurrsInstalled.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Since this isn't a coffee blog (yet...) I don't want to get too deep in the weeds, but the old grinder and the new one have different burr types (flat vs. conical) which has allowed me to highlight the difference between them as I try new coffees. It's a lot of fun!&lt;/p&gt;</content><category term="coffee"></category><category term="coffee"></category></entry><entry><title>Signing and Notarizing a Unity App for MacOS</title><link href="https://programmingmylife.com/2023-02-24-signing-and-notarizing-a-unity-app-for-macos.html" rel="alternate"></link><published>2023-02-24T08:40:06+00:00</published><updated>2023-02-24T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-02-24:/2023-02-24-signing-and-notarizing-a-unity-app-for-macos.html</id><summary type="html">&lt;p&gt;Edit: A lot of the information in this post is still great, but Apple have recently recommended moving from &lt;code&gt;altool&lt;/code&gt; (referenced in the linked scripts below) to &lt;code&gt;notarytool&lt;/code&gt;, so I have added an update to this post &lt;a href="https://programmingmylife.com/2023-06-26-update-for-notarizing-mac-unity-apps.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the past few years, Apple's Gatekeeper has made it difficult to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Edit: A lot of the information in this post is still great, but Apple have recently recommended moving from &lt;code&gt;altool&lt;/code&gt; (referenced in the linked scripts below) to &lt;code&gt;notarytool&lt;/code&gt;, so I have added an update to this post &lt;a href="https://programmingmylife.com/2023-06-26-update-for-notarizing-mac-unity-apps.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the past few years, Apple's Gatekeeper has made it difficult to run apps downloaded from the internet. Since most of the users of the game that I distribute (more on this soon) aren't technical, we get a significant number of folks needing help even with detailed instructions we supply. I finally had the time to look into code-signing our Unity game for MacOS that would be distributed outside the app store (downloaded from the internet). &lt;/p&gt;
&lt;p&gt;The following two links have almost all of the instructions necessary, but I want to highlight two issues I ran into in case others have a similar problem. And I think I can explain the root issue a little better than I found elsewhere.&lt;/p&gt;
&lt;p&gt;Links: &lt;a href="https://gist.github.com/rje/70ea38c85e5a690ae8aa0c66e3df441e"&gt;a good example of how to script the process&lt;/a&gt; and
&lt;a href="https://gist.github.com/dpid/270bdb6c1011fe07211edf431b2d0fe4"&gt;a walkthrough with much more thorough explanations&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Note that my Macbook is managed by the IT department at my current employer, so this may be an issue only for managed machines.&lt;/p&gt;
&lt;p&gt;First, it isn't specified in either link, but I installed my certificate (the one I created and downloaded from developer.apple.com, see the second link above for more about that) into my 'login' keychain after running into the issue below and reading through some apple developer forum and stack overflow discussions. I'm not sure if that matters, but it seems that is the recommended way.&lt;/p&gt;
&lt;p&gt;I kept getting &lt;code&gt;Warning: unable to build chain to self-signed root for signer&lt;/code&gt; when trying to run &lt;code&gt;codesign&lt;/code&gt;, and the answer &lt;a href="https://developer.apple.com/forums/thread/86161"&gt;here&lt;/a&gt; about the WWDR Intermediate Certification worked for me initially. I installed the linked cert into my System keychain. But I was using the wrong cerification (Distribution, which is for submitting to the store, I think) so notarization failed.&lt;/p&gt;
&lt;p&gt;After getting the correct certification (again, from the instructions in the second link above, and installing into my 'login' keychain), I had to download the intermediate certification &lt;code&gt;Developer ID - G2 (Expiring 09/17/2031 00:00:00 UTC)&lt;/code&gt; from &lt;a href="https://www.apple.com/certificateauthority/"&gt;here&lt;/a&gt;, and I installed that into my System keychain. This got notarization to work!&lt;/p&gt;
&lt;p&gt;I know it was this one because after I added that, my certification (the one I created in my developer account, downloaded and installed to login) changed to 'trusted'. I tried a different one first that didn't change the status of my certification.&lt;/p&gt;
&lt;p&gt;In short, if codesigning is giving you the error above, you are probably missing the intermediate certification from Apple (this was another answer from the developer forum link above, but I didn't understand it when I read it). What this means is that you should determine which type of certification you requested from Apple (Apple Distribution, etc.) and find the matching certification from &lt;a href="https://www.apple.com/certificateauthority/"&gt;Apple&lt;/a&gt;. It wasn't immediately clear to me which was the matching certification in my case, but I downloaded two that seemed like they might be correct and only one of them changed my cert to trusted in Keychain Access. Good luck!&lt;/p&gt;</content><category term="Unity"></category><category term="Unity"></category></entry><entry><title>Using Docker for Debugging</title><link href="https://programmingmylife.com/2023-01-31-using-docker-for-debugging.html" rel="alternate"></link><published>2023-01-31T08:40:06+00:00</published><updated>2023-01-31T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-01-31:/2023-01-31-using-docker-for-debugging.html</id><summary type="html">&lt;p&gt;My current set of tools for deploying my application to production includes Packer and Terraform. I didn't write all of the code for the deployments, but I've been over most of it now. &lt;/p&gt;
&lt;p&gt;When trying to upgrade my server from Ubuntu 20.01 to 22.01, I ran into some …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My current set of tools for deploying my application to production includes Packer and Terraform. I didn't write all of the code for the deployments, but I've been over most of it now. &lt;/p&gt;
&lt;p&gt;When trying to upgrade my server from Ubuntu 20.01 to 22.01, I ran into some problems with conflicting versions of dependencies (related to postgres, mostly). My first instinct was to create an EC2 instance, then walk through each step manually, but I realized I didn't even have to spin up an instance if I could use Docker.&lt;/p&gt;
&lt;p&gt;I'm not much of a Docker user, but I've used it a few times professionally. Mostly other people have done the hard work of creating a docker image, and I've run it for development. So I thought this was a great opportunity to try using it myself.&lt;/p&gt;
&lt;p&gt;I started by spinning up a docker image for the target Ubuntu version, named &lt;code&gt;jammy&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;ubuntu:jammy
docker&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;jelly&lt;span class="w"&gt; &lt;/span&gt;ubuntu:jammy
docker&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;jelly
docker&lt;span class="w"&gt; &lt;/span&gt;attach&lt;span class="w"&gt; &lt;/span&gt;jelly
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then running through the scripts from my packer build manually:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main&amp;quot;&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/apt/sources.list.d/pgdg.list
apt-get&lt;span class="w"&gt; &lt;/span&gt;update
apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;gnupg
wget&lt;span class="w"&gt; &lt;/span&gt;--quiet&lt;span class="w"&gt; &lt;/span&gt;-O&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;https://www.postgresql.org/media/keys/ACCC4CF8.asc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt-key&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;-

&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive
apt&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;upgrade&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;auto-remove&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;update

apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;unzip&lt;span class="w"&gt; &lt;/span&gt;libpq-dev&lt;span class="w"&gt; &lt;/span&gt;postgresql-client-14&lt;span class="w"&gt; &lt;/span&gt;nginx&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;awscli&lt;span class="w"&gt; &lt;/span&gt;logrotate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is where I get the errors about conflicting versions or versions not available. Did you catch it? &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main&amp;quot;&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/apt/sources.list.d/pgdg.list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;bionic&lt;/code&gt; in there refers to the version of Ubuntu that we are requesting the dependencies for. It should be jammy!&lt;/p&gt;
&lt;p&gt;This was a great example where Docker saved me some time (and a few pennies) from not having to spin up a cloud instance. And there really wasn't much to learn about Docker itself to dive in. So my advice to you is to give Docker a try for a simple use case like this. Despite being used in massive (sometimes very complicated) build chains, Docker is a relatively simple technology to get started with if you already have some familiarity with the Linux version you are planning to use in your container.&lt;/p&gt;
&lt;p&gt;Maybe someday I'll start using it for building a small project...&lt;/p&gt;</content><category term="devops"></category><category term="python"></category><category term="django"></category><category term="devops"></category><category term="docker"></category></entry><entry><title>Bash Aliases</title><link href="https://programmingmylife.com/2023-01-24-bash-aliases.html" rel="alternate"></link><published>2023-01-24T08:40:06+00:00</published><updated>2023-01-24T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-01-24:/2023-01-24-bash-aliases.html</id><summary type="html">&lt;p&gt;This is a short post to tell you to create more bash aliases.&lt;/p&gt;
&lt;p&gt;I have had one for my devops setup for a while because I have to set several environment variables to get just about anything done. More recently, though, I set up a few more for my main …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is a short post to tell you to create more bash aliases.&lt;/p&gt;
&lt;p&gt;I have had one for my devops setup for a while because I have to set several environment variables to get just about anything done. More recently, though, I set up a few more for my main Django project and even this blog. For the blog it's a simple as entering the directory and activating the environment:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;alias pmlstart='cd ~/pml_blog &amp;amp;&amp;amp; source env/bin/activate'&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;For the Django project, it took me forever to remember how to start my local postgres instance (is it service start, or start service, or...?), so I finally just made that part of an alias for when I start working on the project:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;alias webstart="cd ~/website &amp;amp;&amp;amp; source env/bin/activate &amp;amp;&amp;amp; sudo service postgresql start &amp;amp;&amp;amp; source env_vars.sh"&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Since writing this, I added that last piece to it to load environment variables into my environment so I don't have to remember to edit my activate file when I need to recreate my environment locally. Highly recommended!&lt;/p&gt;
&lt;p&gt;Are there bash commands you use frequently together? Make an alias!&lt;/p&gt;</content><category term="WSL"></category><category term="bash"></category><category term="wsl"></category><category term="django"></category></entry><entry><title>Code Formatting Configs in Django</title><link href="https://programmingmylife.com/2023-01-21-code-formatting-configs-in-django.html" rel="alternate"></link><published>2023-01-21T08:40:06+00:00</published><updated>2023-01-21T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2023-01-21:/2023-01-21-code-formatting-configs-in-django.html</id><summary type="html">&lt;p&gt;There are a few code formatting tools I like to use in just about any Python (and Django) project: black, isort, and flake8. These all serve slightly different purposes, and there are alternatives to each. A full discussion/comparison of code formatting tools in Python is beyond the scope of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;There are a few code formatting tools I like to use in just about any Python (and Django) project: black, isort, and flake8. These all serve slightly different purposes, and there are alternatives to each. A full discussion/comparison of code formatting tools in Python is beyond the scope of this post*, but in brief:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Black is great because it automatically formats my code with a well recognized style. I have my editor (currently VSCode) set to run black when I save a file. This makes my code consistent, and is great in teams I've worked in because it means we don't have to worry about styling in code reviews.&lt;/li&gt;
&lt;li&gt;isort is great for sorting imports. Even though I tend to do a bit of this sorting as I go, I inevitably swap an import or leave one out of place. This organizes them so I don't have to.&lt;/li&gt;
&lt;li&gt;flake8 catches minor (and sometimes not so minor) code issues like unused variables.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are a few ways to configure these libraries, but for now, I have them in their own individual config files. These should all be placed at the root of your Django project.&lt;/p&gt;
&lt;p&gt;For black, I use the default config. In some other projects, I've extended the line length, but currently I'm leaving black as default.&lt;/p&gt;
&lt;p&gt;For isort, &lt;code&gt;isort.cfg&lt;/code&gt; is the filename and mine looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[settings]&lt;/span&gt;
&lt;span class="na"&gt;skip_glob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;env/*&lt;/span&gt;
&lt;span class="na"&gt;extend_skip_glob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;**/migrations&lt;/span&gt;
&lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;black&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Where env is the name of your environment. The extended skip for migrations isn't necessary, but I wanted to avoid changing my migration files. The black profile is nice to avoid conflicts between black and isort. Skipping your environment is likely required. When I've left this out (or accidentally run isort without the config) isort runs against the libaries in my environment and ends up breaking them by creating circular dependencies. One way to make sure you aren't going to do this is to run &lt;code&gt;isort --check-only .&lt;/code&gt; to make sure it's only running against your files before actually running it.&lt;/p&gt;
&lt;p&gt;For flake8 &lt;code&gt;.flake8&lt;/code&gt; is the filename and mine looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[flake8]&lt;/span&gt;
&lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;migrations&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;__pycache__&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;manage.py&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;settings.py&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;env&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;.pytest_cache&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;.vscode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;These exclude all of the files in the folders &lt;code&gt;migrations&lt;/code&gt;, &lt;code&gt;__pycache__&lt;/code&gt; and &lt;code&gt;env&lt;/code&gt; (where env is the name of your environment). You may want to exclude more, or not exclude some of these, but I've found this to work for me.&lt;/p&gt;
&lt;p&gt;Finally, on other projects I've used all of these with &lt;a href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt;, which is a library that uses git hooks to prevent you from committing code that doesn't use the standards you and your team have set, such as black, isort, and flake8, or some other combination of code formatting tools. It also allows you to use a single command to run all three tools.&lt;/p&gt;
&lt;p&gt;Unfortunately, I can't figure out how to use pre-commit in my current setup with my python projects in WSL and my git client (Fork) in Windows. Perhaps this is a sign I should start using the git CLI more. But if you know of a good way to use precommit with this setup, let me know!&lt;/p&gt;
&lt;p&gt;*between writing and publishing this I read &lt;a href="https://www.b-list.org/weblog/2022/dec/19/boring-python-code-quality/"&gt;James Bennett covering similar ground&lt;/a&gt;. He goes a bit deeper on some of the tools I mention (and other related tools), so I wanted to link this for further reading.&lt;/p&gt;</content><category term="Django"></category><category term="python"></category><category term="django"></category></entry><entry><title>Switching from Jekyll to Pelican</title><link href="https://programmingmylife.com/2022-11-07-switching-from-jekyll-to-pelican.html" rel="alternate"></link><published>2022-11-07T08:40:06+00:00</published><updated>2022-11-07T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2022-11-07:/2022-11-07-switching-from-jekyll-to-pelican.html</id><summary type="html">&lt;p&gt;In the time since I've posted on this blog, I've erased the environment I used to set up and run Jekyll. Since I've never been a Ruby programmer and most of the code I write now is in Python, I thought I'd look into a blogging engine in Python. I …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the time since I've posted on this blog, I've erased the environment I used to set up and run Jekyll. Since I've never been a Ruby programmer and most of the code I write now is in Python, I thought I'd look into a blogging engine in Python. I found Pelican and Nikola to be recommended by a number of folks, so I decided to give Pelican a try, and it's been great so far! Using a Python based site generator feels more comfortable for me since I'm more familiar with pip than gems.&lt;/p&gt;
&lt;p&gt;It seems remarkably similar to Jekyll and I only had to make a few small changes to the front matter on a few posts to get all of my content working with Pelican. I've made some tweaks to the default theme to move a few icons around to my liking, but otherwise haven't made major changes. I'll make a follow up post if I make interesting changes that others might want to use.&lt;/p&gt;</content><category term="Blog"></category><category term="pelican"></category></entry><entry><title>Accessing WebPacked TypeScript from Javascript</title><link href="https://programmingmylife.com/2019-06-15-accessing-webpacked-typescript-from-javascript.html" rel="alternate"></link><published>2019-06-15T08:40:06+00:00</published><updated>2019-06-15T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2019-06-15:/2019-06-15-accessing-webpacked-typescript-from-javascript.html</id><summary type="html">&lt;p&gt;While I was trying to incorporate Stripe’s Javascript library into my codebase, I ran into some issues implementing it in Typescript. I eventually was able to fix those issues, but while I was trying to figure that out, I left the Stripe code as Javascript and stumbled upon an …&lt;/p&gt;</summary><content type="html">&lt;p&gt;While I was trying to incorporate Stripe’s Javascript library into my codebase, I ran into some issues implementing it in Typescript. I eventually was able to fix those issues, but while I was trying to figure that out, I left the Stripe code as Javascript and stumbled upon an interesting way to interface that with my Typescript code compiled by WebPack.&lt;/p&gt;
&lt;p&gt;First, at the bottom of the Typescript file you want to call from Javascript:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;methodName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In webpack.common.js:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;library&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;MyExposedNamespace&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, in the Javascript file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;MyExposedNamespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="TypeScript"></category><category term="Webpack"></category><category term="TypeScript"></category><category term="Javascript"></category></entry><entry><title>AWS presigned URLS in Django</title><link href="https://programmingmylife.com/2019-04-13-aws-presigned-urls-in-django.html" rel="alternate"></link><published>2019-04-13T08:40:06+00:00</published><updated>2019-04-13T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2019-04-13:/2019-04-13-aws-presigned-urls-in-django.html</id><summary type="html">&lt;p&gt;I found the documentation for presigned URLS on AWS using boto to be insufficient, so here is how I formed the request.&lt;/p&gt;
&lt;p&gt;I wanted to have a file private in S3, but expose it to a user for download if they were authorized to access it. I found the easiest …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I found the documentation for presigned URLS on AWS using boto to be insufficient, so here is how I formed the request.&lt;/p&gt;
&lt;p&gt;I wanted to have a file private in S3, but expose it to a user for download if they were authorized to access it. I found the easiest way to do that was leave the object in S3 as private, but use the AWS API to generate a pre-signed URL with a low timeout (a minute or so). Unfortunately, I found the documentation not so straightforward and cobbled together a few Stack Overflow answers to come up with exactly what I needed. In particular, I needed to add the ‘ResponseContentType’ to get the file to download correctly, and needed to specify my AWS credentials.&lt;/p&gt;
&lt;p&gt;I’m using Django to serve up the download via a get request:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetDownloadURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Get the service client.&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;AWSUserName&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Generate the URL to get &amp;#39;key-name&amp;#39; from &amp;#39;bucket-name&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ClientMethod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;get_object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;Bucket&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;your-s3-bucket&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;Key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;SampleDLZip.zip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;ResponseContentType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;application/zip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notes: You will need to update the Key and Bucket params to match what you have in S3. 
Depending how you have set up your AWS credentials, you may be able to omit the ‘profile_name="AWSUserName"’ parameter. I prefer to be explicit in my config because I’ve run into issues when I have used the default in the past.&lt;/p&gt;</content><category term="Django"></category><category term="Django"></category><category term="AWS"></category></entry><entry><title>Jekyll Setup for PML</title><link href="https://programmingmylife.com/2019-03-31-jekyll-setup-for-pml.html" rel="alternate"></link><published>2019-03-31T08:40:06+00:00</published><updated>2019-03-31T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2019-03-31:/2019-03-31-jekyll-setup-for-pml.html</id><summary type="html">&lt;p&gt;I originally created this blog using an EC2 instance on AWS and used that to run Jekyll and host the site. Recently, I shut down that instance in order to move the site generation to my local machine(s) and host the site on S3. &lt;a href="https://medium.com/@willmorgan/moving-a-static-website-to-aws-s3-cloudfront-with-https-1fdd95563106"&gt;This article was very helpful …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I originally created this blog using an EC2 instance on AWS and used that to run Jekyll and host the site. Recently, I shut down that instance in order to move the site generation to my local machine(s) and host the site on S3. &lt;a href="https://medium.com/@willmorgan/moving-a-static-website-to-aws-s3-cloudfront-with-https-1fdd95563106"&gt;This article was very helpful to me when setting up HTTPS with S3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For now, my Jekyll setup is pretty basic, and I’ve only made a handful of changes:&lt;/p&gt;
&lt;p&gt;-I created a pages folder to hold the few non-blog-post pages (currently just About, Books, and Projects) to clean up the home directory. I accidentally moved the homepage into the pages folder at one point and was very annoyed and confused at my homepage not updating when I’d rebuild with changes. Consider yourself warned!&lt;/p&gt;
&lt;p&gt;-Instead of building using the command line, I created a simple bash script to build the site then send the changes to S3:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c1"&gt;#set destination and source so that we can run from anywhere&lt;/span&gt;
&lt;span class="c1"&gt;#helpful when editing posts in the _posts directory&lt;/span&gt;
&lt;span class="nv"&gt;JEKYLL_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production&lt;span class="w"&gt; &lt;/span&gt;bundle&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jekyll&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;/mnt/c/blog_location/_site&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;/mnt/c/blog_location

&lt;span class="c1"&gt;#remove this script from the site directory.&lt;/span&gt;
rm&lt;span class="w"&gt; &lt;/span&gt;/mnt/c/blog_location/build-blog.sh

&lt;span class="c1"&gt;#upload to s3&lt;/span&gt;
aws&lt;span class="w"&gt; &lt;/span&gt;s3&lt;span class="w"&gt; &lt;/span&gt;sync&lt;span class="w"&gt; &lt;/span&gt;--delete&lt;span class="w"&gt; &lt;/span&gt;--size-only&lt;span class="w"&gt; &lt;/span&gt;/mnt/c/blog_location//_site/&lt;span class="w"&gt; &lt;/span&gt;s3://programmingmylife.com/&lt;span class="w"&gt; &lt;/span&gt;--profile&lt;span class="w"&gt; &lt;/span&gt;MyProfile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The file paths are /mnt/c/ because I typically build the site from my local Windows Subsystem for Linux install.&lt;/p&gt;
&lt;p&gt;-Added google analytics. I tried following a blog post to do this, but it broke the page (I must have mistyped something on the includes), but it made me realize jekyll now has GA built in! You just need to add the line:
‘google-analytics: UA-11111111-1 ‘
to your _config.yml (where UA-11111111-1 is your GA tracking ID) and set JEKYLL_ENV=production before building.&lt;/p&gt;
&lt;p&gt;-I got rid of categories in the URLs, which is the default. This was as easy as adding ‘permalink: /:year/:month/:day/:title.html’ to _config.yml&lt;/p&gt;
&lt;p&gt;-I also wanted to add the full contents of posts to my home page. I couldn’t find a config setting to change this, so I had to edit the minima theme. First, I had to find the theme files with &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;bundle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;minima&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, I edited _layouts/home.html to include all post content as shown &lt;a href="https://stackoverflow.com/a/43121111"&gt;here&lt;/a&gt;. I may change this as the number of posts increases.&lt;/p&gt;
&lt;p&gt;I’d like to improve the styling on the blog or get a different theme at some point, but for now, it is mostly the current (at the time of posting) default for Jekyll.&lt;/p&gt;</content><category term="Blog"></category><category term="Jekyll"></category></entry><entry><title>Setting up Webpack to Modify URL per Environment in TypeScript</title><link href="https://programmingmylife.com/2018-10-17-setting-up-webpack-to-modify-url-per-environment-in-typescript.html" rel="alternate"></link><published>2018-10-17T08:40:06+00:00</published><updated>2018-10-17T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2018-10-17:/2018-10-17-setting-up-webpack-to-modify-url-per-environment-in-typescript.html</id><summary type="html">&lt;p&gt;A website I am currently creating has a very simple front end, but I wanted to be able to swap out instances of my API URLs in TypeScript depending which environment I am working in or building for. So far they are all ‘http://127.0.0.1:8000/’, but …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A website I am currently creating has a very simple front end, but I wanted to be able to swap out instances of my API URLs in TypeScript depending which environment I am working in or building for. So far they are all ‘http://127.0.0.1:8000/’, but when I deploy, I didn’t want to have to remember to set a URL for prod or staging etc.&lt;/p&gt;
&lt;p&gt;Unfortunately, I was unable to find a good way to do this with just the TypeScript compiler for static files. I’m not a big fan of the complexity that &lt;a href="https://webpack.js.org/"&gt;webpack&lt;/a&gt; adds, but I’ve noticed it also minifies and obfuscates my javascript, which is a nice bonus. If you know of a good way to do this with the TypeScript compiler alone, please let me know!&lt;/p&gt;
&lt;p&gt;I tried a few different methods, but a combination of &lt;a href="https://webpack.js.org/guides/production/"&gt;using webpack and splitting my config into development and production&lt;/a&gt; and &lt;a href="https://webpack.js.org/plugins/define-plugin/"&gt;DefinePlugin&lt;/a&gt; worked. Otherwise, I mostly just followed the installation instructions for webpack including installing locally. I tried installing globally based on other tutorials, but I ran into a bunch of issues doing that.&lt;/p&gt;
&lt;p&gt;Also, I’m a little frustrated with how confusing the documentation was on this. I tried using &lt;a href="https://webpack.js.org/guides/environment-variables/"&gt;Environment Variables in Webpack&lt;/a&gt;, but I couldn’t figure out how to access those in my code (not just the config file). It also took me some searching to understand how to use DefinePlugin because the documentation does not make it clear where that should go or how to include it. I found &lt;a href="https://github.com/gdi2290/angular-starter/issues/386"&gt;these&lt;/a&gt; &lt;a href="https://tomasalabes.me/blog/web-development/2017/01/03/Useful-Webpack-Define-Plugin-Usages.html"&gt;two&lt;/a&gt; links that helped me figure it out (see my webpack.dev.js, webpack.staging.js, and webpack.prod.js files below).&lt;/p&gt;
&lt;p&gt;With this setup, I run &lt;strong&gt;‘npm run build:test’&lt;/strong&gt;, &lt;strong&gt;‘npm run build:staging’&lt;/strong&gt;, or &lt;strong&gt;‘npm run build:prod’&lt;/strong&gt; depending if I am working locally or building for production. Those commands are mapped in package.json:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;av_frontend&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1.0.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;private&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;echo &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s2"&gt;Error: no test specified&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; exit 1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;build:test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;webpack --config webpack.dev.js --watch &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;build:staging&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;webpack --config webpack.staging.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;build:prod&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;webpack --config webpack.prod.js&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;license&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ISC&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@types/bootstrap&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^4.1.2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@types/node&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^10.12.12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bootstrap&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^4.1.3&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;devDependencies&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ts-loader&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^5.3.1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;typescript&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^3.2.2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;webpack&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^4.27.1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;webpack-cli&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^3.1.2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;webpack-merge&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^4.1.4&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I added --watch on test so that when I’m developing in VSCode, I can just leave it running and it’ll update whenever I save a file. If I want to run that manually I have to run ‘node_modules/.bin/webpack --config webpack.dev.js --watch’ because I installed webpack locally, not globally.&lt;/p&gt;
&lt;p&gt;When I first got this set up, I realized it was only outputting a single Javascript file because I didn’t understand how ‘entry’ and ‘output’ worked in the webpack config file (see code below). Now I define each entry JS file for each page (they both have an import for my config with the IP addresses) with a name. Under ‘output’ [name].js corresponds with each ‘entry’. So my output ends up in the dist/ folder and the two files are named ‘login.js’ and ‘purchase.js’ based on the two fields in the ‘entry’ object. Any files added to 'entry' will produce a corresponding output Javascript file.&lt;/p&gt;
&lt;p&gt;I also missed something in the &lt;a href="https://webpack.js.org/guides/typescript/"&gt;instructions for Typescript&lt;/a&gt; when I was initially setting this up that lead to a long hunt for why it wasn’t including my config.ts file (the error messages were not great). Don’t forget the ‘resolve’ field below or webpack will get confused when trying to import any file with a .ts extension referenced in another file.&lt;/p&gt;
&lt;p&gt;webpack.common.js&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./src/login.ts&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;purchase&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./src/purchase.ts&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;devtool&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;inline-source-map&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sr"&gt;/\.tsx?$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ts-loader&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;exclude&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sr"&gt;/node_modules/&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.tsx&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.ts&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.js&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;[name].js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dist&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;webpack.dev.js&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;webpack-merge&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;webpack&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;common&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./webpack.common.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;common&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;development&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DefinePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;process.env&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;API_URL&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://127.0.0.1:8000/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;webpack.staging.js&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;webpack-merge&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;webpack&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;common&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./webpack.common.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;common&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;production&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DefinePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;process.env&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;API_URL&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://staging.com/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;webpack.prod.js&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;webpack-merge&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;webpack&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;common&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./webpack.common.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;common&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;production&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DefinePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;process.env&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;API_URL&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://production.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I wrote this blog post originally thinking I had solved this problem, but the solution I was using can only handle development and production environments. It also is a little more complicated than the above solution. It is described here: &lt;a href="https://basarat.gitbooks.io/typescript/content/docs/tips/build-toggles.html"&gt;https://basarat.gitbooks.io/typescript/content/docs/tips/build-toggles.html&lt;/a&gt;&lt;/p&gt;</content><category term="TypeScript"></category><category term="Webpack"></category><category term="TypeScript"></category></entry><entry><title>Debugging PostgreSQL Port 5433 and Column Does Not Exist Error</title><link href="https://programmingmylife.com/2018-10-16-debugging-postgresql-port-5433-and-column-does-not-exist-error.html" rel="alternate"></link><published>2018-10-16T08:40:06+00:00</published><updated>2018-10-16T08:40:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2018-10-16:/2018-10-16-debugging-postgresql-port-5433-and-column-does-not-exist-error.html</id><summary type="html">&lt;p&gt;I am creating a Django application using PostgreSQL (PSQL) for my database and was nearly finished with the API when I discovered some strange behavior. After successfully testing the API in the Django app, I decided to run some basic queries on the database. I received the following error for …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I am creating a Django application using PostgreSQL (PSQL) for my database and was nearly finished with the API when I discovered some strange behavior. After successfully testing the API in the Django app, I decided to run some basic queries on the database. I received the following error for nearly every field in the app:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MaxSceneKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;game_progress_gameplaykeys&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ERROR&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;column&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;maxscenekey&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;does&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;exist&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;LINE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MaxSceneKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;game_progress_gameplaykeys&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;HINT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;Perhaps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;meant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;reference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;column&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;game_progress_gameplaykeys.MaxSceneKey&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I was getting the same result for every field in the table that I tried (and when I try to include the table name as the hint suggests), except for ‘user_id’ and ‘objective'.&lt;/p&gt;
&lt;p&gt;I confirmed that the fields existed using &lt;code&gt;\d+ game_progress_gameplaykeys&lt;/code&gt;, tried changing some of their field types, and even upgraded from Postgres 9.5 to 10.5 (I was planning to do this anyway).&lt;/p&gt;
&lt;p&gt;After a bunch of searching, I found the issue:&lt;/p&gt;
&lt;p&gt;“All identifiers (including column names) that are not double-quoted are folded to lower case in PostgreSQL.” from &lt;a href="https://stackoverflow.com/questions/20878932/are-postgresql-column-names-case-sensitive"&gt;https://stackoverflow.com/questions/20878932/are-postgresql-column-names-case-sensitive&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I created camelCase field names in my Django app based on what the field names were previously in my application (written in C#).&lt;/p&gt;
&lt;p&gt;I decided to fix this (for now) by fixing my models to all use snake_case and using &lt;a href="https://github.com/vbabiy/djangorestframework-camel-case"&gt;https://github.com/vbabiy/djangorestframework-camel-case&lt;/a&gt; to switch the keys from camelCase to snake_case when they come into the API. One issue solved!&lt;/p&gt;
&lt;p&gt;While debugging that issue, I decided to update my laptop’s code + postgres version since I hadn’t worked on it in a while and wanted to see if the issue was just on my desktop. When I reinstalled PSQL, I couldn’t seem to log into it using the user I was creating. Using the postgres user was fine, though.&lt;/p&gt;
&lt;p&gt;I finally figured out the issue was that PSQL was running on port 5433, not 5432 (the default). After that, I was puzzling over what could be running on 5432 since ‘netstat’ and ‘lsof’ revealed nothing else running on my WSL Ubuntu VM. As I was searching around, I saw someone mention that really only PSQL should be running on that port, and I realized I had installed PSQL on Windows on that machine before I moved over to WSL. I uninstalled that, switched back to 5432 in Linux, restarted PSQL, and boom, good to go.&lt;/p&gt;
&lt;p&gt;While I was debugging that issue, I learned some good information about PSQL along the way:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/postgresql/10/main/postgresql.conf&lt;/code&gt; allows you to set and check the port that PSQL is running on.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/postgresql/10/main/pg_hba.conf&lt;/code&gt; allows you to set different security protocols for connections to PSQL. Notable for local development: set the local connection lines to ‘trust’ so you don’t have to enter a password when logging in.&lt;/p&gt;
&lt;p&gt;Note: you need to restart the PSQL server for either of these changes to take effect.
Note 2: MORE IMPORTANT NOTE: Don’t use trust anywhere other than a local version of PSQL. Ever.&lt;/p&gt;
&lt;p&gt;These are the lines I had to change to get that to work (may be different in versions of PSQL other than 10.5):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;local&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Unix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;connections&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;only&lt;/span&gt;
&lt;span class="nv"&gt;local&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;                                     &lt;/span&gt;&lt;span class="nv"&gt;trust&lt;/span&gt;
#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IPv4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;connections&lt;/span&gt;:
&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;trust&lt;/span&gt;
#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IPv6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;connections&lt;/span&gt;:
&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;::&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="nv"&gt;trust&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Django"></category><category term="PostgreSQL"></category><category term="Django"></category></entry><entry><title>Configuring NGINX for localhost</title><link href="https://programmingmylife.com/2018-10-14-configuring-nginx-for-localhost.html" rel="alternate"></link><published>2018-10-14T12:50:06+00:00</published><updated>2018-10-14T12:50:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2018-10-14:/2018-10-14-configuring-nginx-for-localhost.html</id><summary type="html">&lt;p&gt;I had a little trouble finding a simple way to set NGINX up to work locally, so I wanted to write up some quick instructions here. I’m using NGINX with Windows Subsystem for Linux (WSL).&lt;/p&gt;
&lt;p&gt;First, I installed NGINX in WSL with ‘sudo apt-get install nginx’&lt;/p&gt;
&lt;p&gt;Then, I created …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I had a little trouble finding a simple way to set NGINX up to work locally, so I wanted to write up some quick instructions here. I’m using NGINX with Windows Subsystem for Linux (WSL).&lt;/p&gt;
&lt;p&gt;First, I installed NGINX in WSL with ‘sudo apt-get install nginx’&lt;/p&gt;
&lt;p&gt;Then, I created a symlink to my frontend directory in my home directory in WSL.&lt;/p&gt;
&lt;p&gt;In /etc/nginx/conf.d, I created basic config file localhost.conf:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;server {
    listen       8080;

    location / {
        root   /home/username/frontend_directory;
        index  index.html index.htm;
    }

    # redirect server error pages to the static page /50x.html
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The only thing you should need to change is the bolded frontend_directory. Keep in mind the path may differ depending where you keep your files. I restructured my front end after &lt;a href="https://programmingmylife.com/2018-10-17-setting-up-webpack-to-modify-url-per-environment-in-typescript.html"&gt;setting up Webpack&lt;/a&gt; to include a dist/ folder and broke this config before modifying this line again.&lt;/p&gt;
&lt;p&gt;To start NGINX (I have to do this every time I restart the computer): ‘sudo nginx’&lt;/p&gt;
&lt;p&gt;Then go to 127.0.0.1:8080 and your site should be live!&lt;/p&gt;
&lt;p&gt;If you need to make changes to your configuration, you should first try: ‘sudo service nginx reload’ which will give you a ‘hot’ reload instead of restarting the server. &lt;/p&gt;
&lt;p&gt;If you do need to restart the server, you can do so with ‘sudo service nginx restart’.&lt;/p&gt;</content><category term="NGINX"></category><category term="NGINX"></category></entry><entry><title>Sending JSON to a server using fetch() in TypeScript</title><link href="https://programmingmylife.com/2018-10-11-sending-json-to-a-server-using-fetch-in-typescript.html" rel="alternate"></link><published>2018-10-11T12:50:06+00:00</published><updated>2018-10-11T12:50:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2018-10-11:/2018-10-11-sending-json-to-a-server-using-fetch-in-typescript.html</id><summary type="html">&lt;p&gt;For a new project, I wanted to use TypeScript on the front end but not any of the frameworks that usually include it (React, Angular, etc.). Unfortunately, this means that when I have been trying to figure out how to do something in TypeScript, searches often lead me to solutions …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For a new project, I wanted to use TypeScript on the front end but not any of the frameworks that usually include it (React, Angular, etc.). Unfortunately, this means that when I have been trying to figure out how to do something in TypeScript, searches often lead me to solutions involving those frameworks.&lt;/p&gt;
&lt;p&gt;I still haven’t found a good resource for creating a JSON object and sending it to a backend using TypeScript. The easiest solution would be to relax the TypeScript compiler and writing it the same way we would in JavaScript, but that defeats the point of using TypeScript. In looking at example code, I found that creating an interface to describe the JSON object is one accepted way to do it. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IJSON&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;shortName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;institution&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;isStudent&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://127.0.0.1:8000/register/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gatherData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;//don&amp;#39;t reload page so that we can test.&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;IJSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;email&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fullName&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;shortName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shortName&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;password&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;institution&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;institution&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;isStudent&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;sendDataViaFetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sendDataViaFetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;IJSON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Authorization&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Handle response we get from the API&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gatherData&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 have a better way, please let me know!&lt;/p&gt;</content><category term="TypeScript"></category><category term="TypeScript"></category></entry><entry><title>Python Reference Talks</title><link href="https://programmingmylife.com/2018-10-10-python-reference-talks.html" rel="alternate"></link><published>2018-10-10T12:50:06+00:00</published><updated>2018-10-10T12:50:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2018-10-10:/2018-10-10-python-reference-talks.html</id><summary type="html">&lt;p&gt;While trying to get more familiar with Django, I started watching talks from DjangoCon from the last few years. I can’t seem to find the talk, but one of them had a list of great Python/Django talks, which inspired me to create me own list (with some definite …&lt;/p&gt;</summary><content type="html">&lt;p&gt;While trying to get more familiar with Django, I started watching talks from DjangoCon from the last few years. I can’t seem to find the talk, but one of them had a list of great Python/Django talks, which inspired me to create me own list (with some definite overlap).&lt;/p&gt;
&lt;p&gt;I have found that revisiting talks like these make me reconsider some design problems that I have recently worked through, so I want to keep a list and rewatch these periodically. I will likely add to this list in the future.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=r9cnHO15YgU"&gt;Greg Ward - How to Write Reusable Code&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=imW-trt0i9I"&gt;You Don’t Need That! - Christopher Neugebauer (About Design Patterns in Python)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=wf-BqAjZb8M"&gt;Raymond Hettinger - Beyond PEP 8 -- Best practices for beautiful intelligible code&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=OSGv2VnC0go"&gt;Raymond Hettinger - Transforming Code into Beautiful, Idiomatic Python&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=HTLu2DFOdTg"&gt;Raymond Hettinger - Python's Class Development Toolkit&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=EnSu9hHGq5o"&gt;Loop like a native: while, for, iterators, generators&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two go together (from DjangoCon2015):
- &lt;a href="https://www.youtube.com/watch?v=55gXwFviOuQ"&gt;Jane Austen on Pep 8 - Lacey Williams Henschel&lt;/a&gt;
- &lt;a href="https://www.youtube.com/watch?v=bg1wdbKBRKg"&gt;The Other Hard Problem: Lessons and Advice on Naming Things by Jacob Burch&lt;/a&gt;&lt;/p&gt;</content><category term="Python"></category><category term="Python"></category><category term="Django"></category></entry><entry><title>Setting up Conda, Django, and PostgreSQL in Windows Subsystem for Linux</title><link href="https://programmingmylife.com/2018-08-19-setting-up-conda-django-and-postgresql-in-windows-subsystem-for-linux.html" rel="alternate"></link><published>2018-08-19T12:50:06+00:00</published><updated>2018-08-19T12:50:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2018-08-19:/2018-08-19-setting-up-conda-django-and-postgresql-in-windows-subsystem-for-linux.html</id><summary type="html">&lt;p&gt;Because I feel much more comfortable in a terminal than on the Windows command line (or in Powershell), I’ve really been enjoying Windows Subsystem for Linux (WSL). In fact, I use it almost exclusively for accessing the server I run this blog from. WSL is essentially a Linux VM …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Because I feel much more comfortable in a terminal than on the Windows command line (or in Powershell), I’ve really been enjoying Windows Subsystem for Linux (WSL). In fact, I use it almost exclusively for accessing the server I run this blog from. WSL is essentially a Linux VM of only a terminal shell in Windows (with no GUI access to Linux) and no lag (like you get in most VMs).&lt;/p&gt;
&lt;p&gt;When I created my &lt;a href="https://programmingmylife.com/2018-07-01-grocery-list-app-and-flask-deployment-issues.html"&gt;Grocery List Flask App&lt;/a&gt;, I began by using WSL. However, I ran into an &lt;a href="https://github.com/Microsoft/WSL/issues/2471"&gt;issue&lt;/a&gt; that prevented me from seeing a locally hosted version of the API in Windows, so I switched to the Windows command line for that app. &lt;/p&gt;
&lt;p&gt;Recently, I’ve been developing a Django application (more on that in a future post), and I ran into a similar issue. Between posting the issue with localhost on WSL and starting this new app, there was a &lt;a href="https://github.com/Microsoft/WSL/issues/2471#issuecomment-392282351"&gt;response&lt;/a&gt; that I had been meaning to check out. I found that for Django and PostgreSQL, making sure everything was running from localhost (or 0.0.0.0) instead of 127.0.0.x, seemed to fix any issues I had. PSQL gave me some issues just running within WSL, but I found that I just need to add '-h localhost' to get it to run.&lt;/p&gt;
&lt;p&gt;Below are the commands I used to get Conda, Django, and PSQL all set up on my PC and then again my laptop. This works for Django 2.0, PSQL 9.5, and Conda 4.5.9. &lt;/p&gt;
&lt;h2&gt;Installation Instructions&lt;/h2&gt;
&lt;p&gt;Edit: I originally had installation instructions in here for PSQL 9.5. If you want 9.5 in Ubuntu, good news! You already have it. To install the newest version of PSQL, you should uninstall that version first, then install the new version from &lt;a href="https://www.postgresql.org/download/linux/ubuntu/"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://conda.io/docs/user-guide/install/linux.html"&gt;Install Conda&lt;/a&gt; (need to restart after installing for it to recognize ‘conda’ commands)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;#create environment&lt;/span&gt;
&lt;span class="n"&gt;conda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NameOfEnvironment&lt;/span&gt;
&lt;span class="c1"&gt;#activate environment&lt;/span&gt;
&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;conda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;activate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NameOfEnvironment&lt;/span&gt;
&lt;span class="c1"&gt;#install Django&lt;/span&gt;
&lt;span class="n"&gt;conda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;anaconda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;
&lt;span class="c1"&gt;#install psycopg2, to interface with PSQL&lt;/span&gt;
&lt;span class="n"&gt;conda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;anaconda&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;psycopg2&lt;/span&gt;

&lt;span class="n"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;denied&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hangs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;just&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rerun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;why&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fixed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;things&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;span class="c1"&gt;#Remove PSQL from Ubuntu:&lt;/span&gt;
&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;purge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;\&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="c1"&gt;#Then run this to make sure you didn’t miss any:&lt;/span&gt;
&lt;span class="n"&gt;dpkg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;

&lt;span class="c1"&gt;#Install PSQL 10 using instructions here: https://www.postgresql.org/download/linux/ubuntu/ I have copied them here for convenience, but please double check that they have not changed&lt;/span&gt;
&lt;span class="c1"&gt;#Create the file /etc/apt/sources.list.d/pgdg.list and add the following line:&lt;/span&gt;
&lt;span class="c1"&gt;#deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main&lt;/span&gt;

&lt;span class="c1"&gt;#Then exeucte the following three commands&lt;/span&gt;
&lt;span class="n"&gt;wget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;quiet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ACCC4CF8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;
&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;
&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;
&lt;span class="n"&gt;createuser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;interactive&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;psql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local_user&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;superuser&lt;/span&gt;

&lt;span class="c1"&gt;#create the database&lt;/span&gt;
&lt;span class="n"&gt;createdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local_db&lt;/span&gt;
&lt;span class="c1"&gt;#log into local_db&lt;/span&gt;
&lt;span class="n"&gt;psql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local_db&lt;/span&gt;

&lt;span class="c1"&gt;#privileges for Django to modify tables.&lt;/span&gt;
&lt;span class="n"&gt;GRANT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ALL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DATABASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local_db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local_user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WITH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PASSWORD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="s1"&gt;&amp;#39;\q&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;interactive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;exit&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;leave&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;span class="c1"&gt;#one line command to log in as the user to check tables during development.&lt;/span&gt;
&lt;span class="n"&gt;psql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local_db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local_user&lt;/span&gt;

&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;makemigrations&lt;/span&gt;
&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;migrate&lt;/span&gt;

&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PSQL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;above&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;enter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\dt&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;see&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django_admin_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django_content_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django_migrations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;django_sessions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PSQL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Django&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="c1"&gt;#optional for now, but allows you to ensure db connection works by storing credentials for the superuser you create.&lt;/span&gt;
&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;createsuperuser&lt;/span&gt;

&lt;span class="c1"&gt;#command to run the server. go to localhost:8000 in your web browser to view!&lt;/span&gt;
&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;runserver&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-postgresql-with-your-django-application-on-ubuntu-14-04"&gt;I used this post for reference&lt;/a&gt;&lt;/p&gt;</content><category term="WSL"></category><category term="Python"></category><category term="Conda"></category><category term="Django"></category><category term="PostgreSQL"></category></entry><entry><title>Unity3D Scriptable Objects</title><link href="https://programmingmylife.com/2018-07-15-unity3d-scriptable-objects.html" rel="alternate"></link><published>2018-07-15T12:50:06+00:00</published><updated>2018-07-15T12:50:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2018-07-15:/2018-07-15-unity3d-scriptable-objects.html</id><summary type="html">&lt;p&gt;This week at our local Unity user meetup group, I presented (along with a co-organizer of the group) about Scriptable Objects in Unity. You can find that talk &lt;a href="https://youtu.be/3C0J5w3QGIY?t=3m51s"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is that same content in text form.&lt;/p&gt;
&lt;p&gt;Scriptable objects are a powerful tool in designing and developing games in Unity3D …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This week at our local Unity user meetup group, I presented (along with a co-organizer of the group) about Scriptable Objects in Unity. You can find that talk &lt;a href="https://youtu.be/3C0J5w3QGIY?t=3m51s"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is that same content in text form.&lt;/p&gt;
&lt;p&gt;Scriptable objects are a powerful tool in designing and developing games in Unity3D. It took me longer than I’d like to admit to get around to using them, but I’d like to introduce them in such a way that makes it easier for you to just get started using them.&lt;/p&gt;
&lt;h1&gt;What is a Scriptable Object (SO)?&lt;/h1&gt;
&lt;p&gt;It is a script that derives from ScriptableObject, instead of MonoBehaviour. This script allows the user to create objects either in memory or as .asset files in Unity, which are also referred to as Scriptable Objects.. A simple example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;[CreateAssetMenu(menuName = &amp;quot;SOs/FloatVar&amp;quot;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;FloatVariable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ScriptableObject&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;MethodName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;//Do stuff&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The line with ‘CreateAssetMenu’ adds a new line to the ‘Create’ menu in the Project window in Unity. When you click that menu item, it will create a new .asset file that has access to the variables and methods defined in your file.&lt;/p&gt;
&lt;p&gt;It does not have access to the standard Update(), Start(), Awake()* methods because those are part of MonoBehaviour. It does derive from Unity’s Object class, so it has access to classes like GameObject, Transform, etc.&lt;/p&gt;
&lt;p&gt;*use OnEnable for initialization instead of Start or Awake&lt;/p&gt;
&lt;p&gt;SOs can contain data and functions just like a MB, but it can’t be attached to a GameObject in the hierarchy as a component. A SO can be referenced by a MB, though.&lt;/p&gt;
&lt;p&gt;Two things to differentiate:&lt;/p&gt;
&lt;p&gt;A script that creates the SO (same as a MB in Project)
The SO, which is a .asset file. (lives in Project folder, analogous to an instance of a MB in the hierarchy).&lt;/p&gt;
&lt;p&gt;SOs aren’t meant to replace MBs everywhere in your project. But there are places where they are a better fit for storing data/functions.&lt;/p&gt;
&lt;h1&gt;Why use SOs?&lt;/h1&gt;
&lt;p&gt;No need for JSON, XML, Text, which means no need to parse data.
Can save large amounts of data and optimize data loading when you need it.
They don’t get reset when exiting playmode!
Since SOs aren’t tied to the scene (not in the hierarchy), you can commit changes to source control without impacting a scene another team member may be working on.
This allows you to more easily reference data/functions across scenes when using multiple scenes in development, which I highly recommend you do (this could be a whole other blog post).
No need to depend on manager classes to hold all of your references.&lt;/p&gt;
&lt;p&gt;3 and 4 combine to allow us to store the data and functions for a type of enemy and tweak that inside of play mode, have the changes saved, then share that with a teammate without having to worry about impacting the scene file. We also don’t have to re-instantiate prefabs or change every instance of the enemy in a scene (or multiple scenes).&lt;/p&gt;
&lt;p&gt;You may already be doing something similar with prefabs (holding/referencing data and never instantiating that particular prefab). If so, look at SOs! Using prefabs for this purpose is confusing and accident prone (accidentally throw a prefab into a scene, get confused between what is a prefab and what is a data holder).&lt;/p&gt;
&lt;p&gt;If you are a less experienced Unity developer and this seems like a lot to consider, don’t worry about digesting all of it. Just think about some piece of your game design and try to make it as a SO instead of a Monobehaviour such as an enemies stats, or some inventory items.&lt;/p&gt;
&lt;p&gt;If you are more experienced, but some of this doesn’t entirely make sense, please try out SOs for some small use cases to see how they differ from MBs. It took me some time to get used to thinking in terms of using SOs, but they are a great tool for a lot of use cases.&lt;/p&gt;
&lt;h1&gt;How to use SOs&lt;/h1&gt;
&lt;p&gt;A few Unity Learn examples that demonstrate different use cases for SOs:&lt;br/&gt;
&lt;a href="https://unity3d.com/learn/tutorials/topics/scripting/introduction-and-goals"&gt;Text Adventure&lt;/a&gt;&lt;br/&gt;
&lt;a href="https://unity3d.com/learn/tutorials/topics/scripting/ability-system-scriptable-objects"&gt;Ability System&lt;/a&gt;&lt;br/&gt;
&lt;a href="https://unity3d.com/learn/tutorials/topics/scripting/character-select-system-scriptable-objects"&gt;Character Select&lt;/a&gt;&lt;br/&gt;
&lt;a href="https://www.youtube.com/watch?v=HkUSmI7F304"&gt;Customizing UI&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Two talks about SOs that really helped me understand how and where to use them:&lt;br/&gt;
&lt;a href="https://www.youtube.com/watch?v=6vmRwLYWNRo"&gt;Richard Fine - Overthrowing the MonoBehaviour Tyranny in a Glorious Scriptable Object Revolution&lt;/a&gt;&lt;br/&gt;
&lt;a href="https://bitbucket.org/richardfine/scriptableobjectdemo/downloads/"&gt;Link to the project from the talk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/WBJCr89I4XQ"&gt;Game Architecture with Scriptable Objects&lt;/a&gt;&lt;br/&gt;
&lt;a href="https://unity3d.com/how-to/architect-with-Scriptable-Objects"&gt;Blog post for the previous talk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Serialization&lt;/p&gt;
&lt;p&gt;This is how Unity reads out your data attached in scripts. This gets talked about alongside Scriptable Objects sometimes because the serialization that Unity does sometimes messes up scriptable objects. Unity serializes data when it enters/exits play mode and some data types don’t play nice (polymorphic classes for example). If you are having issues with data resetting/corrupting under those circumstances, check these out: &lt;br/&gt;
&lt;a href="http://forum.unity3d.com/threads/a-guide-to-editor-script-serialization-making-sure-fields-dont-go-null-on-assembly-reloads.276069/"&gt;Forum post on Serialization and Scriptable Objects&lt;/a&gt;&lt;br/&gt;
&lt;a href="https://blogs.unity3d.com/2014/06/24/serialization-in-unity/"&gt;Blog post from Lucas Meijer&lt;/a&gt;&lt;br/&gt;
&lt;a href="https://blogs.unity3d.com/2012/10/25/unity-serialization/"&gt;Blog Post from Tim Cooper&lt;/a&gt;&lt;br/&gt;
&lt;a href="https://www.youtube.com/watch?v=N-HJvfVuKRw"&gt;Talk by Richard Fine&lt;/a&gt;&lt;/p&gt;</content><category term="Unity"></category><category term="Unity"></category></entry><entry><title>Grocery List App and Flask Deployment Issues</title><link href="https://programmingmylife.com/2018-07-01-grocery-list-app-and-flask-deployment-issues.html" rel="alternate"></link><published>2018-07-01T12:50:06+00:00</published><updated>2018-07-01T12:50:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2018-07-01:/2018-07-01-grocery-list-app-and-flask-deployment-issues.html</id><summary type="html">&lt;p&gt;In addition to starting this blog, I wanted to build some small projects to get some experience with technologies I am not currently using at work. Since I’m currently using Django at work, I decided to create a small Grocery List application using Flask and DynamoDB. You can find …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In addition to starting this blog, I wanted to build some small projects to get some experience with technologies I am not currently using at work. Since I’m currently using Django at work, I decided to create a small Grocery List application using Flask and DynamoDB. You can find the repo and installation info &lt;a href="https://github.com/programmylife/GroceryList"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I ran into two major issues when trying to create the application:&lt;/p&gt;
&lt;p&gt;First, I tried to set things up without a virtual environment for Python, which caused errors with libraries not pointing to the correct locations. I thought that since this was the only application I’d have on the server, a virtual environment wouldn’t be as important. I realize now there is a big upside to separating your application Python install from your system Python install. I highly recommend setting up a virtual environment for your Python app whether it be virtualenv, conda, or something else even if you only intend it to be the only app on a system.&lt;/p&gt;
&lt;p&gt;Second, I didn’t understand how to set up virtual hosts with Apache when I started this project. Getting this blog, the front page of the grocery list app, and the Flask API all routed correctly and running simultaneously took me a few hours to figure out. Two things that seemed to be required to get this all running (otherwise, I was getting the top root folder serving up on all subdomains):&lt;/p&gt;
&lt;p&gt;'NameVirtualHost *:80' at the top of the file and the port number (80) in this line for each of the host definitions: '&lt;VirtualHost *:80&gt;'. See link below for that line in context.&lt;/p&gt;
&lt;p&gt;As I was working through the issues, I created this thread for help on reddit:
&lt;a href="https://www.reddit.com/r/flask/comments/7fbfs1/af_apache_deployment_questions/"&gt;https://www.reddit.com/r/flask/comments/7fbfs1/af_apache_deployment_questions/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I almost posted to serverfault.com, but I ended up figuring things out as I was creating a post.&lt;/p&gt;
&lt;p&gt;References:
&lt;a href="http://peatiscoding.me/geek-stuff/mod_wsgi-apache-virtualenv/"&gt;http://peatiscoding.me/geek-stuff/mod_wsgi-apache-virtualenv/&lt;/a&gt;
&lt;a href="https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xvii-deployment-on-linux-even-on-the-raspberry-pi"&gt;https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xvii-deployment-on-linux-even-on-the-raspberry-pi&lt;/a&gt;&lt;/p&gt;</content><category term="Python"></category><category term="Python"></category></entry><entry><title>Backups Using a Network Attached Raspberry Pi</title><link href="https://programmingmylife.com/2018-03-10-backups-using-a-network-attached-raspberry-pi.html" rel="alternate"></link><published>2018-03-10T12:50:06+00:00</published><updated>2018-03-10T12:50:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2018-03-10:/2018-03-10-backups-using-a-network-attached-raspberry-pi.html</id><summary type="html">&lt;p&gt;After setting up my &lt;a href="https://programmingmylife.com/2018-03-01-setting-up-a-raspberry-pi-as-a-nas-and-plex-server.html"&gt;Raspberry Pi as a NAS&lt;/a&gt;, I wanted to set up backups that are easy to run and check on. Initially, I wanted to set them up to run automatically, but another goal of the Pi set up was for me to turn my PC off more …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After setting up my &lt;a href="https://programmingmylife.com/2018-03-01-setting-up-a-raspberry-pi-as-a-nas-and-plex-server.html"&gt;Raspberry Pi as a NAS&lt;/a&gt;, I wanted to set up backups that are easy to run and check on. Initially, I wanted to set them up to run automatically, but another goal of the Pi set up was for me to turn my PC off more often. I’m currently thinking that if I’m going to turn the PC off, I will just run the backups manually since turning my PC on every Saturday night (or whenever I’d set it to run) isn’t really automated. If I go back on this and set up a cron job I’ll be sure to post about that as well.&lt;/p&gt;
&lt;p&gt;One problem I haven’t been able to solve yet is how to backup Windows itself with this set up. Windows 7 backup tool fails, and I can’t see my network drives with the two free backup applications I tried for Windows. I can include specific folders from that drive in my backup script and/or occasionally switch my external drive to my PC to run that full backup, which is probably what I’ll end up doing.&lt;/p&gt;
&lt;p&gt;Apart from Windows, I have a few different backups I want to run:
1. The SD card for my raspberry pi
2. A large hard drive with a lot of media files (movies, music, pictures, etc.)
3. A SSD that has all of my games on it. 
4. Also on that SSD are folders for my personal software and game development projects. I want to also back these up to AWS.&lt;/p&gt;
&lt;p&gt;Here is the script as it currently stands. I run it from Windows Subsystem for Linux on Windows 10. I have some notes below to explain the set up and why I chose to use the tools and configurations that I did.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;today&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;date&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+%Y_%m_%d&amp;#39;&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;#backup raspberry pi&lt;/span&gt;
ssh&lt;span class="w"&gt; &lt;/span&gt;username@ipaddress&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sudo dd if=/dev/mmcblk0 bs=1M | gzip - | dd of=/media/pi/HDDName/pibackup/pibackup&lt;/span&gt;&lt;span class="nv"&gt;$today&lt;/span&gt;&lt;span class="s2"&gt;.gz&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/mnt/e/rsynclogs/pibackuplog&lt;span class="nv"&gt;$today&lt;/span&gt;.txt

&lt;span class="c1"&gt;#backing up all of my development work including Unity to S3 for offsite backups. Have to add dates to the log files otherwise it overwrites the file&lt;/span&gt;
aws&lt;span class="w"&gt; &lt;/span&gt;s3&lt;span class="w"&gt; &lt;/span&gt;sync&lt;span class="w"&gt; &lt;/span&gt;/mnt/e/Development&lt;span class="w"&gt; &lt;/span&gt;s3://developmentFolder/&lt;span class="w"&gt; &lt;/span&gt;--delete&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/mnt/e/rsynclogs/S3DevOutput&lt;span class="nv"&gt;$today&lt;/span&gt;.txt

aws&lt;span class="w"&gt; &lt;/span&gt;s3&lt;span class="w"&gt; &lt;/span&gt;sync&lt;span class="w"&gt; &lt;/span&gt;/mnt/e/Unity&lt;span class="w"&gt; &lt;/span&gt;s3://unityFolder/&lt;span class="w"&gt; &lt;/span&gt;--delete&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/mnt/e/rsynclogs/S3UnityOutput&lt;span class="nv"&gt;$today&lt;/span&gt;.txt

&lt;span class="c1"&gt;#backup D drive excluding a few folders, and write logs out.&lt;/span&gt;
rsync&lt;span class="w"&gt; &lt;/span&gt;-avP&lt;span class="w"&gt; &lt;/span&gt;--delete&lt;span class="w"&gt; &lt;/span&gt;--size-only&lt;span class="w"&gt; &lt;/span&gt;--exclude-from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/mnt/d/rsynclogs/exclude.txt&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--log-file&lt;span class="o"&gt;=&lt;/span&gt;/mnt/d/rsynclogs/rsynclog&lt;span class="nv"&gt;$today&lt;/span&gt;.txt&lt;span class="w"&gt; &lt;/span&gt;/mnt/d/&lt;span class="w"&gt; &lt;/span&gt;username@ipaddress:/media/pi/MediaBackup/

&lt;span class="c1"&gt;#backup E drive excluding a few folders, and write logs out.&lt;/span&gt;
rsync&lt;span class="w"&gt; &lt;/span&gt;-avW&lt;span class="w"&gt; &lt;/span&gt;--delete&lt;span class="w"&gt; &lt;/span&gt;--size-only&lt;span class="w"&gt; &lt;/span&gt;--exclude-from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/mnt/e/rsynclogs/exclude.txt&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--log-file&lt;span class="o"&gt;=&lt;/span&gt;/mnt/e/rsynclogs/rsynclog&lt;span class="nv"&gt;$today&lt;/span&gt;.txt&lt;span class="w"&gt; &lt;/span&gt;/mnt/e/&lt;span class="w"&gt; &lt;/span&gt;username@ipaddress:/media/pi/GamesBackup/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Raspberry Pi backup is modified from:
&lt;a href="https://johnatilano.com/2016/11/25/use-ssh-and-dd-to-remotely-backup-a-raspberry-pi/"&gt;https://johnatilano.com/2016/11/25/use-ssh-and-dd-to-remotely-backup-a-raspberry-pi/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I don't have access to the network drives from the terminal (or at least I don't know how to access them from WSL without ssh ing), so I needed the output to be relative to the Pi. The quotations enclose the commands that get sent to the Pi, so I had to extend them to include the output location. I also changed 'bs=1m' to 'bs=1M'. I believe the lowercase m is expected on Mac, but the uppercase is required on most flavors of Linux.&lt;/p&gt;
&lt;p&gt;In order to run it from the script I had to set up my user to not require a password to execute the command, which I did by doing the following:&lt;/p&gt;
&lt;p&gt;At a terminal on the Pi, enter 'sudo visudo', change the last line to: 'username ALL = NOPASSWD: ALL', where username is the username you are using to ssh. If you are doing this as the pi user, I don’t think this will be necessary. I'd kind of like to limit this to just the ‘dd’ command, but I'm not sure how to tell it where dd. I may update this in the future.&lt;/p&gt;
&lt;p&gt;For setting up rsync with the correct flags, I used these two links: &lt;a href="https://www.howtogeek.com/175008/the-non-beginners-guide-to-syncing-data-with-rsync/"&gt;https://www.howtogeek.com/175008/the-non-beginners-guide-to-syncing-data-with-rsync/&lt;/a&gt;
&lt;a href="https://www.thegeekstuff.com/2011/01/rsync-exclude-files-and-folders/?utm_source=feedburner"&gt;https://www.thegeekstuff.com/2011/01/rsync-exclude-files-and-folders/?utm_source=feedburner&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Two notes for the rsync commands: &lt;/p&gt;
&lt;p&gt;By default, drives mount with the 'pi' user. Since I was setting up my backups to work with a different user, rsync was giving me errors about not being able to set the time on the files when I'd run the command. I’m pretty sure this was because the user didn’t have permissions on the drive. By adding the drives to &lt;a href="https://www.howtogeek.com/howto/38125/htg-explains-what-is-the-linux-fstab-and-how-does-it-work/"&gt;fstab&lt;/a&gt;, it mounts them as root instead, which allows the user to access them since it has root permissions. I should have done this when setting up the drive as a NAS, but I only did it for the initial drive I was testing. See here for instructions on adding drives to fstab: &lt;a href="https://www.howtogeek.com/139433/how-to-turn-a-raspberry-pi-into-a-low-power-network-storage-device/"&gt;https://www.howtogeek.com/139433/how-to-turn-a-raspberry-pi-into-a-low-power-network-storage-device/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For my E drive rsync, I tried initially with the same settings as the D drive, but the backup kept hanging on different files. I saw several recommendations of different flags that people claimed to be the culprit. I tried turning several off and on, but the one that seemed to fix things was swapping -P for -W (suggested here: &lt;a href="https://github.com/Microsoft/WSL/issues/2138"&gt;https://github.com/Microsoft/WSL/issues/2138&lt;/a&gt;), which forces entire files to be transferred instead of partial files. I could probably add --progress back in, but -v for verbose gives me enough output to see where issues arise. I’d advise adding --progress back in if you encounter issues and need to check where things are going wrong..&lt;/p&gt;
&lt;p&gt;You can find instructions for setting up the AWS CLI tools and using syncing with S3 in the AWS docs. I couldn’t find anything in there for logging, but StackOverflow had a good solution: &lt;a href="https://stackoverflow.com/questions/35075668/output-aws-cli-sync-results-to-a-txt-file"&gt;https://stackoverflow.com/questions/35075668/output-aws-cli-sync-results-to-a-txt-file&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The last thing I added to the script was a variable to grab the current date so I don’t overwrite the pi backup or the log files.&lt;/p&gt;
&lt;p&gt;One thing I’d like to add is a way to clean up the pi backups. At ~3GB each, it isn’t a big issue currently, but eventually I’ll want to clean them up. &lt;/p&gt;</content><category term="RaspberryPi"></category><category term="RaspberryPi"></category></entry><entry><title>Setting up a Raspberry Pi as a NAS and Plex server</title><link href="https://programmingmylife.com/2018-03-01-setting-up-a-raspberry-pi-as-a-nas-and-plex-server.html" rel="alternate"></link><published>2018-03-01T12:50:06+00:00</published><updated>2018-03-01T12:50:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2018-03-01:/2018-03-01-setting-up-a-raspberry-pi-as-a-nas-and-plex-server.html</id><summary type="html">&lt;p&gt;When my external HDD failed, I debated getting a network attached storage device (NAS) before realizing the price wasn’t worth it for me. I don’t have that much data, and really all I wanted was a way to automate backups and have a plex server that requires less …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When my external HDD failed, I debated getting a network attached storage device (NAS) before realizing the price wasn’t worth it for me. I don’t have that much data, and really all I wanted was a way to automate backups and have a plex server that requires less power than my PC (so I could turn it off more often).&lt;/p&gt;
&lt;p&gt;While I was looking around at options, I found I could do both of those things with a Raspberry Pi. I’d been wanting to get one for a while, but never had a good project to justify picking one up. I ordered a &lt;a href="http://amzn.to/2CFFA6W"&gt;Raspberry Pi 3&lt;/a&gt;, a &lt;a href="http://amzn.to/2GG06qm"&gt;case with a fan and a power supply (that has a power switch)&lt;/a&gt;, and a 32GB SD card. That is more storage than I need, but a 16GB card wasn’t much cheaper. I also picked up an &lt;a href="http://amzn.to/2GJjORR"&gt;8 TB Seagate external hard drive&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While I think anyone can set this up, I will say that I have a decent working knowledge of Linux, which helped getting started and troubleshooting issues I ran into. If you aren’t very familiar with  Linux and the terminal, you can still get all of this set up, but debugging issues and working through it all might be a little more difficult.&lt;/p&gt;
&lt;p&gt;First, I would suggest setting up SSH on your pi so you don’t have to go back and forth between working on the pi and another machine:
&lt;a href="https://www.raspberrypi.org/documentation/remote-access/ssh/"&gt;https://www.raspberrypi.org/documentation/remote-access/ssh/&lt;/a&gt;
I’d also recommend setting it up with an ssh key so you don’t have to enter your PW every time you log in:
&lt;a href="https://www.thegeekstuff.com/2008/11/3-steps-to-perform-ssh-login-without-password-using-ssh-keygen-ssh-copy-id/"&gt;https://www.thegeekstuff.com/2008/11/3-steps-to-perform-ssh-login-without-password-using-ssh-keygen-ssh-copy-id/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I didn’t do either of those at first and it became annoying switching back and forth between machines since I only have one keyboard/mouse/display set up. If you have a separate keyboard, mouse, and display for your Pi, it might not be as helpful right away, but I’d still recommend it.&lt;/p&gt;
&lt;p&gt;My first step was to try to set up the NAS. I ran through this tutorial:
&lt;a href="https://www.howtogeek.com/139433/how-to-turn-a-raspberry-pi-into-a-low-power-network-storage-device/"&gt;https://www.howtogeek.com/139433/how-to-turn-a-raspberry-pi-into-a-low-power-network-storage-device/&lt;/a&gt;, but I hit a wall near the end. I think this was written for Windows 7 and I’m on Windows 10 where mapping a network drive looks different. I also think I may have skipped the last step of setting the samba PW for my user account, which may have been the bigger problem. Here are some notes about where I did things a little differently: &lt;/p&gt;
&lt;p&gt;My external HDD had 4 partitions when I started the process and they automatically mounted, so I skipped the parts about mkdir /media/USBHDD1 and mount...USBHDD1&lt;/p&gt;
&lt;p&gt;'security = user' was not already in the samba config file commented out so I just added it in the authentication section. For the section they tell you to add to the config file, I made four copies of that at the bottom of the file, one for each partition that I have.&lt;/p&gt;
&lt;p&gt;For the last part of the tutorial about adding the network drives on my Windows PC I had to follow different directions and go to Windows Explorer -&amp;gt; This PC -&amp;gt; Map network drive (in the file menu) -&amp;gt; then in the file for folder enter '\raspberrypi\', then I clicked 'Browse' which let me select the folder (I divided my external HDD into a few partitions). I was also to manually enter '\raspberrypi\nameOfFolder' to get it to see a drive. I repeated that for each of my drive partitions.&lt;/p&gt;
&lt;p&gt;One mistake I made was only setting up one of the partitions in fstab. This caused me some serious issues with permissions when trying to set up backups with rsync. &lt;/p&gt;
&lt;p&gt;I’m in the process of automating my backups. When I finish testing my backup scripts, I’ll be sure to post about it.&lt;/p&gt;
&lt;p&gt;To set my pi up as a plex server, I followed this tutorial:
&lt;a href="https://www.codedonut.com/raspberry-pi/raspberry-pi-plex-media-server/"&gt;https://www.codedonut.com/raspberry-pi/raspberry-pi-plex-media-server/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The only problem I ran into was that I had to change permissions on the folder ‘/media/pi’ where it automounted my drives because Plex couldn't access them. The permissions on the drive folders themselves were fine.&lt;/p&gt;
&lt;p&gt;After that small adjustment, I was able to stream a 1080p movie with a bitrate ~10Mb/s over my  local network without any trouble, but I tried streaming one closer to 25 Mb/s and the Pi definitely couldn’t handle it. I’m not sure where exactly the limit is, but that is something to note.&lt;/p&gt;
&lt;p&gt;Finally, I wanted to test how resilient this set up is so I made sure I could restart while ssh’d and even shut down the pi.&lt;/p&gt;
&lt;p&gt;To restart from command line:
‘sudo reboot’. This allowed me log back in after a couple of minutes.&lt;/p&gt;
&lt;p&gt;To shutdown then start up the pi:
‘sudo halt’ on the command line. This shuts it down, but the red light stays on (and the fan, so the board is still getting power). I can then use the power button on the AC cable to shut off power, then press it again to turn it back on. When it comes back up, the NAS drives automount and I can see them on my PC and Plex is running.&lt;/p&gt;
&lt;p&gt;One last thing I did, for security, was to create a user other than pi, give it sudo permissions (‘sudo usermod -a -G sudo USERNAME’), and give the ‘pi’ user a much more complicated password so it would be more difficult to hack. I saw one tutorial recommend deleting the pi user account, but I decided that was overkill. At the very least, you should change the default password of you default user if you are going to make the Pi visible on a network.&lt;/p&gt;</content><category term="RaspberryPi"></category><category term="RaspberryPi"></category></entry><entry><title>Unity 2D Tools for Level Building</title><link href="https://programmingmylife.com/2018-02-24-unity-2d-tools-for-level-building.html" rel="alternate"></link><published>2018-02-24T12:50:06+00:00</published><updated>2018-02-24T12:50:06+00:00</updated><author><name>Andrew Mshar</name></author><id>tag:programmingmylife.com,2018-02-24:/2018-02-24-unity-2d-tools-for-level-building.html</id><summary type="html">&lt;p&gt;This week for our local Unity meetup group, I presented an intro to some of the new 2D Tools in Unity (There is an intro about more general Unity topics, so for the 2D stuff skip to 17 minutes in): &lt;a href="https://www.youtube.com/watch?v=xopzxmzFJUs"&gt;https://www.youtube.com/watch?v=xopzxmzFJUs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here are the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This week for our local Unity meetup group, I presented an intro to some of the new 2D Tools in Unity (There is an intro about more general Unity topics, so for the 2D stuff skip to 17 minutes in): &lt;a href="https://www.youtube.com/watch?v=xopzxmzFJUs"&gt;https://www.youtube.com/watch?v=xopzxmzFJUs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here are the links to things I mentioned were outside of the scope of that talk but might be interesting to learn:&lt;/p&gt;
&lt;p&gt;Sprite masks: &lt;a href="https://docs.unity3d.com/Manual/class-SpriteMask.html"&gt;https://docs.unity3d.com/Manual/class-SpriteMask.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2D side scrolling brawler style camera (focus on 9-slicing sprites and new features for sorting): &lt;a href="https://unity3d.com/learn/tutorials/topics/2d-game-creation/introduction-and-goals"&gt;https://unity3d.com/learn/tutorials/topics/2d-game-creation/introduction-and-goals&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Platformer character controller: &lt;a href="https://unity3d.com/learn/tutorials/topics/2d-game-creation/intro-and-session-goals"&gt;https://unity3d.com/learn/tutorials/topics/2d-game-creation/intro-and-session-goals&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/MelvynMay/UnityPhysics2D"&gt;https://github.com/MelvynMay/UnityPhysics2D&lt;/a&gt; - a lot of interesting scenes demoing 2D physics.&lt;/p&gt;
&lt;p&gt;Pretty cool topdown game from Unity to show off tilemap and other 2d features (from a talk at Unite Austin, &amp;gt;https://www.youtube.com/watch?v=RkaEh--qUAY&amp;gt;): &lt;a href="https://github.com/Unity-Technologies/2d-gamedemo-robodash"&gt;https://github.com/Unity-Technologies/2d-gamedemo-robodash&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2D Game Kit - This is a 2D Game that Unity built to show off 2D features, and what a complete project looks like including tools for designers so that they don’t need to dive into the code to create new puzzles, levels, etc. &lt;a href="https://blogs.unity3d.com/2018/02/13/introducing-2d-game-kit-learn-unity-with-drag-and-drop"&gt;https://blogs.unity3d.com/2018/02/13/introducing-2d-game-kit-learn-unity-with-drag-and-drop&lt;/a&gt;/, &lt;a href="https://unity3d.com/learn/tutorials/s/2d-game-kit"&gt;https://unity3d.com/learn/tutorials/s/2d-game-kit&lt;/a&gt;. Unity also recorded a live training for this recently that I’m assuming they will publish soon, but I can’t find a link to it yet.&lt;/p&gt;
&lt;p&gt;Edit: Unity posted the live training for 2D Game Kit here: &lt;a href="https://unity3d.com/learn/tutorials/projects/2d-game-kit/overview-and-goals?playlist=49633"&gt;https://unity3d.com/learn/tutorials/projects/2d-game-kit/overview-and-goals?playlist=49633&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What I covered in the video is using the new Tilemaps and associated features for designing levels in 2D. This was also covered by this Unity Learn tutorial: &lt;a href="https://unity3d.com/learn/tutorials/topics/2d-game-creation/intro-2d-world-building-w-tilemap"&gt;https://unity3d.com/learn/tutorials/topics/2d-game-creation/intro-2d-world-building-w-tilemap&lt;/a&gt;, and this blog post: &lt;a href="https://blogs.unity3d.com/2018/01/25/2d-tilemap-asset-workflow-from-image-to-level/"&gt;https://blogs.unity3d.com/2018/01/25/2d-tilemap-asset-workflow-from-image-to-level/&lt;/a&gt;. This are very thorough and a great reference for these features. I found that there were a couple of things I could talk about not covered in those videos, specifically how to create your own rule and random tiles, and how to create tiles and tilemaps from art that you generate or find yourself.&lt;/p&gt;
&lt;p&gt;Finally, here is the collection of brushes and tiles that Unity has coded that cover a huge range of use cases: &lt;a href="https://github.com/Unity-Technologies/2d-extras"&gt;https://github.com/Unity-Technologies/2d-extras&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The ground sprites I used came from here: &lt;a href="https://bitcan.itch.io/tileset-simples"&gt;https://bitcan.itch.io/tileset-simples&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And the flower sprites I used came from here: &lt;a href="https://onimaru.itch.io/green-platform"&gt;https://onimaru.itch.io/green-platform&lt;/a&gt;&lt;/p&gt;</content><category term="Unity"></category><category term="Unity"></category></entry></feed>