<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Start Coding Now</title><link href="https://startcodingnow.com/" rel="alternate"/><link href="https://startcodingnow.com/feed" rel="self"/><id>https://startcodingnow.com/</id><updated>2024-10-29T00:00:00-07:00</updated><entry><title>Empty Image Fields in Django</title><link href="https://startcodingnow.com/empty-image-fields-in-django" rel="alternate"/><published>2024-10-29T00:00:00-07:00</published><updated>2024-10-29T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2024-10-29:/empty-image-fields-in-django</id><summary type="html">&lt;p&gt;How to set a single value as blank for your Django FileField and ImageField.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;Django FileFields and ImageFields are stored as strings in your database. Don't use &lt;code&gt;null=True&lt;/code&gt; with them, instead use &lt;code&gt;blank=True&lt;/code&gt; and filter by &lt;code&gt;.filter(image__exact="")&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Discovering a Bug&lt;/h2&gt;
&lt;p&gt;I ran into a bug where I was trying to filter a QuerySet by &lt;code&gt;image__isnull=False&lt;/code&gt; to determine if the &lt;code&gt;image&lt;/code&gt; field was set. It ended up returning all objects where &lt;code&gt;image&lt;/code&gt; was not yet set, so I did some digging.&lt;/p&gt;
&lt;h2&gt;How &lt;code&gt;FileField&lt;/code&gt; and &lt;code&gt;ImageField&lt;/code&gt; Look in the Database&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;FileField&lt;/code&gt; and &lt;code&gt;ImageField&lt;/code&gt; are both implemented in your database table as a &lt;code&gt;varchar&lt;/code&gt; field. Note the type column from the psql display table below:&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="n"&gt;Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public.myapp_mymodel"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Column&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;Type&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;Collation&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;Nullable&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;Default&lt;/span&gt;
&lt;span class="o"&gt;--------------+------------------------+-----------+----------+----------------------------------&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&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;bigint&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="o"&gt;|&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="nb nb-Type"&gt;null&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;generated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;default&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;identity&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;image&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;character&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&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="o"&gt;|&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="nb nb-Type"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This stores the relative path of the file, including your &lt;code&gt;upload_to&lt;/code&gt; and filename.&lt;/p&gt;
&lt;p&gt;The problem is that these strings have two values that might mean "empty": &lt;code&gt;None&lt;/code&gt; and &lt;code&gt;""&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;How to Define a Model with Not Required File Fields&lt;/h2&gt;
&lt;p&gt;I had defined my model 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;class&lt;/span&gt; &lt;span class="nc"&gt;MyModel&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;image&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;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"images/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&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;blank&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;But I should have removed the &lt;code&gt;null=True&lt;/code&gt; option:&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;MyModel&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;image&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;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"images/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&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;h2&gt;How to Filter for Empty File Fields&lt;/h2&gt;
&lt;p&gt;Now, I &lt;em&gt;still&lt;/em&gt; cannot filter by &lt;code&gt;image__isnull&lt;/code&gt;, because the &lt;code&gt;image&lt;/code&gt; field should never be set to &lt;code&gt;NULL&lt;/code&gt; in the database and &lt;code&gt;None&lt;/code&gt; in Python.&lt;/p&gt;
&lt;p&gt;But I &lt;strong&gt;can&lt;/strong&gt; filter by &lt;code&gt;.filter(image__exact="")&lt;/code&gt;, which is the empty string.&lt;/p&gt;
&lt;p&gt;Or exclude those values with &lt;code&gt;.exclude(image__exact="")&lt;/code&gt;.&lt;/p&gt;</content><category term="django"/><category term="files"/><category term="images"/></entry><entry><title>Opening Django Model Files in PyMuPDF</title><link href="https://startcodingnow.com/django-files-in-pymupdf" rel="alternate"/><published>2024-10-28T00:00:00-07:00</published><updated>2024-10-28T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2024-10-28:/django-files-in-pymupdf</id><summary type="html">&lt;p&gt;Local and remote files might behave differently.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have been working on a file upload feature for a Django project. We use &lt;code&gt;django-storages&lt;/code&gt; to store files in AWS S3, but we store them on the filesystem for local development.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pymupdf.readthedocs.io/en/latest/index.html"&gt;PyMuPDF&lt;/a&gt; is a Python library for reading and writing PDF files. It's very fast and I've been delighted to use it.&lt;/p&gt;
&lt;p&gt;I ran into a small issue getting my Django file uploads to work with PyMuPDF. This post will outline my notes on how I worked through it.&lt;/p&gt;
&lt;h2&gt;Opening a file from the local filesystem&lt;/h2&gt;
&lt;p&gt;Let's say we have a model &lt;code&gt;SlideDeck&lt;/code&gt; with a &lt;code&gt;FileField&lt;/code&gt; that stores a PDF file:&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;SlideDeck&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;pdf&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;FileField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"slide_decks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"500"&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;And a model &lt;code&gt;SlideImage&lt;/code&gt; that stores an image for each slide in the PDF:&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;SlideImage&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;slide_deck&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;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;SlideDeck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&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;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;img&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;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"slide_decks/images"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"500"&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;And we have a helper function to create a list of &lt;code&gt;SlideImage&lt;/code&gt; objects from a &lt;code&gt;SlideDeck&lt;/code&gt; object:&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;_create_empty_slides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SlideDeck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SlideImage&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;pdf_document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&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="n"&gt;slide_img_objs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SlideImage&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;bulk_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;SlideImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_document&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;pdf_document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&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;slide_img_objs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above works with the Django default &lt;a href="https://docs.djangoproject.com/en/dev/ref/files/storage/#the-filesystemstorage-class"&gt;&lt;code&gt;FileSystemStorage&lt;/code&gt; backend&lt;/a&gt;. But if you're using a remote storage backend like the &lt;a href="https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html"&gt;AWS S3 backend&lt;/a&gt; from &lt;code&gt;django-storages&lt;/code&gt;, the file will &lt;strong&gt;not&lt;/strong&gt; be available on the filesystem.&lt;/p&gt;
&lt;p&gt;We are using AWS S3 and this was the error I ran into when trying to open a file with &lt;code&gt;open(model_obj.file.path)&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;NotImplementedError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;doesn&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;support&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;absolute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Should you open with &lt;code&gt;file.name&lt;/code&gt; or &lt;code&gt;file.path&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;Assuming we have a model &lt;code&gt;FieldFile&lt;/code&gt; object:&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;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model_obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;file.name&lt;/code&gt; returns the relative path to the file.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;file.path&lt;/code&gt; returns the absolute path on the local filesystem.&lt;/p&gt;
&lt;p&gt;We could then rewrite the above &lt;code&gt;_create_empty_slides()&lt;/code&gt; function 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;def&lt;/span&gt; &lt;span class="nf"&gt;_create_empty_slides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SlideDeck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SlideImage&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;pdf_document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slide_img_objs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SlideImage&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;bulk_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;SlideImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_document&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;pdf_document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&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;slide_img_objs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that would still work on the filesystem, but it's not portable to a remote storage backend. More on that later.&lt;/p&gt;
&lt;h2&gt;Opening an &lt;code&gt;InMemoryUploadedFile&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;There's a &lt;a href="https://github.com/pymupdf/PyMuPDF/discussions/1844#discussioncomment-3282681"&gt;GitHub discussion&lt;/a&gt; about uploaded files in PyMuPDF. The author had to specify to seek back to the beginning of the file to open it:&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;import&lt;/span&gt; &lt;span class="nn"&gt;pymupdf&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_pages_of_uploaded_pdf&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;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Get the file&lt;/span&gt;
    &lt;span class="n"&gt;uploaded_pdf&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;FILES&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="s2"&gt;"user_uploaded_pdf"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Set read location&lt;/span&gt;
    &lt;span class="n"&gt;uploaded_pdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Open the file&lt;/span&gt;
    &lt;span class="n"&gt;pdf_document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uploaded_pdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;filetype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"pdf"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;num_pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf_document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&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;num_pages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Opening a file from remote storage&lt;/h2&gt;
&lt;p&gt;The PyMuPDF docs have a section on &lt;a href="https://pymupdf.readthedocs.io/en/latest/how-to-open-a-file.html#opening-remote-files"&gt;opening remote files&lt;/a&gt;. They outline getting the remote file content as a &lt;code&gt;bytes&lt;/code&gt; object, and then opening the file with PyMuPDF:&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;import&lt;/span&gt; &lt;span class="nn"&gt;pymupdf&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;


&lt;span class="n"&gt;r&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="s2"&gt;"https://mupdf.com/docs/mupdf_explored.pdf"&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;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I started to implement this in our project, but didn't want to have to check for what backend we're using every time we want to open a file.&lt;/p&gt;
&lt;p&gt;So instead, we can leverage the &lt;code&gt;default_storage&lt;/code&gt; class from Django to open the file:&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;import&lt;/span&gt; &lt;span class="nn"&gt;pymupdf&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.files.storage&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;default_storage&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_pages_from_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SlideDeck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Get the file bytes data&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;default_storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Open the file with pymupdf&lt;/span&gt;
    &lt;span class="n"&gt;pdf_document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;num_pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pdf_document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&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;num_pages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This reads the entire file into memory as a &lt;code&gt;bytes&lt;/code&gt; object in the &lt;code&gt;content&lt;/code&gt; variable. If you're low on memory, this might create problems.&lt;/p&gt;
&lt;h2&gt;Opening a file with &lt;code&gt;pymupdf&lt;/code&gt; as a context manager&lt;/h2&gt;
&lt;p&gt;We can rewrite the above function to use a context manager, automatically closing the file when we're done:&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;import&lt;/span&gt; &lt;span class="nn"&gt;pymupdf&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.files.storage&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;default_storage&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_pages_from_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SlideDeck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Get the file bytes data&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;default_storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slide_deck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Open the file with pymupdf&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pdf_document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;num_pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_document&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;num_pages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="django"/><category term="files"/><category term="pdf"/></entry><entry><title>Custom Django Python Migration</title><link href="https://startcodingnow.com/django-python-custom-migration" rel="alternate"/><published>2024-10-03T00:00:00-07:00</published><updated>2024-10-03T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2024-10-03:/django-python-custom-migration</id><summary type="html">&lt;p&gt;A quick outline of how to create a custom migration in Django.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Sometimes I find myself wanting to add some model objects along with a feature. You can make a custom migration to handle that!&lt;/p&gt;
&lt;h2&gt;Overview of what we will do&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Create an empty migration&lt;/li&gt;
&lt;li&gt;Create a forwards operation&lt;/li&gt;
&lt;li&gt;Create a backwards operation&lt;/li&gt;
&lt;li&gt;Tie those operations to the migration&lt;/li&gt;
&lt;li&gt;Apply, un-apply, and re-apply the migration&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Whole file contents&lt;/h2&gt;
&lt;p&gt;For a tl;dr, make an empty migration and populate it 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="c1"&gt;# Generated by Django 5.1.1 on 2024-10-03 12:59&lt;/span&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;migrations&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema_editor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;MyModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my_app'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MyModel'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;MyModel&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;field_name1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_name2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"describe"&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;backwards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema_editor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;MyModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my_app'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MyModel'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;MyModel&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;field_name1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_name2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"describe"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&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;"app_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"0001_initial"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# could be more here&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunPython&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backwards&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;h2&gt;Create an empty migration&lt;/h2&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&lt;span class="w"&gt; &lt;/span&gt;&lt;app_name&gt;&lt;span class="w"&gt; &lt;/span&gt;--empty
&lt;/app_name&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I tend to add a descriptive name as well:&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&lt;span class="w"&gt; &lt;/span&gt;&lt;app_name&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;&lt;description&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--empty
&lt;/description&gt;&lt;/app_name&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The empty file should look something 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="c1"&gt;# Generated by Django 5.1.1 on 2024-10-03 12:59&lt;/span&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;migrations&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&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;"app_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"0001_initial"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# could be more here&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Create the change you want to see&lt;/h2&gt;
&lt;p&gt;Next, I'm going to add a top-level function that runs the code I seek to run:&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;forwards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema_editor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;MyModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my_app'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MyModel'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;MyModel&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;field_name1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_name2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"describe"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note the part where we set the model with &lt;code&gt;apps.get_model&lt;/code&gt;. This ensures the migration uses the correct version of the model at the time the migration is applied even if the model changes later.&lt;/p&gt;
&lt;h2&gt;Undo the change you made for migrating back in time&lt;/h2&gt;
&lt;p&gt;Then, we'll write the equivalent of an "undo" operation in another top-level function:&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;backwards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema_editor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;MyModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my_app'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MyModel'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;MyModel&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;field_name1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_name2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"describe"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Tie these operations to the migration&lt;/h2&gt;
&lt;p&gt;Now we need to tell Django that we're looking to run Python code for this migration:&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;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunPython&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backwards&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;h2&gt;Apply, un-apply, and re-apply the migration&lt;/h2&gt;
&lt;p&gt;Assuming, like our example, that the migration we've been working on is &lt;code&gt;0002&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="c1"&gt;# Apply the migration&lt;/span&gt;
python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate&lt;span class="w"&gt; &lt;/span&gt;&lt;app_name&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0002&lt;/span&gt;

&lt;span class="c1"&gt;# Check for the expected change&lt;/span&gt;

&lt;span class="c1"&gt;# Un-apply the migration&lt;/span&gt;
python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate&lt;span class="w"&gt; &lt;/span&gt;&lt;app_name&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0001&lt;/span&gt;

&lt;span class="c1"&gt;# Check for reversal of change&lt;/span&gt;

&lt;span class="c1"&gt;# Re-apply the migration&lt;/span&gt;
python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate&lt;span class="w"&gt; &lt;/span&gt;&lt;app_name&lt;span class="w"&gt; &lt;span class="m"&gt;0002&lt;/span&gt;
&lt;/app_name&lt;span&gt;&lt;/app_name&gt;&lt;/app_name&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="django"/><category term="migrations"/></entry><entry><title>DjangoCon US 2024 - Short Summary</title><link href="https://startcodingnow.com/djangocon-us-2024" rel="alternate"/><published>2024-09-25T00:00:00-07:00</published><updated>2024-09-25T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2024-09-25:/djangocon-us-2024</id><summary type="html">&lt;p class="first last"&gt;A by-no-means-comprehensive list of notes from DjangoCon US 2024.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I apologize in advance for (1) the brevity, and (2) that not every talk is listed here. I didn't get a chance to watch them all yet. I do not mean any disrespect to any speaker who spoke, but is not listed here.&lt;/p&gt;
&lt;p&gt;Hopefully this can serve as a taste of the conference. I had a great time at DjangoCon US 2024, and I hope to see you next year!&lt;/p&gt;
&lt;div class="section" id="day-1"&gt;
&lt;h2&gt;Day 1&lt;/h2&gt;
&lt;div class="section" id="learning-teaching"&gt;
&lt;h3&gt;Learning, Teaching&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.sheenaoc.com/"&gt;Sheena O'Connell&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/keynote-monday/"&gt;Power to the People who Teach the People&lt;/a&gt; (&lt;a class="reference external" href="https://sheenarbw.github.io/pres-power-to-the-educators-djangoconus-2024/"&gt;Sheena's slides&lt;/a&gt;)&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://amzn.to/47yVVdH"&gt;"Make It Stick"&lt;/a&gt; by Peter C. Brown, Henry I. Roediger III, and Mark A. McDaniel.&lt;/li&gt;
&lt;li&gt;Mastery-based Learning&lt;/li&gt;
&lt;li&gt;Self-efficacy&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="spa-vs-progressive-enhancement"&gt;
&lt;h3&gt;SPA vs Progressive Enhancement&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://everydaysuperpowers.dev/"&gt;Chris May&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/choosing-wisely-spa-vs-htmx-for-your-next-web-project/"&gt;Choosing Wisely: SPA vs. HTMX for Your Next Web Project&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;"Progressive enhancement" means your site still works without JavaScript.&lt;/li&gt;
&lt;li&gt;The longer someone is on a page, the more likely a SPA would be useful.
- 20+ operations on the same subset of data?
- UI for chat or conversations?&lt;/li&gt;
&lt;li&gt;Katie Sylor-Miller's "Component Islands" explained in &lt;a class="reference external" href="https://jasonformat.com/islands-architecture/"&gt;"Islands Architecture"&lt;/a&gt; by Jason Miller.&lt;/li&gt;
&lt;li&gt;The browser can handle a lot of things without JavaScript.&lt;/li&gt;
&lt;li&gt;Streamed HTML components&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://infrequently.org/series/reckoning"&gt;Article series from Alex Russell&lt;/a&gt; on JavaScript excess.&lt;/li&gt;
&lt;li&gt;Packages: &lt;a class="reference external" href="https://www.django-unicorn.com/"&gt;django-unicorn&lt;/a&gt;, &lt;a class="reference external" href="https://htmx.org/"&gt;htmx&lt;/a&gt;, &lt;a class="reference external" href="https://alpinejs.dev/"&gt;alpinejs&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/carltongibson/django-template-partials"&gt;django-template-partials&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/EmilStenstrom/django-components"&gt;django-components&lt;/a&gt;, &lt;a class="reference external" href="https://datastar.fly.dev/"&gt;datastar&lt;/a&gt;, &lt;a class="reference external" href="https://unpoly.com/"&gt;unpoly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="error-culture"&gt;
&lt;h3&gt;Error Culture&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.ryancheley.com/"&gt;Ryan Cheley&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/error-culture/"&gt;Error Culture&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Reactive, firefighting Culture&lt;/li&gt;
&lt;li&gt;Provide useful and relevant context along with your alerts: actionable, important items sent to the correct people.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="lightning-talks"&gt;
&lt;h3&gt;Lightning Talks&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Calvin Hendry-Parker: &lt;a class="reference external" href="https://github.com/sixfeetup/scaf"&gt;scaf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Paul Everitt: &lt;a class="reference external" href="https://peps.python.org/pep-0750/"&gt;PEP750&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tim Allen: &lt;a class="reference external" href="https://pypi.org/project/pyromania/"&gt;pyromania&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Jay Miller: learn about Python&lt;/li&gt;
&lt;li&gt;Santos: &lt;a class="reference external" href="https://github.com/PyCQA/bandit"&gt;bandit&lt;/a&gt;, &lt;a class="reference external" href="https://codeql.github.com/"&gt;CodeQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Sangeeta Jadoonanan: &lt;a class="reference external" href="https://djangostickers.com/"&gt;Django Stickers&lt;/a&gt;, &lt;a class="reference external" href="https://open.spotify.com/show/0CsNsFFmeUHUdm5W2rE11z?si=23d94ef3d14a4c4a"&gt;Django Brew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Megan Voss: &lt;a class="reference external" href="https://mk.bcgsc.ca/carpalx/"&gt;Carpalx&lt;/a&gt;, &lt;a class="reference external" href="https://forum.colemak.com/topic/1858-learn-colemak-in-steps-with-the-tarmak-layouts/"&gt;Tarmak&lt;/a&gt; (transitional colemak layout)&lt;/li&gt;
&lt;li&gt;Norma Miller: &lt;a class="reference external" href="https://whitecoatcaptioning.com/"&gt;White Coat Captioning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="django-forms"&gt;
&lt;h3&gt;Django Forms&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://joshthomas.dev/"&gt;Josh Thomas&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/an-opinionated-guide-to-modern-django-forms/"&gt;An Opinionated Guide to Modern Django Forms&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Forms &lt;code&gt;as_field_group&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/carltongibson/django-template-partials"&gt;django-template-partials&lt;/a&gt; and &lt;a class="reference external" href="https://django-cotton.com/"&gt;django-cotton&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="sql-queries"&gt;
&lt;h3&gt;SQL Queries&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.christopheradams.info/"&gt;Chris Adams&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/a-related-matter-optimizing-your-webapp-by-using-django-debug-toolbar-select-related-and-prefetch-related/"&gt;A Related Matter: Optimizing your webapp by using django-debug-toolbar, select_related(), and prefetch_related()&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://django-debug-toolbar.readthedocs.io/en/stable/"&gt;django-debug-toolbar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;select_related()&lt;/code&gt; for foreign keys&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prefetch_related()&lt;/code&gt; for many-to-many relationships and reverse foreign keys&lt;/li&gt;
&lt;li&gt;Make sure to inspect the 95th and 99th percentile queries on sites with high traffic because highly active users may be encountering problems that your other users don't feel.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="passkeys"&gt;
&lt;h3&gt;Passkeys&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="http://ryanhiebert.com/"&gt;Ryan Hiebert&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/passkeys-your-password-free-future/"&gt;Passkeys: Your password-free future&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Passkeys are in development for &lt;a class="reference external" href="https://docs.allauth.org/en/latest/"&gt;django-allauth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Passkeys are inherently multi-factor (you own the device, you have the fingerprint/retina)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://webauthn.guide/"&gt;webauthn.guide&lt;/a&gt;, &lt;a class="reference external" href="https://webauthn.io/"&gt;webauthn.io&lt;/a&gt;, &lt;a class="reference external" href="https://passkeys.dev/"&gt;passkeys.dev&lt;/a&gt;, &lt;a class="reference external" href="https://fy.blackhats.net.au/blog/2024-04-26-passkeys-a-shattered-dream/"&gt;Passkeys: A Shattered Dream&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="running-django-in-production"&gt;
&lt;h3&gt;Running Django in Production&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.codered.cloud/"&gt;Vince Salvino&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/one-thousand-and-one-django-sites/"&gt;One Thousand and One Django Sites&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Error detection might auto-trigger a self-healing patch or rollback&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.maxmind.com/en/home"&gt;MaxMind&lt;/a&gt; for fraud detection&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.codered.cloud/"&gt;codered.cloud&lt;/a&gt; for deploying Django and Wagtail apps&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="security"&gt;
&lt;h3&gt;Security&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://blog.adarshd.dev/"&gt;Adarsh Divakaran&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/pygoat-learn-django-security-the-hard-way/"&gt;Pygoat - Learn django security the hard way&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/adeyosemanputra/pygoat"&gt;pygoat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://owasp.org/www-project-top-ten/"&gt;OWASP Top 10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.hacksplaining.com/lessons"&gt;Hacksplaining&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://haveibeenpwned.com/"&gt;haveibeenpwned&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="day-2"&gt;
&lt;h2&gt;Day 2&lt;/h2&gt;
&lt;div class="section" id="self-improvement"&gt;
&lt;h3&gt;Self-Improvement&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://pythonbynight.com/"&gt;Mario Munoz&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/keynote-tuesday/"&gt;How To Be A Developer and Other Lies We Tell Ourselves&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The way to be a better developer is to be a better human.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="htmx-alpine-js"&gt;
&lt;h3&gt;htmx + Alpine.js&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://2024.djangocon.us/talks/django-alpine-js-htmx-ups-downs/"&gt;Karen Tracey&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/django-alpine-js-htmx-ups-downs/"&gt;Django + Alpine.js + htmx Ups &amp;amp; Downs&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;htmx for interacting with the server&lt;/li&gt;
&lt;li&gt;Alpine.js for interacting with the user&lt;/li&gt;
&lt;li&gt;These tools increase the shared knowledge between the frontend and backend&lt;/li&gt;
&lt;li&gt;django-formset, &lt;a class="reference external" href="https://github.com/carltongibson/django-template-partials"&gt;django-template-partials&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/EmilStenstrom/django-components"&gt;django-components&lt;/a&gt;, and &lt;a class="reference external" href="https://django-cotton.com/"&gt;django-cotton&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- django-formset: https://pypi.org/project/django-formset/ --&gt;
&lt;/div&gt;
&lt;div class="section" id="maps"&gt;
&lt;h3&gt;Maps&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.paulox.net/"&gt;Paolo Melchiorre&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/maps-with-django/"&gt;Maps with Django&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;SQLite uses SpatiaLite&lt;/li&gt;
&lt;li&gt;PostgreSQL uses PostGIS&lt;/li&gt;
&lt;li&gt;Django uses GeoDjango&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="lightning-talks-1"&gt;
&lt;h3&gt;Lightning Talks&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.linkedin.com/in/richardterry/"&gt;Richard Terry&lt;/a&gt;: &lt;a class="reference external" href="https://github.com/radiac/nanodjango"&gt;nanodjango&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://twitter.com/adamcanfly"&gt;Adam Fast&lt;/a&gt;: what it takes to bring DjangoCon to your city&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.linkedin.com/in/jib-adegunloye-3673a0a0/"&gt;Jib Adegunloye&lt;/a&gt;: Brute forcing a new MongoDB integration for Django&lt;/li&gt;
&lt;li&gt;Lilian: Djangonaut space mentorship program&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.linkedin.com/in/reddy-tintaya-108142165/"&gt;Reddy Tintaya&lt;/a&gt;: TDD&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.laceyhenschel.com/"&gt;Lacey Henschel&lt;/a&gt;: &lt;a class="reference external" href="https://github.com/django-commons"&gt;Django Commons&lt;/a&gt; supporting community package maintenance&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://vurt.org/"&gt;Daniele Procida&lt;/a&gt;: &lt;a class="reference external" href="https://vurt.org/articles/twelve-rules/"&gt;Twelve rules for job applications and interviews&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.rachellcalhoun.com/"&gt;Rachell Calhoun&lt;/a&gt;: &lt;a class="reference external" href="https://djangogirls.org/"&gt;Django Girls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.sheenaoc.com/"&gt;Sheena O'Connell&lt;/a&gt;: DjangoCon Africa 2023&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="end-to-end-testing"&gt;
&lt;h3&gt;End-to-End Testing&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://taprobaneconsulting.tech/"&gt;Avindra Fernando&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/lessons-from-e2e-testing-web-applications/"&gt;Lessons from E2E Testing Web Applications&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Cypress and Playwright&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://testing-playground.com/"&gt;Testing Playground&lt;/a&gt; for selecting elements in your tests&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="atomic-transactions"&gt;
&lt;h3&gt;Atomic Transactions&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.linkedin.com/in/eva-nanyonga-143b6b33/"&gt;Eva Nanyonga&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/only-reliable-data-protecting-database-integrity/"&gt;Only reliable Data: Protecting Database Integrity&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.djangoproject.com/en/5.1/topics/db/transactions/"&gt;Database transactions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="plugins"&gt;
&lt;h3&gt;Plugins&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://simonwillison.net/"&gt;Simon Willison&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/how-to-design-and-implement-extensible-software-with-plugins/"&gt;How to design and implement extensible software with plugins&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The beauty of plugins is that they allow you to extend the functionality of an app without changing the app itself&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://pluggy.readthedocs.io/en/stable/"&gt;pluggy&lt;/a&gt; and &lt;a class="reference external" href="https://www.piwheels.org/project/djp/"&gt;djp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="migrations"&gt;
&lt;h3&gt;Migrations&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://twitter.com/timb07"&gt;Tim Bell&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/deploying-django-migrations-at-kraken-scale/"&gt;Deploying Django migrations at Kraken scale&lt;/a&gt; (&lt;a class="reference external" href="https://docs.google.com/presentation/d/1EqB2ZwelYzfr5AGM_b0os0yp7ma3kgrLs-90z7ENXaU/pub?start=false&amp;amp;loop=false&amp;amp;delayms=5000&amp;amp;slide=id.g196a20def4a_0_231"&gt;Tim's slides&lt;/a&gt;)&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Locks&lt;/li&gt;
&lt;li&gt;Break up migrations into smaller chunks&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://posthog.com/blog/async-migrations"&gt;"Async Migrations"&lt;/a&gt; from Yakko Majuri at Posthog&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="large-scale-project-considerations"&gt;
&lt;h3&gt;Large Scale Project Considerations&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://zagaran.com/"&gt;Benjamin "Zags" Zagorsky&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/fighting-homelessness-with-django/"&gt;Fighting Homelessness with Django&lt;/a&gt; (&lt;a class="reference external" href="https://drive.google.com/file/d/1CMqTpKHgAjnMParttL29ppxKw4XaVdZA/view?usp=drive_link"&gt;Zags' slides&lt;/a&gt;)&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Used Vue with one Vue app per Django template that needed it&lt;/li&gt;
&lt;li&gt;Sticky sessions on load balancer&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="day-3"&gt;
&lt;h2&gt;Day 3&lt;/h2&gt;
&lt;div class="section" id="django-fellows"&gt;
&lt;h3&gt;Django Fellows&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://fosstodon.org/@nessita"&gt;Natalia Bidart&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/keynote-wednesday/"&gt;The Fellowship of the Pony&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Who is a Django Fellow and what do they do?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="release-notes"&gt;
&lt;h3&gt;Release Notes&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://mastodon.social/@sarahboyce"&gt;Sarah Boyce&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/hidden-gems-of-django-5-x/"&gt;Hidden gems of Django 5.x&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Not all changes are listed in the release notes&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="django-history"&gt;
&lt;h3&gt;Django History&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://frankwiles.social/@frank"&gt;Frank Wiles&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/a-brief-history-of-django/"&gt;A Brief History of Django&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;How Django came to be (starting out known as "The CMS")&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="lightning-talks-2"&gt;
&lt;h3&gt;Lightning Talks&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://fosstodon.org/@nessita"&gt;Natalia Bidart&lt;/a&gt;: &lt;a class="reference external" href="https://inclusivenaming.org/"&gt;https://inclusivenaming.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://epicserve.com/"&gt;Brent O'Connor&lt;/a&gt;: &lt;a class="reference external" href="https://github.com/epicserve/django-base-site"&gt;https://github.com/epicserve/django-base-site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.linkedin.com/in/levi-mann-8b34a6ab/"&gt;Levi Mann&lt;/a&gt;: Debugging SQL&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.linkedin.com/in/josh-obrien-4639ab68/?originalSubdomain=uk"&gt;Josh O'Brien&lt;/a&gt;: Tips for improving your CV&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.linkedin.com/in/sameure/"&gt;Sam Eure&lt;/a&gt;: Custom querysets&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.instagram.com/melizeche/"&gt;Marcelo Elizeche Lando&lt;/a&gt;: &lt;a class="reference external" href="https://airelib.re/"&gt;https://airelib.re/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.linkedin.com/in/abigail-afi-gbadago/?originalSubdomain=gh"&gt;Abigail (Afi) G.&lt;/a&gt;: Updates from the Ghanaian community&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.linkedin.com/in/jongould?originalSubdomain=uk"&gt;Jon Gould&lt;/a&gt;: Light work now makes your next job search easier&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.linkedin.com/in/turicas/"&gt;Alvaro Justen&lt;/a&gt; (Turicas): Storing data, vcard info in QR codes&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/thibaudcolas"&gt;Thibaud Colas&lt;/a&gt;: DSF updates&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="webrtc"&gt;
&lt;h3&gt;WebRTC&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.kenwhitesell.com/"&gt;Ken Whitesell&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/webrtc-with-django-channels-htmx-and-coturn/"&gt;WebRTC with Django, Channels, HTMX, and coturn&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;STUN = "What's my IP?" for WebRTC; like a ping&lt;/li&gt;
&lt;li&gt;TURN = distribution hub for WebRTC data&lt;/li&gt;
&lt;li&gt;SDP = data defining the content of the connection&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation"&gt;https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;chrome://webrtc-internals&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="postgresql"&gt;
&lt;h3&gt;PostgreSQL&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.crunchydata.com/blog/author/elizabeth-christensen"&gt;Elizabeth Christensen&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/a-guided-tour-through-postgres-internals/"&gt;A Guided Tour Through Postgres Internals&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Tons of tips on how to use PostgreSQL and the psql commmand&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="django-user-model"&gt;
&lt;h3&gt;Django User Model&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://learndjango.com/"&gt;Will Vincent&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/django-user-model-past-present-and-future/"&gt;Django User Model: Past, Present, and Future&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Did you know Django has &lt;a class="reference external" href="https://code.djangoproject.com/wiki/TracGuide"&gt;a wiki&lt;/a&gt;?&lt;/li&gt;
&lt;li&gt;Django's current auth provides friction for new users trying to learn Django&lt;/li&gt;
&lt;li&gt;Suggesting a profile model, ship with a login template, add Profile model to docs&lt;/li&gt;
&lt;li&gt;Simon Willison also suggested maybe a profile JSONField on the User model&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="bootstrapping"&gt;
&lt;h3&gt;Bootstrapping&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://noumenal.es/"&gt;Carlton Gibson&lt;/a&gt; - &lt;a class="reference external" href="https://2024.djangocon.us/talks/api-maybe-bootstrapping-a-web-application-circa-2024/"&gt;API Maybe: Bootstrapping a Web Application circa 2024&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Django is the perfect framework when money is tight&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="django"/><category term="conference"/></entry><entry><title>Understanding the Template Method Pattern in Python</title><link href="https://startcodingnow.com/template-method-design-pattern" rel="alternate"/><published>2024-08-24T00:00:00-07:00</published><updated>2024-08-24T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2024-08-24:/template-method-design-pattern</id><summary type="html">&lt;p class="first last"&gt;Template your business logic algorithms to swap out different services without breaking your application.&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="introduction"&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I found myself wanting to write an extensible class for handling a few fancy new APIs out there. There are a &lt;strong&gt;ton&lt;/strong&gt; that do the same thing, and the field is constantly evolving, so it's useful to be able to easily switch from one to the next.&lt;/p&gt;
&lt;p&gt;I chose the Template Method design pattern. This is useful when you have a series of steps that need to be executed in a specific order, but the implementation of some steps may vary.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-is-the-template-method-pattern"&gt;
&lt;h2&gt;What is the Template Method Pattern?&lt;/h2&gt;
&lt;p&gt;The Template Method pattern defines the skeleton of an algorithm in a base class but lets subclasses override specific steps of the algorithm without changing its structure. It's called "template method" because it defines a template for an algorithm's steps.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-simple-example-coffee-and-tea"&gt;
&lt;h2&gt;A Simple Example: Coffee and Tea&lt;/h2&gt;
&lt;p&gt;Let's illustrate this pattern with a simple example of brewing beverages. We'll create a base class for brewing beverages and then implement specific classes for coffee and tea.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ABC&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;abstractmethod&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Beverage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&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;prepare&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;boil_water&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;brew&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;pour_in_cup&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;add_condiments&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;boil_water&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Boiling water"&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;pour_in_cup&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Pouring into cup"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;brew&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="k"&gt;pass&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_condiments&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="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Coffee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Beverage&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;brew&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Dripping coffee through filter"&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;add_condiments&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Adding sugar and milk"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Beverage&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;brew&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Steeping the tea"&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;add_condiments&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Adding lemon"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Usage&lt;/span&gt;
&lt;span class="n"&gt;coffee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Coffee&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;tea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tea&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Making coffee:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Making tea:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this example, &lt;tt class="docutils literal"&gt;Beverage&lt;/tt&gt; is our abstract base class that defines the template method &lt;tt class="docutils literal"&gt;prepare()&lt;/tt&gt;. This method outlines the steps for preparing a beverage, but leaves the implementation of &lt;tt class="docutils literal"&gt;brew()&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;add_condiments()&lt;/tt&gt; to the subclasses.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="benefits-of-the-template-method-pattern"&gt;
&lt;h2&gt;Benefits of the Template Method Pattern&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Reduce duplication by implementing the common parts of the algorithm in the base class.&lt;/li&gt;
&lt;li&gt;Subclasses can easily override certain steps without changing the overall algorithm structure.&lt;/li&gt;
&lt;li&gt;The base class handles the algorithm's structure, while the subclasses focus on the specific details of each step.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As long as you're clear on the data structure you want your methods to return, you can swap in many different subclasses.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="real-world-applications"&gt;
&lt;h2&gt;Real-World Applications&lt;/h2&gt;
&lt;p&gt;The Template Method pattern is widely used in frameworks and libraries. For instance:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;In web frameworks, the base controller class might define a template method for handling HTTP requests, with subclasses implementing specific behaviors for different routes.&lt;/li&gt;
&lt;li&gt;In testing frameworks, the setup-run-teardown process is often implemented as a template method, allowing test classes to override specific parts of this process.&lt;/li&gt;
&lt;li&gt;When calling external services, your template transforms the API response into the data structure your application is expecting.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="example-template-class"&gt;
&lt;h3&gt;Example Template Class&lt;/h3&gt;
&lt;p&gt;You might want to call a nutrition facts API to get info about food, but not feel locked in to a single API provider. You could write a &lt;tt class="docutils literal"&gt;NutritionFacts&lt;/tt&gt; base class which outlines the &lt;tt class="docutils literal"&gt;get_nutrition_facts&lt;/tt&gt; method and returns a Django &lt;tt class="docutils literal"&gt;NutritionInfo&lt;/tt&gt; model object, outlining the &lt;tt class="docutils literal"&gt;fat&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;carbohydrates&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;protein&lt;/tt&gt; in a particular meal.&lt;/p&gt;
&lt;p&gt;Then, when you're fed up (get it?) with the &lt;a class="reference external" href="https://spoonacular.com/food-api"&gt;Spoonacular API&lt;/a&gt;, you can implement the &lt;a class="reference external" href="https://www.edamam.com/"&gt;Edamam API&lt;/a&gt; with .&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The Template Method pattern allows for the creation of flexible and reusable code structures, making it easier to implement variations of an algorithm while keeping the overall process consistent. By understanding and applying this pattern, you can write more maintainable and extensible code in your Python projects.&lt;/p&gt;
&lt;p&gt;Remember, while design patterns like the Template Method are useful, they should be applied judiciously. If you're only making one class, it might not be helpful to make that inherit from a base class.&lt;/p&gt;
&lt;p&gt;Best of luck in your design endeavors.&lt;/p&gt;
&lt;/div&gt;
</content><category term="python"/><category term="design-patterns"/></entry><entry><title>Custom Error Messages on Model Deletion in the Django Admin</title><link href="https://startcodingnow.com/custom-django-admin-messages" rel="alternate"/><published>2024-08-21T00:00:00-07:00</published><updated>2024-08-21T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2024-08-21:/custom-django-admin-messages</id><summary type="html">&lt;p class="first last"&gt;You can add your own custom logic to your model deletion and provide helpful messages to the user in the Django admin.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I have been working on a project where we might want to delete models even just for testing purposes, but we don't want to &lt;em&gt;accidentally&lt;/em&gt; delete models.&lt;/p&gt;
&lt;div class="section" id="protect-model-deletion"&gt;
&lt;h2&gt;Protect Model Deletion&lt;/h2&gt;
&lt;p&gt;Let's say you have a Django model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&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;User&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;name&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;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;email&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;EmailField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Rental&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;name&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;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;current_renter&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;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&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;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&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;blank&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;/pre&gt;&lt;/div&gt;
&lt;p&gt;At some point, I might sell and delete my Rental object, but I don't want to do that if I have someone renting it at the moment.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="override-model-delete-method"&gt;
&lt;h2&gt;Override Model &lt;tt class="docutils literal"&gt;delete()&lt;/tt&gt; Method&lt;/h2&gt;
&lt;p&gt;We can put in our own custom logic to prevent this deletion if there's an active renter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;  &lt;span class="c1"&gt;# new&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Rental&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="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&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="c1"&gt;# Check if someone is renting at the moment&lt;/span&gt;
        &lt;span class="k"&gt;if&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;user_set&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ValidationError&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;"Cannot delete rental &lt;/span&gt;&lt;span class="si"&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; while someone is in it."&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# If no renter, proceed with deletion&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;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&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;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="message-the-user-by-overriding-the-delete-model-method"&gt;
&lt;h2&gt;Message the User by Overriding the &lt;tt class="docutils literal"&gt;delete_model()&lt;/tt&gt; Method&lt;/h2&gt;
&lt;p&gt;In our Model's Admin class, we can override the &lt;tt class="docutils literal"&gt;delete_model()&lt;/tt&gt; method to give the user feedback as to &lt;em&gt;why&lt;/em&gt; we stopped their deletion so they aren't left totally confused:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&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.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;


&lt;span class="nd"&gt;@admin&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;Rental&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RentalAdmin&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;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"current_renter"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;list_filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"current_renter"&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
    &lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"current_renter__name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"current_renter__email"&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;delete_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="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&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;delete_model&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;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message_user&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="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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will pass along our error message through the &lt;a class="reference external" href="https://docs.djangoproject.com/en/dev/ref/contrib/messages/"&gt;Django Messages framework&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;ModelAdmin.delete_model()&lt;/tt&gt; method &lt;a class="reference external" href="https://github.com/django/django/blob/519087819ed6e8bfbe6be208df71a7df19f23a58/django/contrib/admin/options.py#L1296"&gt;normally calls the object's deletion&lt;/a&gt;. This is what we just overrode, and we can use the &lt;tt class="docutils literal"&gt;ValidationError&lt;/tt&gt; we've raise to communicate across methods.&lt;/p&gt;
&lt;p&gt;Neat.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="customize-the-deletion-http-response-by-overriding-the-response-delete-method"&gt;
&lt;h2&gt;Customize the Deletion HTTP Response by Overriding the &lt;tt class="docutils literal"&gt;response_delete()&lt;/tt&gt; Method&lt;/h2&gt;
&lt;p&gt;You might notice that if you test this code, you'll get two messages: the one you wrote that says the deletion failed, and another one that said it succeeded.&lt;/p&gt;
&lt;p&gt;Weird!&lt;/p&gt;
&lt;p&gt;That's because &lt;a class="reference external" href="https://github.com/django/django/blob/519087819ed6e8bfbe6be208df71a7df19f23a58/django/contrib/admin/options.py#L1680"&gt;Django's method version is cuing up a success message&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can override this method an add a check related to our deletion logic to divert the response if it makes sense:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&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;HttpResponseRedirect&lt;/span&gt;  &lt;span class="c1"&gt;# new&lt;/span&gt;


&lt;span class="nd"&gt;@admin&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;Rental&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RentalAdmin&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;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;response_delete&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;obj_display&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj_id&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;Rental&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;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;obj_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="c1"&gt;# Return to the admin's Rental detail page&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponseRedirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"admin:rentals_rental_change"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;obj_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Otherwise proceed as usual&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;response_delete&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;obj_display&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will stop Django from adding that confusing "successful deletion" message.&lt;/p&gt;
&lt;p&gt;Voila.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bulk-deletions-follow-a-different-course-of-action"&gt;
&lt;h2&gt;Bulk Deletions Follow a Different Course of Action&lt;/h2&gt;
&lt;p&gt;To my knowledge, the bulk deletion option in the Django Admin uses different methods to handle the bulk deletion behavior.&lt;/p&gt;
&lt;p&gt;If you're familiar with how to override this behavior, I'd be curious to hear!&lt;/p&gt;
&lt;p&gt;You can disable bulk deletions by overriding the method &lt;tt class="docutils literal"&gt;ModelAdmin.get_actions()&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&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;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Rental&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RentalAdmin&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;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_actions&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;actions&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_actions&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="s2"&gt;"delete_selected"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"delete_selected"&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;actions&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="Django"/><category term="django-admin"/></entry><entry><title>Custom Dictionary Representation of Python Dataclass</title><link href="https://startcodingnow.com/dataclass-asdict-factory" rel="alternate"/><published>2024-07-10T00:00:00-07:00</published><updated>2024-07-10T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2024-07-10:/dataclass-asdict-factory</id><summary type="html">&lt;p class="first last"&gt;Customize your dataclass dictionary generation.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've really enjoyed the shortcuts provided by Python's &lt;a class="reference external" href="https://docs.python.org/3/library/dataclasses.html"&gt;dataclasses&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Recently, I found myself working with an API that required camelCase keys, but I wanted to keep my Python keys in snake_case as I find it &lt;em&gt;comforting&lt;/em&gt;.&lt;/p&gt;
&lt;div class="section" id="creating-a-dataclass"&gt;
&lt;h2&gt;Creating a dataclass&lt;/h2&gt;
&lt;p&gt;I created the following dataclass for working with cell locations in Google Sheets:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Coordinate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sheet_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;row_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;column_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To instantiate an object:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;coord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Coordinate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# cell D2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="turning-the-object-into-a-dictionary"&gt;
&lt;h2&gt;Turning the object into a dictionary&lt;/h2&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#dataclasses.asdict"&gt;dataclasses.asdict()&lt;/a&gt; function transforms your dataclass object. It reuses your declared key names. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asdict&lt;/span&gt;

&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"sheet_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"row_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"column_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="making-a-custom-dict-factory"&gt;
&lt;h2&gt;Making a custom dict factory&lt;/h2&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;asdict()&lt;/tt&gt; accepts an optional parameter &lt;tt class="docutils literal"&gt;dict_factory&lt;/tt&gt; which is a function that tells &lt;tt class="docutils literal"&gt;asdict&lt;/tt&gt; how to
parse the dataclass object.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# help us transform the keys strings&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;snake_case_to_camel_case&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# split character and remove underscores&lt;/span&gt;
    &lt;span class="n"&gt;data_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="o"&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;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"_"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# capitalize the first letter of all words except the first&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_list&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;data_list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;data_list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# our method to plug into `asdict()`&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;camel_case_dict_factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;snake_case_to_camel_case&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To use this new factory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"sheetId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"rowIndex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"columnIndex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;coord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dict_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;camel_case_dict_factory&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For reference, here's the &lt;a class="reference external" href="https://github.com/python/cpython/blob/f62161837e68c1c77961435f1b954412dd5c2b65/Lib/dataclasses.py#L1362"&gt;standard implementation of asdict()&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="python"/><category term="dataclasses"/></entry><entry><title>Where Does Validation Occur?</title><link href="https://startcodingnow.com/validation" rel="alternate"/><published>2024-03-04T00:00:00-07:00</published><updated>2024-03-04T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2024-03-04:/validation</id><summary type="html">&lt;p class="first last"&gt;Where and how to validate your Django REST Framework API&lt;/p&gt;
</summary><content type="html">&lt;p&gt;We can be more confident that our application will not break during runtime if we validate our data is of an expected size/shape/range.&lt;/p&gt;
&lt;p&gt;If we have a user account, for example, we don't want to save "larryjohnson" as an email address because when we try to email that address, the application will fail. We might want to restrict names to a reasonable character length, numbers to be only positive, or start dates to be earlier than end dates.&lt;/p&gt;
&lt;div class="section" id="where-does-validation-occur-1"&gt;
&lt;h2&gt;Where Does Validation Occur?&lt;/h2&gt;
&lt;p&gt;There are many places where data is validated in a frontend + DRF backend situation:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;The user validates what they write when they enter it, assuming they are not malicious&lt;/li&gt;
&lt;li&gt;The browser validates a field or form using HTML5's &lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation"&gt;constraint validation&lt;/a&gt; for common use cases, assuming &lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types"&gt;the correct usage of the &lt;tt class="docutils literal"&gt;type&lt;/tt&gt; attribute on an &lt;tt class="docutils literal"&gt;input&lt;/tt&gt; field&lt;/a&gt; or other &lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation#validation-related_attributes"&gt;validation-related attributes&lt;/a&gt; such as &lt;tt class="docutils literal"&gt;required&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;pattern&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;The browser can also validate a field or form in custom situations using JavaScript&lt;/li&gt;
&lt;li&gt;The serializer validates each field on the serializer with their respective &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;validate_&lt;fieldname&gt;(self,&lt;/fieldname&gt;&lt;/span&gt; value)&lt;/tt&gt; methods&lt;/li&gt;
&lt;li&gt;The serializer validates the serializer as a whole with the &lt;tt class="docutils literal"&gt;validate(self, attrs)&lt;/tt&gt; method&lt;/li&gt;
&lt;li&gt;The database validates the data does not violate any database constraints&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="simple-validation-of-data-types-in-the-serializer"&gt;
&lt;h2&gt;Simple Validation of Data Types in the Serializer&lt;/h2&gt;
&lt;p&gt;When using a serializer, DRF maps JSON values back into native Python values. This is the simplest form of validation within our API.&lt;/p&gt;
&lt;p&gt;As an example, if the supplied string cannot be coerced into a datetime, DRF will raise a &lt;cite&gt;serializers.ValidationError&lt;/cite&gt; on the field defining the datetime.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="custom-data-validation-in-the-serializer"&gt;
&lt;h2&gt;Custom Data Validation in the Serializer&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://testdriven.io/blog/drf-serializers/#custom-data-validation"&gt;TestDriven.io has a good overview of custom data validation in DRF.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As mentioned above, there are two access points for validating your serializer data:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Field&lt;/li&gt;
&lt;li&gt;Object&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When checking serializer validity, first each field is checked, and lastly the object as a whole is checked.&lt;/p&gt;
&lt;p&gt;Here's an example serializer with custom validation for the &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;rating&lt;/tt&gt; fields as well as a custom object-level validator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serializers&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;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"creator_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rating"&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;validate_name&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;value&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;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;"We play by old school Twitter rules here"&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;validate_rating&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;value&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;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;"Rating must be between 1 and 5"&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;value&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&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;data&lt;/span&gt;&lt;span class="p"&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;User&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;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="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"creator_id"&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;Product&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;creator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name__iexact&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidationError&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;"A product with this name already exists for creator &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="writing-reusable-validators"&gt;
&lt;h2&gt;Writing Reusable Validators&lt;/h2&gt;
&lt;p&gt;If you have a validator to reuse in multiple serializers, you can write it separately and then set it on the field under the &lt;tt class="docutils literal"&gt;validators&lt;/tt&gt; argument.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serializers&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;Product&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_rating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;"Rating cannot be lower than 1."&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;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;"Rating cannot be higher than 5."&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;validators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;is_rating&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"creator_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="Django"/><category term="django-rest-framework"/><category term="html5"/></entry><entry><title>How to Kill the Django Development Server Without Ctrl+C</title><link href="https://startcodingnow.com/kill-django-development-server" rel="alternate"/><published>2023-11-05T00:00:00-07:00</published><updated>2023-11-05T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2023-11-05:/kill-django-development-server</id><summary type="html">&lt;p class="first last"&gt;How to stop the development server when your terminal window is nowhere to be found.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Let's say you're developing a Django project and you've lost track of the terminal window that ran your &lt;tt class="docutils literal"&gt;python manage.py runserver&lt;/tt&gt; command. If you can't press &lt;tt class="docutils literal"&gt;Ctrl+C&lt;/tt&gt; in the terminal, how do you stop the server?&lt;/p&gt;
&lt;div class="section" id="finding-the-runserver-process"&gt;
&lt;h2&gt;Finding the &lt;tt class="docutils literal"&gt;runserver&lt;/tt&gt; Process&lt;/h2&gt;
&lt;p&gt;First, we need to find the ID of the &lt;tt class="docutils literal"&gt;manage.py runserver&lt;/tt&gt; process.&lt;/p&gt;
&lt;p&gt;On Linux, you can list all processes with the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;aux
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To search the results, you can pipe the output of that command into the &lt;tt class="docutils literal"&gt;grep&lt;/tt&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;aux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;manage.py
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you want to search multiple words, enclose them in single quotes &lt;tt class="docutils literal"&gt;'&lt;/tt&gt; or double quotes &lt;tt class="docutils literal"&gt;"&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;aux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'manage.py runserver'&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This should give you rows of table data such as:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
lance    12839  0.0  0.1  57068 49840 pts/25   S+   07:11   0:00 python manage.py runserver
lance    12840 23.0  0.2 435156 67752 pts/25   Sl+  07:11   4:40 /home/lance/django-workout/.venv/bin/python manage.py runserver
lance    23752  0.0  0.0   4040  1980 pts/30   S+   07:31   0:00 grep --color=auto manage.py runserver
&lt;/pre&gt;
&lt;p&gt;This can be confusing because there are a few numbers here and I'm not sure what each number represents.&lt;/p&gt;
&lt;p&gt;To prepend a row of headers, we can run two commands back-to-back:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;aux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;head&lt;span class="w"&gt; &lt;/span&gt;--lines&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&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;ps&lt;span class="w"&gt; &lt;/span&gt;aux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'manage.py runserver'&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;head&lt;/tt&gt; command is used for printing the beginning of a file, or, if no file is specified, it reads standard input in the console.&lt;/p&gt;
&lt;p&gt;We're looking for the &lt;tt class="docutils literal"&gt;PID&lt;/tt&gt; or the "Process ID":&lt;/p&gt;
&lt;pre class="literal-block"&gt;
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
lance    12839  0.0  0.1  57068 49840 pts/25   S+   07:11   0:00 python manage.py runserver
lance    12840 23.0  0.2 435156 67752 pts/25   Sl+  07:11   4:40 /home/lance/django-workout/.venv/bin/python manage.py runserver
lance    23752  0.0  0.0   4040  1980 pts/30   S+   07:31   0:00 grep --color=auto manage.py runserver
&lt;/pre&gt;
&lt;p&gt;There are three to pick from. Which do we choose?&lt;/p&gt;
&lt;p&gt;We can kill either of the first two, PID 12839 or PID 12840. They are related. Notice that they have the same start time.&lt;/p&gt;
&lt;p&gt;If we read the command from the third process, we can see it's a grep command. That's the one we just ran to search for results. That process is already gone by the time we're reading the table.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="killing-a-process"&gt;
&lt;h2&gt;Killing a Process&lt;/h2&gt;
&lt;p&gt;Now that we know the process ID, we can tell the operating system to stop it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;kill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12839&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Zero results suggests the command was successful, but we can double check.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="verify-the-process-was-terminated"&gt;
&lt;h2&gt;Verify the Process was Terminated&lt;/h2&gt;
&lt;p&gt;We can confirm this worked by checking our process list again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;aux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;head&lt;span class="w"&gt; &lt;/span&gt;--lines&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&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;ps&lt;span class="w"&gt; &lt;/span&gt;aux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'manage.py runserver'&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You could also load up &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://127.0.0.1:8000&lt;/span&gt;&lt;/tt&gt; in your browser and see if your Django project is still being served there.&lt;/p&gt;
&lt;div class="section" id="summary"&gt;
&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;I had this happen to me when I started a server from the VSCode integrated terminal right when VSCode decided to restart.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Django"/><category term="linux"/></entry><entry><title>Introducing djarter – An opinionated Django starter project</title><link href="https://startcodingnow.com/djarter" rel="alternate"/><published>2023-11-02T00:00:00-07:00</published><updated>2023-11-02T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2023-11-02:/djarter</id><summary type="html">&lt;p class="first last"&gt;&lt;tt class="docutils literal"&gt;djarter&lt;/tt&gt; is a Django starter template with a custom user model, auth views, and various quality assurance tools.&lt;/p&gt;
</summary><content type="html">&lt;a class="reference external image-reference" href="https://github.com/lancegoyke/djarter"&gt;
&lt;img alt="A screenshot of the djarter starter template up and running" class="image-process-article" sizes="(min-width: 557px) 525px, (min-width: 375px) 343px, 343px" src="./images/derivatives/article/343w/djarter-screenshot-wide.png" srcset="./images/derivatives/article/343w/djarter-screenshot-wide.png 343w, ./images/derivatives/article/525w/djarter-screenshot-wide.png 525w"/&gt;
&lt;/a&gt;
&lt;p&gt;I have spent six years resisting the urge to make a starter template because (a) it locks you in to a particular development style, and (b) it prevents the repetition that promotes learning.&lt;/p&gt;
&lt;p&gt;But in the spirit of deploying high quality work, this repo contains a few best practices and useful tools for Django projects.&lt;/p&gt;
&lt;p&gt;These features would take a while to get set up from scratch, and might not be worth it for every project. But cloning this starter template can save you roughly a full day of work if you're planning to extend the built-in user authentication.&lt;/p&gt;
&lt;p&gt;Most importantly, though, no Bootstrap styling was used in the making of this template.&lt;/p&gt;
&lt;!-- Check out `djarter on GitHub. &lt;https://github.com/lancegoyke/djarter&gt;`__? --&gt;
&lt;p&gt;Check out &lt;a class="reference external" href="https://github.com/lancegoyke/djarter"&gt;&lt;tt class="docutils literal"&gt;djarter&lt;/tt&gt; on GitHub&lt;/a&gt;.&lt;/p&gt;
</content><category term="django"/></entry><entry><title>Logging in Google Apps Script</title><link href="https://startcodingnow.com/logging-in-google-apps-script" rel="alternate"/><published>2023-08-25T00:00:00-07:00</published><updated>2023-08-25T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2023-08-25:/logging-in-google-apps-script</id><summary type="html">&lt;p class="first last"&gt;Simple logging in Google Apps Script can be done with &lt;tt class="docutils literal"&gt;console.log&lt;/tt&gt;&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I have some &lt;a class="reference external" href="/gmail-auto-archive"&gt;Google Apps Script code&lt;/a&gt; that archives emails. I can see if the code ran without errors in the execution log, but I would like to also see how many emails were archived on a run to know if it changed the state of my inbox.&lt;/p&gt;
&lt;p&gt;We can &lt;a class="reference external" href="https://developers.google.com/apps-script/guides/logging"&gt;use the execution log to do some simple logging.&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&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;archiveInbox&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="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;threads&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;GmailApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'label:inbox from:(jobalerts-noreply@linkedin.com) older_than:4d'&lt;/span&gt;&lt;span class="p"&gt;);&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="p"&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;i&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="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s1"&gt;'Moving thread to archive: "%s"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;getFirstMessageSubject&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;threads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;moveToArchive&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;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the snippet above, the &lt;tt class="docutils literal"&gt;console.log&lt;/tt&gt; statement gets pushed directly to the execution log. This will leave a note whenever a thread gets archived with the subject of the thread.&lt;/p&gt;
&lt;p&gt;To view the logs, check the execution log from the Executions sidebar menu.&lt;/p&gt;
</content><category term="google-apps-script"/></entry><entry><title>Auto-Archive Emails in Gmail</title><link href="https://startcodingnow.com/gmail-auto-archive" rel="alternate"/><published>2023-07-28T00:00:00-07:00</published><updated>2023-07-28T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2023-07-28:/gmail-auto-archive</id><summary type="html">&lt;p class="first last"&gt;Set a timer to clean your inbox of old emails&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="goal"&gt;
&lt;h2&gt;Goal&lt;/h2&gt;
&lt;p&gt;I'm looking for a job in software engineering and have signed up for job alerts from LinkedIn.&lt;/p&gt;
&lt;p&gt;I still, however, have a personal training business to run, and these emails are muddying my inbox, making it easier to miss important emails from my clients.&lt;/p&gt;
&lt;p&gt;Given these job alerts are time-sensitive, I am okay with archiving emails that have been in my inbox for three days.&lt;/p&gt;
&lt;p&gt;A good alternative would be to check these emails right away, but hey, sometimes I get busy!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="starting-point"&gt;
&lt;h2&gt;Starting Point&lt;/h2&gt;
&lt;p&gt;I'm starting with &lt;a class="reference external" href="https://www.labnol.org/code/19786-cleanup-gmail-inbox"&gt;the following snippet from Amit Agarwal.&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&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;cleanInbox&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="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;delayDays&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="mf"&gt;2&lt;/span&gt;&lt;span class="p"&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;maxDate&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="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;maxDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;delayDays&lt;/span&gt;&lt;span class="p"&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;threads&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;GmailApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getInboxThreads&lt;/span&gt;&lt;span class="p"&gt;();&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="p"&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;i&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="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;getLastMessageDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&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;archiveInbox&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="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;threads&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;GmailApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'label:inbox is:read older_than:2d'&lt;/span&gt;&lt;span class="p"&gt;);&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="p"&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;i&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="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;threads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;moveToArchive&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;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="google-apps-script-project-setup"&gt;
&lt;h2&gt;Google Apps Script Project Setup&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Head to &lt;a class="reference external" href="https://script.google.com"&gt;https://script.google.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Make sure you're logged in with the account associated with the Gmail inbox you want to clean&lt;/li&gt;
&lt;li&gt;Click on "&lt;a class="reference external" href="https://script.google.com/u/1/home/projects/create"&gt;+ New Project&lt;/a&gt;"&lt;/li&gt;
&lt;li&gt;Click on the "+" button next to Services and add "Gmail" to give the script permission to use Gmail, an &lt;a class="reference external" href="https://developers.google.com/apps-script/guides/services/advanced"&gt;advanced service&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Your script will be written inside the &lt;tt class="docutils literal"&gt;code.gs&lt;/tt&gt; file that automatically opens when creating a new project.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="search-and-archive-script"&gt;
&lt;h2&gt;Search and Archive Script&lt;/h2&gt;
&lt;p&gt;Open up &lt;tt class="docutils literal"&gt;code.gs&lt;/tt&gt; under Files and enter your script. Here's how I adjusted Amit's code to serve my purposes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&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;archiveInbox&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="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;threads&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;GmailApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'label:inbox from:(jobalerts-noreply@linkedin.com) older_than:3d'&lt;/span&gt;&lt;span class="p"&gt;);&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="p"&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;i&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="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;threads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;moveToArchive&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;/pre&gt;&lt;/div&gt;
&lt;p&gt;Read &lt;a class="reference external" href="https://developers.google.com/apps-script/reference/gmail"&gt;the documentation&lt;/a&gt; to learn more about the &lt;tt class="docutils literal"&gt;GmailApp&lt;/tt&gt;. For example, you may want to skip archiving and &lt;a class="reference external" href="https://developers.google.com/apps-script/reference/gmail/gmail-thread#moveToTrash()"&gt;move the thread straight to the trash&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-note-on-feature-design"&gt;
&lt;h2&gt;A Note on Feature Design&lt;/h2&gt;
&lt;p&gt;When searching for the features I wanted, it was helpful to take a step back from the Gmail Service documentation and think about how I would accomplish this task using the standard Gmail graphical user interface.&lt;/p&gt;
&lt;p&gt;For my situation, I would use search to filter emails by sender and date. I figured out who the sender was and used the date filtering similar to Amit's example.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="test-your-script"&gt;
&lt;h2&gt;Test Your Script&lt;/h2&gt;
&lt;p&gt;Once you think your code is correct, click on the "Save" button and then the "Run" button.&lt;/p&gt;
&lt;p&gt;An Execution log should open at the bottom. If everything worked, you should see "Execution started" and "Execution completed".&lt;/p&gt;
&lt;p&gt;You can add &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;console.log("message")&lt;/span&gt;&lt;/tt&gt; statements to tell you what's happening when the code is run.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="enable-time-trigger"&gt;
&lt;h2&gt;Enable Time Trigger&lt;/h2&gt;
&lt;p&gt;As it stands, the script is actually done.&lt;/p&gt;
&lt;p&gt;The issue is that it's not saving any time to have to open up Google Apps Script every time you want to archive a few emails.&lt;/p&gt;
&lt;p&gt;To run this script automatically, click on "Triggers" with the time clock icon in the leftmost sidebar.&lt;/p&gt;
&lt;p&gt;Then click "+ Add Trigger" and choose your settings. Mine were like this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Choose which function to run on: &lt;tt class="docutils literal"&gt;archiveInbox&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Choose which deployment should run: &lt;tt class="docutils literal"&gt;Head&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Select event source: &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;Time-driven&lt;/span&gt;&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Select type of time based trigger: &lt;tt class="docutils literal"&gt;Day timer&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Select time of day: &lt;tt class="docutils literal"&gt;Midnight to 1am&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Failure notification settings: &lt;tt class="docutils literal"&gt;Notify me daily&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you changed the function name or you have multiple, they should show up in this dialog. You can customize the time to fit your needs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="troubleshooting"&gt;
&lt;h2&gt;Troubleshooting&lt;/h2&gt;
&lt;p&gt;When first running, Apps Script will tell you "Authorization required". You must review and allow permissions using Google Single Sign-On.&lt;/p&gt;
&lt;p&gt;If you forgot to add the Gmail service, the execution log will tell you that "Execution started", but it will hit an error of "Script function not found".&lt;/p&gt;
&lt;p&gt;If you haven't placed your script within a function, you will not be able to run it.&lt;/p&gt;
&lt;p&gt;If the code appears to run correctly, but you do not notice the desired outcome in your Gmail inbox, double check that the project was created under the correct account.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I'm seeing the power of Google Apps Script as someone using many Google services. Perhaps we can start utilizing Google Sheets and Docs more?&lt;/p&gt;
&lt;/div&gt;
</content><category term="google-apps-script"/><category term="gmail"/></entry><entry><title>3 Git Configuration Tips I Use Every Day</title><link href="https://startcodingnow.com/git-config-tips" rel="alternate"/><published>2023-06-15T00:00:00-07:00</published><updated>2023-06-15T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2023-06-15:/git-config-tips</id><summary type="html">&lt;p class="first last"&gt;Some extremely useful tips I learned from Adam Johnson's book, &lt;em&gt;Boost Your Git DX&lt;/em&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Do you use Git literally every day? Me too.&lt;/p&gt;
&lt;p&gt;I've been using the basics of Git for years, but only recently did I learn some new powerful tricks.&lt;/p&gt;
&lt;p&gt;If you want to move past &lt;tt class="docutils literal"&gt;add&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;commit&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;push&lt;/tt&gt;, keep reading.&lt;/p&gt;
&lt;div class="section" id="contents"&gt;
&lt;h2&gt;Contents&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Praise for &lt;em&gt;Boost Your Git DX&lt;/em&gt; by Adam Johnson&lt;/li&gt;
&lt;li&gt;Make a &lt;tt class="docutils literal"&gt;dotfiles&lt;/tt&gt; Repository&lt;/li&gt;
&lt;li&gt;Use Shell Aliases for Git Commands&lt;/li&gt;
&lt;li&gt;Automatically Create Upstream Branches&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="praise-for-boost-your-git-dx-by-adam-johnson"&gt;
&lt;h2&gt;Praise for &lt;em&gt;Boost Your Git DX&lt;/em&gt; by Adam Johnson&lt;/h2&gt;
&lt;p&gt;All of these tips have come from Adam Johnson's book, &lt;a class="reference external" href="https://adamchainz.gumroad.com/l/bygdx"&gt;Boost Your Git DX&lt;/a&gt;. I'm only sharing (with permission) three tips from the book. He's written almost 300 more pages than what I outline here.&lt;/p&gt;
&lt;p&gt;This book transformed my day-to-day operations. As a self-taught engineer, I have found myself starved for mentorship, and &lt;em&gt;Boost Your Git DX&lt;/em&gt; has been my most influential mentor in 2023.&lt;/p&gt;
&lt;p&gt;If you're looking to maximize the usefulness of Git past basic version control, you will &lt;strong&gt;love&lt;/strong&gt; this book.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="make-a-dotfiles-repository"&gt;
&lt;h2&gt;Make a &lt;tt class="docutils literal"&gt;dotfiles&lt;/tt&gt; Repository&lt;/h2&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;dotfiles&lt;/tt&gt; repository serves as a place to save your configuration files, allowing you to &lt;tt class="docutils literal"&gt;git clone&lt;/tt&gt; it down to new computers or accounts. This is a huge plus for your developer experience if you work on multiple machines.&lt;/p&gt;
&lt;p&gt;I can now use my optimized shell on Windows Subsystem for Linux for every version of Ubuntu I have installed, my home Ubuntu server, my powerhouse desktop computer, and my more portable laptop. You could even clone it into your web servers!&lt;/p&gt;
&lt;p&gt;Check if you have files to save:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;~
ls&lt;span class="w"&gt; &lt;/span&gt;~/.config
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If so, set up a &lt;tt class="docutils literal"&gt;dotfiles&lt;/tt&gt; repository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles
git&lt;span class="w"&gt; &lt;/span&gt;init
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Set up a &lt;tt class="docutils literal"&gt;config&lt;/tt&gt; directory to store config files and move in your Git config:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;config
mv&lt;span class="w"&gt; &lt;/span&gt;~/.config/git&lt;span class="w"&gt; &lt;/span&gt;config/git
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Removing the prepended &lt;tt class="docutils literal"&gt;.&lt;/tt&gt; from &lt;tt class="docutils literal"&gt;.config&lt;/tt&gt; means the folder won't be hidden by default.&lt;/p&gt;
&lt;p&gt;Then, make a script to create a symlink for the config files back to their expected location inside the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.config&lt;/span&gt;&lt;/tt&gt; directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;touch&lt;span class="w"&gt; &lt;/span&gt;setup.sh
chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;setup.sh
&lt;span class="nv"&gt;$EDITOR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;setup.sh
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Inside &lt;tt class="docutils literal"&gt;setup.sh&lt;/tt&gt;, add the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c1"&gt;# Enable shell script strictness&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-eu

&lt;span class="c1"&gt;# Show each command when running script&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-x

&lt;span class="c1"&gt;# Ensure config directory exists&lt;/span&gt;
mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;~/.config

&lt;span class="c1"&gt;# Link Git config if it doesn’t exist&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;-e&lt;span class="w"&gt; &lt;/span&gt;~/.config/git&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/config/git"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.config/git
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If the target &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.config/git&lt;/span&gt;&lt;/tt&gt; file already exists, it will not be overwritten.&lt;/p&gt;
&lt;p&gt;If it doesn't exist, the script will create a symbolic link or "symlink" with &lt;tt class="docutils literal"&gt;ln &lt;span class="pre"&gt;-s&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Run the script to link your config files from your &lt;tt class="docutils literal"&gt;dotfiles&lt;/tt&gt; directory into your home directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./setup.sh
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Check if it's working, commit the files to your repo, and push them up to your preferred Git host.&lt;/p&gt;
&lt;p&gt;When you want to pull your config down to a new server&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;&lt;github_repo_location&gt;
./setup.sh
&lt;/github_repo_location&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you've made updates to your config from one machine, &lt;tt class="docutils literal"&gt;add&lt;/tt&gt; + &lt;tt class="docutils literal"&gt;commit&lt;/tt&gt; + &lt;tt class="docutils literal"&gt;push&lt;/tt&gt; them to your dotfiles repo, then &lt;tt class="docutils literal"&gt;pull&lt;/tt&gt; them down to your other machines.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-shell-aliases-for-git-commands"&gt;
&lt;h2&gt;Use Shell Aliases for Git Commands&lt;/h2&gt;
&lt;p&gt;Though I believe the &lt;tt class="docutils literal"&gt;dotfiles&lt;/tt&gt; repo has saved me the most headache when working on multiple machines, these shell aliases are the change that I use the absolute most.&lt;/p&gt;
&lt;p&gt;Aliases allow you to type abbreviations for longer commands. Some examples:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;g&lt;/tt&gt; for &lt;tt class="docutils literal"&gt;git&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;ga&lt;/tt&gt; for &lt;tt class="docutils literal"&gt;git add&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;gc&lt;/tt&gt; for &lt;tt class="docutils literal"&gt;git commit&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To enable these, you can type them directly into a single shell session, or you can add a line to your shell's startup file (Bash: &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.bashrc&lt;/span&gt;&lt;/tt&gt;, Zsh: &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.zshrc&lt;/span&gt;&lt;/tt&gt;) to persist them across all interactive login shell sessions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# ex: alias &lt;name&gt;=&lt;command/&gt;&lt;/name&gt;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;git
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ga&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;push
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Make sure to restart your shell to load the new configuration if you put it in your &lt;tt class="docutils literal"&gt;.bashrc&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;.zshrc&lt;/tt&gt; file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.zshrc
ga&lt;span class="w"&gt; &lt;/span&gt;.
gc&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"added new config"&lt;/span&gt;
gp&lt;span class="w"&gt; &lt;/span&gt;--set-upstream&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;main
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="automatically-create-upstream-branches"&gt;
&lt;h2&gt;Automatically Create Upstream Branches&lt;/h2&gt;
&lt;p&gt;Once a project is more established, I prefer to work on new features in their own respective Git branches.&lt;/p&gt;
&lt;p&gt;The problem is that when I push my changes to GitHub, the branch does not yet exist on the remote, so I have to type this wordy &lt;tt class="docutils literal"&gt;gp &lt;span class="pre"&gt;-u&lt;/span&gt; origin &lt;span class="pre"&gt;new-feature-1&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;But what I &lt;em&gt;really&lt;/em&gt; want to type is &lt;tt class="docutils literal"&gt;gp&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;To automatically create an upstream branch and link it to your local branch, you can use the &lt;tt class="docutils literal"&gt;push.autoSetupRemote&lt;/tt&gt; option.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--global&lt;span class="w"&gt; &lt;/span&gt;push.autoSetupRemote&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will add a new setting to your global Git configuration file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="s"&gt;[push]&lt;/span&gt;
    autoSetupRemote &lt;span class="s"&gt;= true&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This Git feature was added in Git v2.37, so you may check your &lt;tt class="docutils literal"&gt;git &lt;span class="pre"&gt;--version&lt;/span&gt;&lt;/tt&gt; to see if you need to upgrade.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;There you have it! Three Git tips I use all the time since discovering them in Adam Johnson's book, &lt;a class="reference external" href="https://adamchainz.gumroad.com/l/bygdx"&gt;Boost Your Git DX&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;May your contribution streak be long and light green 🙏.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Developer Experience"/><category term="git"/></entry><entry><title>Save a Django Query to the Database</title><link href="https://startcodingnow.com/save-a-django-query-to-database" rel="alternate"/><published>2022-01-30T00:00:00-07:00</published><updated>2022-01-30T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2022-01-30:/save-a-django-query-to-database</id><summary type="html">&lt;p class="first last"&gt;Store Django queries as JSON objects&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I found a &lt;a class="reference external" href="https://stackoverflow.com/questions/3040088/how-to-obtain-and-or-save-the-queryset-criteria-to-the-db"&gt;StackOverflow
question&lt;/a&gt;
on saving a Django query to the database, but I was left &lt;em&gt;super&lt;/em&gt; unclear
on what to do. There’s all this talk about pickling and serialization
that I’m sure makes a ton of sense to someone smarter than me.&lt;/p&gt;
&lt;p&gt;The current answer is old, however, and in 2022, I found another
solution: turning a dict into JSON.&lt;/p&gt;
&lt;p&gt;Plus, I found a way to make it work with my htmx live search, allowing
me to view the results of my query on the front end of my site (read:
pretty) to double-check that things look okay before saving.&lt;/p&gt;
&lt;p&gt;I’ll show you what I did below.&lt;/p&gt;
&lt;p&gt;I believe pickling still works, so at the end I will show some more
thoughts there.&lt;/p&gt;
&lt;div class="section" id="models-py-example-database-structure"&gt;
&lt;h2&gt;models.py - example database structure&lt;/h2&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transaction&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="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;id&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;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&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="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;date&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;DateField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;amount&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;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;info&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;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;account&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;ForiegnKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&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;SET_NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&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="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;category&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;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&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;SET_NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&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;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&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="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;name&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;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;email&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;EmailField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Category&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="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;name&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;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unique&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="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Rule&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="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;category&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;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&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;SET_NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&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;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;criteria&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;JSONField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# this will hold our query&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;My models store financial transactions, the category the transaction
fits into (e.g., salaried income, 1099 income, office expenses, labor
expenses, etc…), and a rule to save a query to automatically categorize
future transactions without having to remember the query every year when
doing taxes.&lt;/p&gt;
&lt;p&gt;I know, for example, that all my transactions with my consulting clients
should be marked as 1099 income. So I want to create a rule for clients
that will grab each monthly transaction and mark it as 1099 income.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="making-the-query-the-old-fashioned-way"&gt;
&lt;h2&gt;Making the query the old-fashioned way&lt;/h2&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;transactions.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;client1_transactions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Transaction&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;account__name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Client One"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;QuerySet&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;Transaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Transaction&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1111111&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;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="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Transaction&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1111112&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;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="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Transaction&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1111113&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;client1_transactions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Twelve transactions, one for each month. Beautiful.&lt;/p&gt;
&lt;p&gt;But how do we save this to the database?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="save-query-to-database-in-jsonfield"&gt;
&lt;h2&gt;Save query to database in JSONField&lt;/h2&gt;
&lt;p&gt;We now have Django 4.0 and a bunch of support for
&lt;a class="reference external" href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#jsonfield"&gt;JSONField&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’ve been able to grab the filtering values out of a form POST request,
then add them in view logic.&lt;/p&gt;
&lt;div class="section" id="urls-py"&gt;
&lt;h3&gt;urls.py&lt;/h3&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;transactions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;app_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"transactions"&lt;/span&gt;&lt;span class="w"&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="w"&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;"categorize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;categorize&lt;/span&gt;&lt;span class="p"&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;"categorize"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&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;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&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;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"list"&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;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="transactions-list-html"&gt;
&lt;h3&gt;transactions/list.html&lt;/h3&gt;
&lt;pre class="code html literal-block"&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&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;"{% url 'transactions:categorize' %}"&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;"POST"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  {% csrf_token %}
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Info field contains...&lt;span class="p"&gt;&lt;!--&lt;/span--&gt;&lt;span class="nt"&gt;label&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;input&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;"info"&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;"text"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"info"&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;label&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Account name contains...&lt;span class="p"&gt;&lt;!--&lt;/span--&gt;&lt;span class="nt"&gt;label&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;input&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;"account"&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;"text"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"account"&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;label&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;New category should be...&lt;span class="p"&gt;&lt;!--&lt;/span--&gt;&lt;span class="nt"&gt;label&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;input&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;"category"&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;"text"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"category"&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;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Make a Rule&lt;span class="p"&gt;&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;&lt;!--&lt;/span--&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="views-py"&gt;
&lt;h3&gt;views.py&lt;/h3&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;categorize&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="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# get POST data from our form&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;info&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;POST&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="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&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;account&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;POST&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="s2"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&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;category&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;POST&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="s2"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&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;# set up query&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;info&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="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"info__icontains"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;account&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="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"account__name__icontains"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="c1"&gt;# update the database&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;category_obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Category&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;get_or_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Transaction&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-date"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;Rule&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;get_or_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;category_obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;category_obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="c1"&gt;# render the template&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s2"&gt;"transactions/list.html"&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;"transactions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"account"&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;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="how-to-do-it-with-htmx-and-live-search"&gt;
&lt;h2&gt;How to do it with htmx and live search&lt;/h2&gt;
&lt;p&gt;I am using htmx to get live search to ensure that my query is correct.
This shows the filtered queryset to me as I type, then I can click
“Create Rule” to save this query to the database.&lt;/p&gt;
&lt;div class="section" id="transactions-list-html-1"&gt;
&lt;span id="transactionslist-html-1"&gt;&lt;/span&gt;&lt;h3&gt;transactions/list.html&lt;/h3&gt;
&lt;pre class="code html literal-block"&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;
  &lt;span class="na"&gt;hx-post&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{% url 'transactions:perform_categorization' %}"&lt;/span&gt;
  &lt;span class="na"&gt;hx-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#tables"&lt;/span&gt;
  &lt;span class="na"&gt;hx-indicator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;".htmx-indicator"&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;input&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;"info"&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;"text"&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"info"&lt;/span&gt;
    &lt;span class="na"&gt;hx-post&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{% url 'transactions:categorize' %}"&lt;/span&gt;
    &lt;span class="na"&gt;hx-trigger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"keyup changed delay:250ms"&lt;/span&gt;
    &lt;span class="na"&gt;hx-indicator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;".htmx-indicator"&lt;/span&gt;
    &lt;span class="na"&gt;hx-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#tables"&lt;/span&gt;
    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"info"&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;input&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;"account"&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;"text"&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"account"&lt;/span&gt;
    &lt;span class="na"&gt;hx-post&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{% url 'transactions:categorize' %}"&lt;/span&gt;
    &lt;span class="na"&gt;hx-trigger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"keyup changed delay:250ms"&lt;/span&gt;
    &lt;span class="na"&gt;hx-indicator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;".htmx-indicator"&lt;/span&gt;
    &lt;span class="na"&gt;hx-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#tables"&lt;/span&gt;
    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"account"&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;span&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;"htmx-indicator"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="p"&gt;&lt;!--&lt;/span--&gt;&lt;span class="nt"&gt;span&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;input&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;"category"&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;"text"&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"category"&lt;/span&gt;
    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"new category..."&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;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Make a Rule&lt;span class="p"&gt;&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;&lt;!--&lt;/span--&gt;&lt;span class="nt"&gt;form&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;"tables"&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;"stack"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{% include "transactions/tables.html" %}&lt;span class="p"&gt;&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;script&lt;/span&gt;
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/htmx.org@1.6.1"&lt;/span&gt;
  &lt;span class="na"&gt;integrity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"sha384-tvG/2mnCFmGQzYC1Oh3qxQ7CkQ9kMzYjWZSNtrRZygHPDDqottzEJsqS4oUVodhW"&lt;/span&gt;
  &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;!--&lt;/span--&gt;&lt;span class="nt"&gt;script&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;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&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;body&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="s2"&gt;"htmx:configRequest"&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;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"X-CSRFToken"&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="s2"&gt;"{{ csrf_token }}"&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;!--&lt;/span--&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="views-py-2"&gt;
&lt;span id="views-py-1"&gt;&lt;/span&gt;&lt;h3&gt;views.py&lt;/h3&gt;
&lt;p&gt;I’ve got one view tied to &lt;tt class="docutils literal"&gt;{% url 'transactions:categorize' %}&lt;/tt&gt; that
will display the list of transactions. The htmx live active search works
there.&lt;/p&gt;
&lt;p&gt;Then I have another view tied to
&lt;tt class="docutils literal"&gt;{% url 'transactions:perform_categorization' %}&lt;/tt&gt; which actually saves
the query to the database by creating a new Rule. The code is the same
as above.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="how-to-do-it-with-pickle"&gt;
&lt;h2&gt;How to do it with pickle?&lt;/h2&gt;
&lt;p&gt;So, I actually lied before. I have a little experience with pickle and I
do like it, but I am not sure on how to save it to the database. My
guess is that you’d then save the pickled string to a
&lt;a class="reference external" href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#binaryfield"&gt;BinaryField&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Perhaps something like this:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# imports&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pickle&lt;/span&gt;  &lt;span class="c1"&gt;# standard library&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;transactions.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Transaction&lt;/span&gt;  &lt;span class="c1"&gt;# my own stuff&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# create the query&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;qs_to_save&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Transaction&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;account__name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Client 1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;qs_to_save&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# create the pickle&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;saved_pickle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qs_to_save&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved_pickle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;bytes&lt;/span&gt;&lt;span class="s1"&gt;'&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# save to database&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# make sure `criteria = models.BinaryField()` above in models.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# I'm unsure about this&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;test_category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Category&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;get_or_create&lt;/span&gt;&lt;span class="p"&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;"Test Category"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;test_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Rule&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;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;saved_pickle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# remake queryset at a later date&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;new_qs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Transaction&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="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;new_qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_rule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;new_qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Hope this helps!&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="binaryfield"/><category term="jsonfield"/><category term="pickle"/></entry><entry><title>Get With the Program: The Basics for Freelancing as a Coder</title><link href="https://startcodingnow.com/get-with-the-program-the-basics-for-freelancing-as-a-coder" rel="alternate"/><published>2021-12-19T00:00:00-07:00</published><updated>2021-12-19T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2021-12-19:/get-with-the-program-the-basics-for-freelancing-as-a-coder</id><summary type="html">&lt;p&gt;Written by &lt;a class="reference external" href="https://ablefutures.org"&gt;Ed Carter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If the pandemic has confirmed one thing it’s that the tech sector isn’t
slowing down and its willful stewards, the computer programmers (or
‘coders’), continue to enjoy high demand. If you’re an aspiring coder,
it’s now more viable than ever to go …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Written by &lt;a class="reference external" href="https://ablefutures.org"&gt;Ed Carter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If the pandemic has confirmed one thing it’s that the tech sector isn’t
slowing down and its willful stewards, the computer programmers (or
‘coders’), continue to enjoy high demand. If you’re an aspiring coder,
it’s now more viable than ever to go it alone and enjoy freedom and
profit as a ‘codepreneur’ - here’s how you can get started.&lt;/p&gt;
&lt;div class="section" id="the-coding-landscape"&gt;
&lt;h2&gt;The Coding Landscape&lt;/h2&gt;
&lt;p&gt;Before embarking on a solo career of any kind, it’s always important to
first understand the scope and pitfalls of the business landscape. In
the case of coding, that means finding the right &lt;a class="reference external" href="https://www.zenbusiness.com/blog/how-to-identify-your-target-market/"&gt;target
market&lt;/a&gt;
and business types in requirement of your services. Some industries,
such as finance, work in close coalition with tech and are already well
saturated with programmers - finding a niche in these sectors (although
lucrative) can be difficult. Finding the right market (or just a good
client) means finding somewhere you’re valued and where you can be aptly
compensated.&lt;/p&gt;
&lt;p&gt;Fortunately, regardless of where you choose to work,
&lt;a class="reference external" href="https://www.weforum.org/agenda/2021/09/fastest-growing-jobs-employment-skills/"&gt;research&lt;/a&gt;
shows that computer and mathematical jobs continue to see increasing
growth and at high salaries. Especially with a burgeoning &lt;a class="reference external" href="https://firstsiteguide.com/startup-stats/"&gt;‘startup’
economy&lt;/a&gt; (with heavy
reliance on tech), part-time or contract coders are an increasingly
necessary expense, as companies seek to build websites, apps, and
proprietary algorithms. It’s important to remember this when you first
begin to set rates and working hours - unlike many freelance
professionals, you may have more freedom to call the shots.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="finding-your-first-client"&gt;
&lt;h2&gt;Finding Your First Client&lt;/h2&gt;
&lt;p&gt;Unless you are already well connected, the first step for a codepreneur
is always to develop a &lt;a class="reference external" href="https://www.codeofhonor.com/blog/marketing-yourself-as-a-programmer"&gt;marketing
strategy&lt;/a&gt;.
You’ll need to think carefully about your brand - what is your business
ethos, what do you do well and where would you like to work? Answering
these three questions will allow you to more easily set pricing,
identify potential clients, and engage in a productive dialogue with
businesses. If you haven’t already, it may be necessary to develop a
&lt;a class="reference external" href="https://www.springboard.com/blog/careersmithing/programmer-portfolio/"&gt;portfolio&lt;/a&gt;
of your work. If you’re starting from scratch, this inevitably will mean
having to build websites or write code in exchange for a testimonial or
creating speculative work either for yourself or for ghost clients.&lt;/p&gt;
&lt;p&gt;Once you have the assets to market yourself, the next step is to develop
leads. You can begin by reaching out directly to individuals via social
media or you could take a more passive, paid approach and build PPC
campaigns/marketing campaigns. Either Way, it’s important that you have
completed work that you can reference and preferably some
credentials/&lt;a class="reference external" href="https://blog.edx.org/the-4-most-lucrative-programming-skills-you-can-learn-online"&gt;qualifications&lt;/a&gt;,
which allude to your ability.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conducting-business"&gt;
&lt;h2&gt;Conducting Business&lt;/h2&gt;
&lt;p&gt;Sometimes all you need is that first client. Once you’ve got your foot
through the door and a job well done, your business can multiply itself
via recommendation. It’s of the utmost importance, therefore, to keep
your clients happy. This means clear, regular communication but, more
crucially, good quality work at reasonable
&lt;a class="reference external" href="https://devoxsoftware.com/blog/average-software-developer-hourly-rate/"&gt;rates&lt;/a&gt;.
If you can prove yourself easy to work with, convenient at short notice,
and not overly expensive, you might also become irreplaceable.&lt;/p&gt;
&lt;p&gt;Remember, before entering into any contract, ensure you’ve got your head
around the basic freelance admin. That means understanding how to file
your own
&lt;a class="reference external" href="https://turbotax.intuit.com/tax-tips/self-employment-taxes/a-freelancers-guide-to-taxes/L6ACNfKVW"&gt;taxes&lt;/a&gt;,
how to manage invoices, and maintain spreadsheets. If you don’t know
your way around these essential processes, not only will it appear
unprofessional but it can slow down negotiations.&lt;/p&gt;
&lt;p&gt;Life is good in the tech sector and, if you can find the right clients,
for a talented codepreneur. If you can do the simple things well and
keep yourself on task, there’s no reason you can’t have a successful,
lucrative career in the years to come.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Start Coding Now is a blog and information resource for Coders and
programming professionals. Learn from or spread the wealth of knowledge,
at:&lt;/em&gt; &lt;a class="reference external" href="http://www.startcodingnow.com"&gt;www.startcodingnow.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Image by
&lt;a class="reference external" href="https://www.pexels.com/photo/gray-laptop-computer-showing-html-codes-in-shallow-focus-photography-160107/"&gt;Pexels&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="business"/></entry><entry><title>Get Started with Python</title><link href="https://startcodingnow.com/get-started-with-python" rel="alternate"/><published>2021-11-01T00:00:00-07:00</published><updated>2021-11-01T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2021-11-01:/get-started-with-python</id><summary type="html">&lt;p&gt;If you’re looking to build your Python skills, there are a bunch of
resources out there. Being self-taught, I’ve gone through quite a few of
them!&lt;/p&gt;
&lt;p&gt;Here’s a list of the most memorable stuff I’ve come across, as well as
an explanation of a few basic …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you’re looking to build your Python skills, there are a bunch of
resources out there. Being self-taught, I’ve gone through quite a few of
them!&lt;/p&gt;
&lt;p&gt;Here’s a list of the most memorable stuff I’ve come across, as well as
an explanation of a few basic concepts that most people tend to find
confusing.&lt;/p&gt;
&lt;div class="section" id="books"&gt;
&lt;h2&gt;Books&lt;/h2&gt;
&lt;p&gt;A book or course is the best way to begin. Tutorials are great at some
point, but when you need an overview of the entire language, look for
something more comprehensive.&lt;/p&gt;
&lt;div class="section" id="learn-python-the-hard-way-by-zed-shaw"&gt;
&lt;h3&gt;&lt;em&gt;Learn Python the Hard Way&lt;/em&gt; by Zed Shaw&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://shop.learncodethehardway.org/access/buy/9/"&gt;&lt;img alt="Learn Python 3 the Hard Way: A Very Simple Introduction to the Terrifyingly Beautiful World of Computers and Code by Zed A. Shaw" src="./images/learn-python-3-the-hard-way-zed-shaw.jpg"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Of all the books and such that I read, &lt;a class="reference external" href="https://shop.learncodethehardway.org/access/buy/9/"&gt;Learn Python the Hard
Way&lt;/a&gt; was my
favorite intro to Python because it has a little bit of general
programming sprinkled in.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="automate-the-boring-stuff-with-python-by-al-sweigart"&gt;
&lt;h3&gt;&lt;em&gt;Automate the Boring Stuff with Python&lt;/em&gt; by Al Sweigart&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://automatetheboringstuff.com/"&gt;&lt;img alt="Automate the Boring Stuff with Python: Practical Programming for Total Beginners by Al Sweigart" src="./images/automate-the-boring-stuff-with-python-al-sweigart.jpg"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://automatetheboringstuff.com/"&gt;Automate the Boring Stuff with
Python&lt;/a&gt; was the first Python
book I went through and it’s also good (and free to read online!).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="video-courses"&gt;
&lt;h2&gt;Video Courses&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://realpython.com/courses/what-is-pip/"&gt;&lt;img alt="Beautiful artwork from Real Python’s articles on pip, the Python package manager" src="./images/What-is-PIP_Watermarked-800x450.webp"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can try your luck with YouTube which usually has something great,
but Real Python traditionally has the best stuff on Python. They have a
ton of great, free articles, but they also have a membership that will
give you access to some video courses if you prefer to learn that way.
Here are two that tackle some of the most important fundamentals of
Python:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://realpython.com/courses/working-python-virtual-environments/"&gt;Virtual
environments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://realpython.com/courses/what-is-pip/"&gt;pip - The Python package
manager&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And then once you can figure out pip, packages, and virtual
environments, your most powerful tools are just learning all the magic
that’s out there.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="useful-packages"&gt;
&lt;h2&gt;Useful Packages&lt;/h2&gt;
&lt;div class="section" id="python-standard-library"&gt;
&lt;h3&gt;Python Standard Library&lt;/h3&gt;
&lt;p&gt;The Python Standard Library has a bunch of stuff available to help you
manipulate strings, use different data structures, and more. Doug
Hellman wrote a bunch of short articles on these packages called &lt;a class="reference external" href="https://pymotw.com/3/"&gt;Python
Module of the Week&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="third-party-packages"&gt;
&lt;h3&gt;Third-Party Packages&lt;/h3&gt;
&lt;p&gt;You can get similar benefits out of learning powerful third party
packages (those unassociated with Python proper). Here are some examples
that you would just install with `pip install &lt;em&gt;package-name&lt;/em&gt;`:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.djangoproject.com/"&gt;django&lt;/a&gt; - a framework for
building web applications&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.python-requests.org/en/latest/"&gt;requests&lt;/a&gt; - helps
you make web requests&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/psf/black"&gt;black&lt;/a&gt; - a formatter to give you a
consistent, “pythonic” code style&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/jorisschellekens/borb"&gt;borb&lt;/a&gt; - for working
with PDFs&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://pillow.readthedocs.io/en/stable/"&gt;pillow&lt;/a&gt; - for working
with images&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/kkroening/ffmpeg-python"&gt;ffmpeg&lt;/a&gt; - for working
with video&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="projects"&gt;
&lt;h2&gt;Projects&lt;/h2&gt;
&lt;p&gt;Lastly, make sure you get your hands dirty! You can’t truly understand
how to structure a project until you build one!&lt;/p&gt;
&lt;p&gt;Learn Python the Hard Way and Automate the Boring Stuff with Python have
a bunch of projects that get you going.&lt;/p&gt;
&lt;p&gt;If you’re feeling ambitious, try &lt;a class="reference external" href="https://startcodingnow.com/django-projects-ideas-for-beginners/"&gt;building your own
project&lt;/a&gt;
or tinkering around with someone else’s to get going!&lt;/p&gt;
&lt;/div&gt;
</content><category term="python"/><category term="automate-the-boring-stuff-with-python"/><category term="learn-python-3-the-hard-way"/><category term="pip"/><category term="python-standard-library"/><category term="real-python"/><category term="virtual-environments"/></entry><entry><title>Cloning a Single Production Database Table from Heroku to Local Database with PostgreSQL in Docker Compose</title><link href="https://startcodingnow.com/cloning-prod-to-local-postgres-docker-heroku" rel="alternate"/><published>2021-09-22T00:00:00-07:00</published><updated>2021-09-22T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2021-09-22:/cloning-prod-to-local-postgres-docker-heroku</id><summary type="html">&lt;p&gt;I have a few Django apps in a Django project. I used &lt;tt class="docutils literal"&gt;factory_boy&lt;/tt&gt; to
mock a bunch of the data, but I have a few hundred rows of accurate data
in my production database. It made sense to work on a copy of that data
as it contains no sensitive …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have a few Django apps in a Django project. I used &lt;tt class="docutils literal"&gt;factory_boy&lt;/tt&gt; to
mock a bunch of the data, but I have a few hundred rows of accurate data
in my production database. It made sense to work on a copy of that data
as it contains no sensitive user information.&lt;/p&gt;
&lt;p&gt;My project is hosted on Heroku with one of their free tier PostgreSQL
databases. My local environment is a Docker Compose stack with images
for Python, PostgreSQL, and Redis.&lt;/p&gt;
&lt;p&gt;The trickiest part here, in my opinion, is finding out just how to get
the backup to the PostgreSQL docker container.&lt;/p&gt;
&lt;div class="section" id="table-of-contents"&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Table of Contents&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="#backup"&gt;Backup the Production Database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="#clone"&gt;Clone to Local Database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="backup-the-production-database"&gt;
&lt;h2&gt;Backup the Production Database&lt;/h2&gt;
&lt;p&gt;There are two easy ways to do this on Heroku&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Use the Heroku CLI&lt;/li&gt;
&lt;li&gt;Use the Heroku web interface&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a class="reference external" href="https://devcenter.heroku.com/articles/heroku-postgres-backups"&gt;The Heroku docs detail how to make a
backup.&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="use-the-heroku-cli-to-backup-database"&gt;
&lt;h3&gt;Use the Heroku CLI to Backup Database&lt;/h3&gt;
&lt;p&gt;So first, if you aren’t already using it, you’ll need to &lt;a class="reference external" href="https://devcenter.heroku.com/articles/heroku-cli#download-and-install"&gt;install the
Heroku
CLI&lt;/a&gt;
and run &lt;tt class="docutils literal"&gt;heroku login&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Next, you’ll need to know your app’s name in Heroku. Mine is
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;mastering-fitness&lt;/span&gt;&lt;/tt&gt;, but you can get a list of all your Heroku apps
right there in the terminal.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;heroku&lt;span class="w"&gt; &lt;/span&gt;apps
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Before we make a backup, let’s get acquainted with the &lt;tt class="docutils literal"&gt;pg:backups&lt;/tt&gt;
command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;heroku&lt;span class="w"&gt; &lt;/span&gt;pg:backups&lt;span class="w"&gt; &lt;/span&gt;--help
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Read through that briefly as it shows you your options.&lt;/p&gt;
&lt;p&gt;To make a backup manually, I will run the following command. You should
put your app name in place of mine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;heroku&lt;span class="w"&gt; &lt;/span&gt;pg:backups:capture&lt;span class="w"&gt; &lt;/span&gt;--app&lt;span class="w"&gt; &lt;/span&gt;mastering-fitness
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, download the file locally.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;heroku&lt;span class="w"&gt; &lt;/span&gt;pg:backups:download&lt;span class="w"&gt; &lt;/span&gt;--app&lt;span class="w"&gt; &lt;/span&gt;mastering-fitness
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that this file will download to your active directory in the
terminal. I am working out of VSCode so it went right into my root
project folder. But we need to give it to our PostgreSQL Docker
container…&lt;/p&gt;
&lt;p&gt;Let’s talk about making a backup with the Heroku web interface next. If
you’ve made your backup, feel free to skip to &lt;a class="reference external" href="#clone"&gt;Cloning to Local
Database&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-the-heroku-web-interface-to-backup-database"&gt;
&lt;h3&gt;Use the Heroku Web Interface to Backup Database&lt;/h3&gt;
&lt;p&gt;Log in at &lt;a class="reference external" href="https://id.heroku.com/login"&gt;heroku.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Find and click on your app.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Find and click on your app" src="./images/heroku-db-backup-1.png"/&gt;
&lt;p class="caption"&gt;Find and click on your app&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Find and click on your Postgres database.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Find and click on your Postgres database" src="./images/heroku-db-backup-2.png"/&gt;
&lt;p class="caption"&gt;Find and click on your Postgres database&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Click on the Durability tab.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Click on the Durability tab" src="./images/heroku-db-backup-3.png"/&gt;
&lt;p class="caption"&gt;Click on the Durability tab&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Click on Create a Manual Backup.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Click on Create a Manual Backup" src="./images/heroku-db-backup-4.png"/&gt;
&lt;/div&gt;
&lt;p&gt;Once it’s finished, you should see it right there on the page.&lt;/p&gt;
&lt;p&gt;Download the backup and then you’re ready to clone it to your local
table.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Download the backup and then you’re ready to clone it to your local table" src="./images/heroku-db-backup-5.png"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="cloning-to-local-database"&gt;
&lt;h2&gt;Cloning to Local Database&lt;/h2&gt;
&lt;p&gt;I ran into trouble when first trying this because my local database
already had some data. I got a bunch of errors when trying to import the
production data, but I didn’t want to mess with ALL the data, just a
single table in the database.&lt;/p&gt;
&lt;div class="section" id="destroying-your-old-local-data"&gt;
&lt;h3&gt;Destroying Your Old Local Data&lt;/h3&gt;
&lt;p&gt;If you’re okay with destroying everything, you can use the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--clean&lt;/span&gt;&lt;/tt&gt;
flag as Matt Segal noted in his article &lt;a class="reference external" href="https://mattsegal.dev/restore-django-local-database.html"&gt;How to pull production data
into your local Postgres
database&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have plenty of usable data in most of my apps, but one of them is a
list of fitness exercises that I wanted to just nuke and replace. I used
the Django admin to remove the five or so testing records I had locally.
I am not sure if the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--clean&lt;/span&gt;&lt;/tt&gt; flag will delete that single table if
you specify a table, so (1) be careful, and (2) if you know the truth,
leave a comment below. I’d love to know.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-pg-restore-command"&gt;
&lt;h3&gt;The pg_restore Command&lt;/h3&gt;
&lt;p&gt;The basic command to import the backup into your local Postgres database
is…&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pg_restore&lt;span class="w"&gt; &lt;/span&gt;--dbname&lt;span class="w"&gt; &lt;/span&gt;DB_NAME&lt;span class="w"&gt; &lt;/span&gt;--host&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--username&lt;span class="w"&gt; &lt;/span&gt;USERNAME&lt;span class="w"&gt; &lt;/span&gt;--no-owner&lt;span class="w"&gt; &lt;/span&gt;FILE_TO_IMPORT
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But we need to run this in our Postgres Docker container. We might try
something like this…&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker-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;db&lt;span class="w"&gt; &lt;/span&gt;pg_restore&lt;span class="w"&gt; &lt;/span&gt;--dbname&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;--host&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--username&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;--no-owner&lt;span class="w"&gt; &lt;/span&gt;latest.dump
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;…where&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;“db” is the name of our Docker Compose service for Postgres&lt;/li&gt;
&lt;li&gt;“postgres” is the name we gave our database AND the user&lt;/li&gt;
&lt;li&gt;“latest.dump” is the file we downloaded from Heroku&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But the command won’t be able to find the file because it does not exist
there. We need to move it to the Postgres volume where we’re persisting
our database’s data.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="help-docker-db-service-find-our-postgres-backup-file"&gt;
&lt;h3&gt;Help Docker db Service Find Our Postgres Backup File&lt;/h3&gt;
&lt;p&gt;Here’s the docker-compose.yml file&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"3.7"&lt;/span&gt;

&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./app&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python manage.py runserver 0.0.0.0:8000&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./app/:/usr/src/app/&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;8000:8000&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;./.env.dev&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;db&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;postgres:12-alpine&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;postgres_data:/var/lib/postgresql/data/&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;POSTGRES_USER=postgres&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;POSTGRES_PASSWORD=secretsecretivegotasecret&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;POSTGRES_DB=postgres&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;always&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;redis:6.2.3&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"6379"&lt;/span&gt;

&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;postgres_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’ve got a volume called “postgres_data” that maps to the
&lt;tt class="docutils literal"&gt;/var/lib/postgresql/data/&lt;/tt&gt; directory inside our Postgres container.
But where is that on our local filesystem?&lt;/p&gt;
&lt;p&gt;Let’s examine our Docker Volumes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;volume&lt;span class="w"&gt; &lt;/span&gt;ls
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will show you the full name of that “postgres_data” volume,
prepended with the project name. Mine is
“lance-fitness-store_postgres_data”. Let’s inspect that volume.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;volume&lt;span class="w"&gt; &lt;/span&gt;inspect&lt;span class="w"&gt; &lt;/span&gt;lance-fitness-store_postgres_data
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In there we see a value for “Mountpoint”. This is what we’ve been
looking for! We need to copy our database backup file “local.dump” over
to that folder if we want the “db” service to have access to it. To do
it in the terminal…&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;latest.dump&lt;span class="w"&gt; &lt;/span&gt;/var/lib/docker/volumes/lance-fitness-store_postgres_data_dev/_data/latest.dump
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, we need to point to the file in the volume’s folder in the
container. Remember the file location we found in docker-compose.yml?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;postgres:12-alpine&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;postgres_data:/var/lib/postgresql/data/&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So inside the container my backup will be located at:
&lt;tt class="docutils literal"&gt;/var/lib/postgresql/data/latest.dump&lt;/tt&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="importing-only-one-table"&gt;
&lt;h3&gt;Importing Only One Table&lt;/h3&gt;
&lt;p&gt;We’re almost there!&lt;/p&gt;
&lt;p&gt;We just need to add a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--table&lt;/span&gt;&lt;/tt&gt; flag to our &lt;tt class="docutils literal"&gt;pg_restore&lt;/tt&gt; command.&lt;/p&gt;
&lt;p&gt;You’ll have to know the name of your Django table. The Django style is
&lt;tt class="docutils literal"&gt;appname_modelname&lt;/tt&gt;, but &lt;a class="reference external" href="https://docs.djangoproject.com/en/dev/ref/models/options/#table-names"&gt;you can override it if you’d
like&lt;/a&gt;.
I have an Exercise model in an app named exercises. My table is
&lt;tt class="docutils literal"&gt;exercises_exercise&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;So my final command becomes…&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker-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;db&lt;span class="w"&gt; &lt;/span&gt;pg_restore&lt;span class="w"&gt; &lt;/span&gt;--dbname&lt;span class="w"&gt; &lt;/span&gt;store_dev&lt;span class="w"&gt; &lt;/span&gt;--host&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--username&lt;span class="w"&gt; &lt;/span&gt;lance_dev&lt;span class="w"&gt; &lt;/span&gt;--data-only&lt;span class="w"&gt; &lt;/span&gt;--table&lt;span class="w"&gt; &lt;/span&gt;exercises_exercise&lt;span class="w"&gt; &lt;/span&gt;--no-owner&lt;span class="w"&gt; &lt;/span&gt;/var/lib/postgresql/data/latest.dump
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Hopefully that went without any hiccups! But just in case it did, I
tried to outline the goal and thought process of each step here.&lt;/p&gt;
&lt;p&gt;Best of luck!&lt;/p&gt;
&lt;p&gt;Header database icon: database by Anton Outkine from the Noun Project&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="django"/><category term="docker"/><category term="docker-compose-yml"/><category term="postgresql"/><category term="python"/><category term="python-3"/></entry><entry><title>Getting Access to the Wide Internet in Your Hyper-V Virtual Machine</title><link href="https://startcodingnow.com/getting-access-to-the-wide-internet-in-your-hyper-v-virtual-machine" rel="alternate"/><published>2021-04-26T00:00:00-07:00</published><updated>2021-04-26T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2021-04-26:/getting-access-to-the-wide-internet-in-your-hyper-v-virtual-machine</id><summary type="html">&lt;p&gt;I’m using Hyper-V on my workstation to run a Ubuntu Linux virtual
machine.&lt;/p&gt;
&lt;p&gt;I recently moved and changed my office setup. Previously I had a Cat 6
cable into a 2.5G Realtek Ethernet Adapter running into my router, plus
a SFP+ DAC cable running into a 10G switch …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I’m using Hyper-V on my workstation to run a Ubuntu Linux virtual
machine.&lt;/p&gt;
&lt;p&gt;I recently moved and changed my office setup. Previously I had a Cat 6
cable into a 2.5G Realtek Ethernet Adapter running into my router, plus
a SFP+ DAC cable running into a 10G switch to connect to my local
network server.&lt;/p&gt;
&lt;p&gt;With the move, though, I wanted to simplify the setup. I’m no longer
using a Cat 6 cable and 2.5G NIC to get Internet access. Now I’m only
using the small 10 gigabit switch, the Mikrotik CRS305-1G-4S+IN, and
that cable supplies my internal and external network needs.&lt;/p&gt;
&lt;p&gt;I highly recommend this setup even if it’s just for a local connection.
It’s really cool to saturate RAID’ed drive transfer speeds when moving
my videos around. If you’re curious, here’s a longer post detailing how
I did it all: &lt;a class="reference external" href="https://startcodingnow.com/10-gigabit-networking/"&gt;10 Gigabit Networking Between Home Server and
Computer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The problem is that removing that 2.5G network cable broke my VM’s
internet access.&lt;/p&gt;
&lt;p&gt;In this post, I’ll detail what happened and how to fix it.&lt;/p&gt;
&lt;div class="section" id="losing-internet-access-from-hyper-v-virtual-machine"&gt;
&lt;h2&gt;Losing Internet Access from Hyper-V Virtual Machine&lt;/h2&gt;
&lt;p&gt;How does Hyper-V manage networks? What are “virtual switches”?&lt;/p&gt;
&lt;p&gt;Hyper-V creates a virtual switch to connect your VM to your physical
switch.&lt;/p&gt;
&lt;p&gt;If you open up Hyper-V and right click on the server (the name of the
Hyper-V host, a.k.a. the name of my workstation), you can click on
&lt;strong&gt;Virtual Switch Manager…&lt;/strong&gt; to open up some helpful settings.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Hyper-V Virtual Switch Manager shows four virtual switches: default switch, an external switch for the Realtek PCIe 2.5GbE network card, a WiFi switch, and a 10GbE switch." src="./images/Hyper-V-Virtual-Switch-Manager.png"/&gt;
&lt;/div&gt;
&lt;p&gt;My workstation has four virtual switches:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The first listed there is the Realtek 2.5G network card I created&lt;/li&gt;
&lt;li&gt;The second listed is the default that Hyper-V created&lt;/li&gt;
&lt;li&gt;The third listed is one tied to my WiFi network card that I created&lt;/li&gt;
&lt;li&gt;The fourth listed is one tied to my 10G network card that I created&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are quite a few settings when making these virtual switches, but
they are described well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-to-create-a-new-virtual-switch-in-hyper-v"&gt;
&lt;h2&gt;How to Create a New Virtual Switch in Hyper-V&lt;/h2&gt;
&lt;p&gt;When you click on &lt;strong&gt;New virtual network switch&lt;/strong&gt;, the dialog asks what
type it should be.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;External switches give you access to the outside network&lt;/li&gt;
&lt;li&gt;Internal switches connect your VMs on the same server and your host
OS&lt;/li&gt;
&lt;li&gt;Private switches connect only your VMs on the same server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to get wide internet access, you need a new External switch.&lt;/p&gt;
&lt;p&gt;The next screen allows you to pick the “Connection Type”. Since my 10G
network adapter is what I want to use, I selected that.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Virtual Switch Manager in Hyper-V shows a virtual switch called “10G Switch” using the computer’s Mellanox ConnectX-2 Ethernet Adapter." src="./images/Hyper-V-creating-a-new-External-Virtual-Switch.png"/&gt;
&lt;/div&gt;
&lt;p&gt;Then, I also created one for my WiFi adapter in case my wired connection
is ever down.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Virtual Switch Manager in Hyper-V shows a virtual switch called “WiFi Switch” using the computer’s Wi-Fi network adapter." src="./images/Hyper-V-creating-a-new-External-Virtual-Switch-with-WiFi-network-adapter.png"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="add-the-new-virtual-switch-to-the-hyper-v-virtual-machine"&gt;
&lt;h2&gt;Add the New Virtual Switch to the Hyper-V Virtual Machine&lt;/h2&gt;
&lt;p&gt;Now that I have made the virtual switch, I need to tell the virtual
machine that it’s there.&lt;/p&gt;
&lt;p&gt;In the Hyper-V manager, right-click on your VM and select &lt;strong&gt;Settings…&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Now, if you previously had a virtual switch there, it’s likely you still
have the network adapter listed there. If you click on it, you can
change the virtual switch.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Editing settings in Hyper-V for a virtual machine’s network adapter. We select a new virtual switch, in this case one called “10G Switch” that we’ve just created." src="./images/hyper-v-10G-switch-selected.png"/&gt;
&lt;/div&gt;
&lt;p&gt;After hitting &lt;strong&gt;Apply&lt;/strong&gt;, you’ll likely need to re-authenticate into your
VM. But let’s go in and see if we have access to the network now.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="top right corner of ubuntu desktop" src="./images/network-adapter-visible-in-ubuntu.png"/&gt;
&lt;/div&gt;
&lt;p&gt;This little network icon in the top right of the screen gives me hope!
Let’s open the Network settings now.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Ubuntu desktop network settings dialog" src="./images/wired-network-adapter-present-in-ubuntu-network-settings.png"/&gt;
&lt;/div&gt;
&lt;p&gt;Beautiful! And if we test internet access in the web browser, it works.&lt;/p&gt;
&lt;p&gt;Success!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="extra-credit-adding-the-wifi-switch-to-our-hyper-v-virtual-machine"&gt;
&lt;h2&gt;Extra Credit: Adding the WiFi Switch to Our Hyper-V Virtual Machine&lt;/h2&gt;
&lt;p&gt;As a backup and as further practice, let’s add the new WiFi switch as
well.&lt;/p&gt;
&lt;p&gt;Back in our virtual machine’s settings, we want to &lt;strong&gt;Add Hardware&lt;/strong&gt;.
Select &lt;strong&gt;Network Adapter&lt;/strong&gt; and click &lt;strong&gt;Add&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Windows VM settings with Add Hardware &amp;gt; Network Adapter selected" src="./images/add-wifi-switch-to-hyper-v-vm-1.png"/&gt;
&lt;/div&gt;
&lt;p&gt;Then select the correct switch and hit &lt;strong&gt;Apply&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="WiFi switch selected" src="./images/add-wifi-switch-to-hyper-v-vm-2.png"/&gt;
&lt;p class="caption"&gt;WiFi switch selected&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;And then let’s check our network settings in ubuntu again.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Network settings in Ubuntu show two connections: eth0 @ 10000 Mb/s and eth1 @ 234 Mb/s." src="./images/10G-and-wifi-networks-in-ubuntu.png"/&gt;
&lt;/div&gt;
&lt;p&gt;Awesome! If it looks like the network card is there, but you’re getting
a “cable unplugged” message, just make sure your WiFi is turned on in
your host machine.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That’s it! I’m excited to be up-and-running once again with my virtual
machine.&lt;/p&gt;
&lt;p&gt;Good luck!&lt;/p&gt;
&lt;/div&gt;
</content><category term="windows"/><category term="10-gigabit-ethernet"/><category term="10-gigabit-networking"/><category term="ethernet"/><category term="hyper-v"/><category term="networking"/><category term="virtual-machines"/><category term="wifi"/><category term="windows"/></entry><entry><title>Create Your Own YouTube Analytics Dashboard SUPER FAST with Google Apps Script</title><link href="https://startcodingnow.com/youtube-analytics-dashboard-with-google-apps-script" rel="alternate"/><published>2021-04-20T00:00:00-07:00</published><updated>2021-04-20T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2021-04-20:/youtube-analytics-dashboard-with-google-apps-script</id><summary type="html">&lt;p&gt;In my day job as a personal trainer, I find myself doing a lot of
repetitive tasks. When writing a training program, there’s SO MUCH
clicking to format a Google Sheet well enough to make a professional
product.&lt;/p&gt;
&lt;p&gt;So I started playing around with macros and found it easy …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In my day job as a personal trainer, I find myself doing a lot of
repetitive tasks. When writing a training program, there’s SO MUCH
clicking to format a Google Sheet well enough to make a professional
product.&lt;/p&gt;
&lt;p&gt;So I started playing around with macros and found it easy to augment a
macro with &lt;a class="reference external" href="https://developers.google.com/apps-script"&gt;Google Apps
Script&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When recording a macro, the code is saved in the “Script Editor”.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Tools &amp;gt; Script editor will open the script editor." src="./images/google-sheets-script-editor-menu.png"/&gt;
&lt;p class="caption"&gt;Tools &amp;gt; Script editor will open the script editor.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure"&gt;
&lt;img alt="The Google Apps Script Editor." src="./images/google-apps-script-editor.png"/&gt;
&lt;p class="caption"&gt;The Google Apps Script Editor.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;And I come to find out that this Script Editor can do a lot for us!&lt;/p&gt;
&lt;p&gt;In this post, we’re going to make your own personal YouTube dashboard.&lt;/p&gt;
&lt;div class="section" id="table-of-contents"&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="#table-of-contents"&gt;Table of Contents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="#create-a-new-google-sheet"&gt;Create a New Google Sheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="#find-a-youtube-video-id"&gt;Find a YouTube Video ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="#write-the-script"&gt;Write the Script&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference external" href="#line-by-line-code-analysis"&gt;Line By Line Code Analysis&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="#add-the-youtube-service-to-the-script"&gt;Add the YouTube Service to the
Script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="#run-the-script"&gt;Run the Script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="#next-ideas"&gt;Next Ideas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="create-a-new-google-sheet"&gt;
&lt;h2&gt;Create a New Google Sheet&lt;/h2&gt;
&lt;p&gt;We’ll need a place to store your YouTube data. Create a new sheet and
give it three headings in the first columns:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Video ID&lt;/li&gt;
&lt;li&gt;Title&lt;/li&gt;
&lt;li&gt;Views&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="figure"&gt;
&lt;img alt="The first three cells have the headers “Video ID”, “Title”, and “Views”." src="./images/new-google-sheet-with-three-column-names.png"/&gt;
&lt;p class="caption"&gt;The first three cells have the headers “Video ID”, “Title”, and “Views”.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="find-a-youtube-video-id"&gt;
&lt;h2&gt;Find a YouTube Video ID&lt;/h2&gt;
&lt;p&gt;First, we’re going to need a video to analyze.&lt;/p&gt;
&lt;p&gt;I went and grabbed one of my top performers: &lt;a class="reference external" href="https://www.youtube.com/watch?v=2TPwtBl6_KQ"&gt;Elbow Pain During Push
Ups&lt;/a&gt;. Let’s examine the
link:&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;https://www.youtube.com/watch?v=2TPwtBl6_KQ&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;That value after the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;?v=&lt;/span&gt;&lt;/tt&gt; is the ID of the video.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;https://www.youtube.com/watch?v=**2TPwtBl6_KQ**&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;So we’re going to copy that for our Google Sheet and paste it into cell
A2.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="The video ID has been pasted in cell A2." src="./images/google-sheet-with-video-id-pasted-in.png"/&gt;
&lt;p class="caption"&gt;The video ID has been pasted in cell A2.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="write-the-script"&gt;
&lt;h2&gt;Write the Script&lt;/h2&gt;
&lt;p&gt;The inspiration for this post came from another &lt;a class="reference external" href="https://developers.googleblog.com/2015/10/a-day-at-office-automating-youtube_12.html?linkId=17889868"&gt;post by Wesley Chun on
the Google Developers
blog.&lt;/a&gt;
It’s a few years old now, but the code mostly works. The user interface
has changed a bit, though, so I’m going to walk your through it.&lt;/p&gt;
&lt;p&gt;Open up the Script Editor from your Google Sheet: Tools &amp;gt; Script Editor.&lt;/p&gt;
&lt;p&gt;And type in the following code:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
function getVideoInfo() {
  let sheet = SpreadsheetApp.getActiveSheet();
  let vid = sheet.getRange("A2").getValue();
  let data = YouTube.Videos.list('snippet, statistics', {id: vid});
  let item = data.items[0];
  let info = [item.snippet.title, item.statistics.viewCount];
  sheet.getRange("B2:C2").setValues([info]);
}
&lt;/pre&gt;
&lt;p&gt;Wesley does a great job walking through the code in the post, but I’ll
do so here for completeness.&lt;/p&gt;
&lt;div class="section" id="line-by-line-code-analysis"&gt;
&lt;h3&gt;Line By Line Code Analysis&lt;/h3&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;function getVideoInfo() {&lt;/tt&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Here we’re declaring the function we’re going to run.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;let sheet = &lt;span class="pre"&gt;SpreadsheetApp.getActiveSheet();&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;This tells the script what sheet we’re working on and is tied to our
current sheet&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;let vid = &lt;span class="pre"&gt;sheet.getRange("A2").getValue();&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;This selects the video ID we just pasted in. If we need it, we can
use the &lt;tt class="docutils literal"&gt;vid&lt;/tt&gt; variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;let data = &lt;span class="pre"&gt;YouTube.Videos.list('snippet,&lt;/span&gt; statistics', {id: &lt;span class="pre"&gt;vid});&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Here we’re using the &lt;a class="reference external" href="https://developers.google.com/youtube/v3/docs"&gt;YouTube Data
API&lt;/a&gt; to grab info
about our video.&lt;/li&gt;
&lt;li&gt;We use the
&lt;a class="reference external" href="https://developers.google.com/youtube/v3/docs/videos/list"&gt;YouTube.Videos.list&lt;/a&gt;
method.&lt;ul&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;'snippet, statistics'&lt;/tt&gt; tells the API what we want it to give
us.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;snippet&lt;/tt&gt; property contains the &lt;tt class="docutils literal"&gt;channelId&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;title&lt;/tt&gt;,
&lt;tt class="docutils literal"&gt;description&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;tags&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;categoryId&lt;/tt&gt; properties.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;statistics&lt;/tt&gt; property contains the &lt;tt class="docutils literal"&gt;viewCount&lt;/tt&gt;,
&lt;tt class="docutils literal"&gt;likeCount&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;dislikeCount&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;favoriteCount&lt;/tt&gt;, and
&lt;tt class="docutils literal"&gt;commentCount&lt;/tt&gt; properties.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;And we pass in the video ID from the &lt;tt class="docutils literal"&gt;vid&lt;/tt&gt; variable we just set.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;let item = data.items[0];&lt;/tt&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;We’ve only asked for one video, so our response from the YouTube Data
API has only given us one video in our array (at index 0).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;let info = [item.snippet.title, item.statistics.viewCount];&lt;/tt&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Grab the title and view count from the &lt;a class="reference external" href="https://developers.google.com/youtube/v3/docs/videos#resource"&gt;JSON response
data&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;sheet.getRange("B2:C2").setValues([info]);&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Select our B2:C2 range.&lt;/li&gt;
&lt;li&gt;Add the two values from the &lt;tt class="docutils literal"&gt;info&lt;/tt&gt; variable we just set.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="add-the-youtube-service-to-the-script"&gt;
&lt;h2&gt;Add the YouTube Service to the Script&lt;/h2&gt;
&lt;p&gt;This is where things start to look a little different from Wesley’s
original article. Here’s how we do it in April 2021.&lt;/p&gt;
&lt;p&gt;Click + in Services.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="The plus button for adding a new service." src="./images/google-sheets-script-editor-add-service.png"/&gt;
&lt;p class="caption"&gt;The plus button for adding a new service.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Scroll down and select the YouTube Data API.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="The Add a Service dialog box. The YouTube Data API v3 contains a link to the documentation link and can be identified with “YouTube”." src="./images/google-sheets-script-editor-add-service-dialog.png"/&gt;
&lt;p class="caption"&gt;The Add a Service dialog box. The YouTube Data API v3 contains a link
to the documentation link and can be identified with “YouTube”.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Then click “Add” and you should see it in the left menu.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="The YouTube service is showing." src="./images/youtube-service-in-google-apps-script-sidebar.png"/&gt;
&lt;p class="caption"&gt;The YouTube service is showing.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="run-the-script"&gt;
&lt;h2&gt;Run the Script&lt;/h2&gt;
&lt;p&gt;Then we can “Run” the script.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="The run button is towards the top of the window." src="./images/google-sheets-script-editor-run-script-2000x685.png"/&gt;
&lt;p class="caption"&gt;The run button is towards the top of the window.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;A dialog box will open asking for you to authenticate your Google
Account.&lt;/p&gt;
&lt;p&gt;You may also get a warning that executing the code from this sheet is
not safe. Chrome likes to warn you by hiding the ability to go on and
showing a blue “Back to Safety” button. I was able to bypass this by
clicking “Advanced” and proceeding anyways.&lt;/p&gt;
&lt;p&gt;Once you’ve done that, you might have to click “Run” one more time.&lt;/p&gt;
&lt;p&gt;When it runs, the Execution Log should open at the bottom of the window.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Execution log shows execution started and completed." src="./images/google-apps-script-app-execution-started-and-completed-2000x1073.png"/&gt;
&lt;p class="caption"&gt;Execution log shows execution started and completed.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;And if all went well, you’ll see what I’ve pasted above.&lt;/p&gt;
&lt;p&gt;Check the Google Sheet…&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="The video title has been placed in cell B2. The video view count has been placed in C2." src="./images/google-sheets-video-title-in-cell-2000x1073.png"/&gt;
&lt;p class="caption"&gt;The video title has been placed in cell B2. The video view count has been placed in C2.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;And there’s our data!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="next-ideas"&gt;
&lt;h2&gt;Next Ideas&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Track your top ten videos and find their views.&lt;/li&gt;
&lt;li&gt;Track your content marketing videos to see which perform best.&lt;/li&gt;
&lt;li&gt;Test different methods of engagement to see which videos get the most
comments, likes, and dislikes.&lt;/li&gt;
&lt;li&gt;Send these stats to your Gmail.&lt;/li&gt;
&lt;li&gt;Store a new file for every month with the highest performing videos
of the month.&lt;/li&gt;
&lt;li&gt;Grab data from a playlist you’ve created (perhaps a tutorial series?)&lt;/li&gt;
&lt;li&gt;Retrieve the IDs for your most popular videos and get all the data
you want about them.&lt;/li&gt;
&lt;li&gt;Create visual tables and graphs inside your Google Sheet to better
visualize your data.&lt;/li&gt;
&lt;li&gt;Share your results with a friend to hold yourself accountable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any other ideas? How are you using Google Apps Script? Share in a
comment below!&lt;/p&gt;
&lt;/div&gt;
</content><category term="google-apps-script"/><category term="apis"/><category term="authentication"/><category term="gmail"/><category term="google"/><category term="google-sheets"/><category term="graphs"/><category term="ideas"/><category term="tables"/><category term="youtube"/><category term="youtube-data-api"/></entry><entry><title>Using sorl-thumbnail with Redis on Heroku</title><link href="https://startcodingnow.com/using-sorl-thumbnail-with-redis-on-heroku" rel="alternate"/><published>2020-09-23T00:00:00-07:00</published><updated>2020-09-23T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2020-09-23:/using-sorl-thumbnail-with-redis-on-heroku</id><summary type="html">&lt;p&gt;I recently added
&lt;a class="reference external" href="https://github.com/jazzband/sorl-thumbnail"&gt;sorl-thumbnail&lt;/a&gt; to a
project for creating smaller image files. I chose to configure with a
Redis Key-Value Store, as I’ve heard Redis is super hip, but ran into
trouble during deployment.&lt;/p&gt;
&lt;p&gt;In this post, I’ll document how I set up Redis &lt;a class="reference external" href="#local"&gt;locally with
Docker&lt;/a&gt;, then …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently added
&lt;a class="reference external" href="https://github.com/jazzband/sorl-thumbnail"&gt;sorl-thumbnail&lt;/a&gt; to a
project for creating smaller image files. I chose to configure with a
Redis Key-Value Store, as I’ve heard Redis is super hip, but ran into
trouble during deployment.&lt;/p&gt;
&lt;p&gt;In this post, I’ll document how I set up Redis &lt;a class="reference external" href="#local"&gt;locally with
Docker&lt;/a&gt;, then &lt;a class="reference external" href="#prod"&gt;in production on Heroku&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="local-docker-setup"&gt;
&lt;h2&gt;Local Docker Setup&lt;/h2&gt;
&lt;p&gt;The logical place to start is with &lt;a class="reference external" href="https://sorl-thumbnail.readthedocs.io/en/latest/installation.html"&gt;the sorl-thumbnail
documentation.&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="install-the-package"&gt;
&lt;h3&gt;Install the package&lt;/h3&gt;
&lt;pre class="literal-block"&gt;
python3 -m pip install sorl-thumbnail
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="add-package-to-settings-py"&gt;
&lt;h3&gt;Add package to settings.py&lt;/h3&gt;
&lt;pre class="literal-block"&gt;
# settings.py
INSTALLED_APPS += ['sorl.thumbnail']
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="migrate-database"&gt;
&lt;h3&gt;Migrate database&lt;/h3&gt;
&lt;pre class="literal-block"&gt;
python manage.py migrate
&lt;/pre&gt;
&lt;p&gt;This should create a model called KVStore.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="add-redis-server"&gt;
&lt;h3&gt;Add redis server&lt;/h3&gt;
&lt;p&gt;If we navigate to &lt;a class="reference external" href="https://sorl-thumbnail.readthedocs.io/en/latest/requirements.html"&gt;the documentation’s requirements
page&lt;/a&gt;,
we’ll see we need some sort of key-value store and image processing
library.&lt;/p&gt;
&lt;p&gt;As the title suggests, we’re going to use Redis. Let’s add that in our
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yml&lt;/span&gt;&lt;/tt&gt; file.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# docker-compose.yml
version: "3.7"

services:
  web:
    build: ./django
    command: python /code/manage.py runserver 0.0.0.0:8000
    env_file:
      - ./.env.dev
    volumes:
      - ./django:/code
    ports:
      - 8000:8000
      - 443:443
    depends_on:
      - db
      - redis

  db:
    image: postgres
    volumes:
      - postgres_data:/var/lib/postgresql
    environment:
      - POSTGRES_HOST_AUTH_METHOD=trust
      - POSTGRES_USER=username
      - POSTGRES_PASS=supercomplexpasswordthatishardtocrack
      - POSTGRES_DBNAME=pg
    ports:
      - "5432:5432"

  redis:
    restart: always
    image: redis:latest
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/var/lib/redis

volumes:
  postgres_data:
  redis_data:

networks:
  default:
&lt;/pre&gt;
&lt;p&gt;I’m assuming here that your Django &lt;em&gt;web&lt;/em&gt; and &lt;em&gt;db&lt;/em&gt; services and volumes
are already configured. &lt;a class="reference external" href="https://learndjango.com/tutorials/django-docker-and-postgresql-tutorial"&gt;Use this tutorial to get started with Docker,
Django, and
Postgres.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;First, we’re adding a &lt;em&gt;redis&lt;/em&gt; service (&lt;tt class="docutils literal"&gt;redis:&lt;/tt&gt;) and volume
(&lt;tt class="docutils literal"&gt;redis_data&lt;/tt&gt;). Then, we’re telling the &lt;em&gt;web&lt;/em&gt; service that we need
&lt;em&gt;redis&lt;/em&gt; for things to work properly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="add-redis-client"&gt;
&lt;h3&gt;Add Redis client&lt;/h3&gt;
&lt;pre class="literal-block"&gt;
python3 -m pip install redis
&lt;/pre&gt;
&lt;p&gt;Here’s a link to this &lt;a class="reference external" href="https://github.com/andymccurdy/redis-py"&gt;redis-py project on
Github.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="add-a-python-image-library"&gt;
&lt;h3&gt;Add a Python image library&lt;/h3&gt;
&lt;p&gt;If you’ve gotten to this page, you probably already have
&lt;a class="reference external" href="https://pillow.readthedocs.io/en/stable/"&gt;Pillow&lt;/a&gt; installed, but
&lt;a class="reference external" href="https://sorl-thumbnail.readthedocs.io/en/latest/requirements.html#image-library"&gt;you can use a few different image
libraries.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="connect-redis-server-and-client"&gt;
&lt;h3&gt;Connect Redis server and client&lt;/h3&gt;
&lt;p&gt;Lastly, sorl-thumbnail has some settings to configure to link it up to
your Redis key-value store.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# settings.py
THUMBNAIL_KVSTORE = 'sorl.thumbnail.kvstores.redis_kvstore.KVStore'
&lt;/pre&gt;
&lt;p&gt;But if we try to &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose&lt;/span&gt; up&lt;/tt&gt; with just this settings, we
receive an error from Django:&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;**Connection &lt;span class="pre"&gt;error.**&lt;/span&gt; Error 99 connecting to localhost:6379. Cannot assign requested address.&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;The sorl-thumbnail package tries to use some sensible default values for
connecting to the Redis server:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;THUMBNAIL_REDIS_DB&lt;/tt&gt; = &lt;tt class="docutils literal"&gt;0&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;THUMBNAIL_REDIS_PASSWORD&lt;/tt&gt; = &lt;tt class="docutils literal"&gt;''&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;THUMBNAIL_REDIS_HOST&lt;/tt&gt; = &lt;tt class="docutils literal"&gt;'localhost'&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;THUMBNAIL_REDIS_PORT&lt;/tt&gt; = &lt;tt class="docutils literal"&gt;6379&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The problem is that we called our Redis host &lt;em&gt;redis&lt;/em&gt;, so sorl-thumbnail
cannot connect.&lt;/p&gt;
&lt;p&gt;I like to be explicit about variables at the expense of verbosity.
Here’s all my sorl-thumbnail settings:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# settings.py

# sorl-thumbnail
THUMBNAIL_KVSTORE = 'sorl.thumbnail.kvstores.redis_kvstore.KVStore'
THUMBNAIL_REDIS_DB = 0  # the default
THUMBNAIL_REDIS_HOST = 'redis'
THUMBNAIL_REDIS_PORT = 6379  # the default
&lt;/pre&gt;
&lt;p&gt;Then you should be all set locally!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="heroku-production-setup"&gt;
&lt;h2&gt;Heroku Production Setup&lt;/h2&gt;
&lt;p&gt;Production is a little different, but the principles still apply:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Redis server&lt;/li&gt;
&lt;li&gt;Redis client&lt;/li&gt;
&lt;li&gt;Connect sorl-thumbnail to server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sorl-thumbnail uses the redis-py package (the Redis client) to connect
to the Redis server. At the time of writing, sorl-thumbnail’s reference
documentation only showed our above settings as configurable.&lt;/p&gt;
&lt;p&gt;But I went and looked at the code and found a nice little secret! We’ll
use it later.&lt;/p&gt;
&lt;div class="section" id="provision-heroku-redis-add-on"&gt;
&lt;h3&gt;Provision heroku-redis add-on&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://devcenter.heroku.com/articles/heroku-redis#provisioning-the-add-on"&gt;The Heroku documentation shows you how to create a new heroku-redis
add-on&lt;/a&gt;
from the command line or Heroku dashboard.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
heroku addons:create heroku-redis:hobby-dev -a your-app-name
&lt;/pre&gt;
&lt;p&gt;The hobby-dev tier is free.&lt;/p&gt;
&lt;p&gt;This will add a &lt;tt class="docutils literal"&gt;REDIS_URL&lt;/tt&gt; configuration or environment variable to
your Heroku app. It should look like “redis://…”.&lt;/p&gt;
&lt;p&gt;But we didn’t use this variable to connect our project to Redis in our
local environment. How will we use REDIS_URL?&lt;/p&gt;
&lt;p&gt;That’s the secret I just mentioned at the end of the previous section!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configure-django-settings-to-use-redis-url"&gt;
&lt;h3&gt;Configure Django settings to use &lt;tt class="docutils literal"&gt;REDIS_URL&lt;/tt&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/jazzband/sorl-thumbnail/blob/bddd5e2778b0d7bccddd634248effb6b1de81ed3/sorl/thumbnail/kvstores/redis_kvstore.py"&gt;Here’s a link to the sorl-thumbnail code used to connect to
Redis.&lt;/a&gt;
It first searches for a &lt;tt class="docutils literal"&gt;THUMBNAIL_REDIS_URL&lt;/tt&gt; environment variable,
which contains the Redis username, password, host, and port.&lt;/p&gt;
&lt;p&gt;We want to use the &lt;tt class="docutils literal"&gt;REDIS_URL&lt;/tt&gt; variable set by heroku-redis because
that URL might change in the future without us knowing, but will always
be stored as &lt;tt class="docutils literal"&gt;REDIS_URL&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;We need to replace our previous Django settings for sorl-thumbnail and
Redis connections.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# sorl-thumbnail
THUMBNAIL_KVSTORE = 'sorl.thumbnail.kvstores.redis_kvstore.KVStore'
THUMBNAIL_REDIS_URL = os.environ.get('REDIS_URL')
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that should be all! Hopefully everything works for you on your first
try and you feel like a genius.&lt;/p&gt;
&lt;p&gt;I’ve opened an issue for including the &lt;tt class="docutils literal"&gt;THUMBNAIL_REDIS_URL&lt;/tt&gt; in the
Reference documentation.&lt;/p&gt;
&lt;p&gt;Overall lesson today is to keep your eye out for strange anomalies like
this. Sometimes there is a way to do what you need to do, but someone
hasn’t gotten around to documenting it yet.&lt;/p&gt;
&lt;p&gt;Maybe that someone could be you!&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="django"/><category term="key-value-store"/><category term="redis"/><category term="redis-py"/><category term="sorl-thumbnail"/></entry><entry><title>Authenticating Django PostgreSQL User in Multiple Docker Compose Environments</title><link href="https://startcodingnow.com/authenticating-django-postgresql-user-in-multiple-docker-compose-environments" rel="alternate"/><published>2020-09-01T00:00:00-07:00</published><updated>2020-09-01T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2020-09-01:/authenticating-django-postgresql-user-in-multiple-docker-compose-environments</id><summary type="html">&lt;p&gt;I’ve been building a Django project template using Docker Compose,
PostgreSQL, and Nginx. Docker is a definite weak point for me, so I used
an article from Michael Herman to set it up: &lt;a class="reference external" href="https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/"&gt;Dockerizing Django with
Postgres, Gunicorn, and
Nginx.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I made some additions to that tutorial – custom user …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I’ve been building a Django project template using Docker Compose,
PostgreSQL, and Nginx. Docker is a definite weak point for me, so I used
an article from Michael Herman to set it up: &lt;a class="reference external" href="https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/"&gt;Dockerizing Django with
Postgres, Gunicorn, and
Nginx.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I made some additions to that tutorial – custom user model, re-organized
config files – but I kept running into the same issue…&lt;/p&gt;
&lt;div class="section" id="django-cant-connect-to-postgres"&gt;
&lt;h2&gt;Django Can’t Connect to Postgres&lt;/h2&gt;
&lt;p&gt;When switching between development and production environments, Django
could not connect to my postgres database:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker-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
docker-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;python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate

django.db.utils.OperationalError:&lt;span class="w"&gt; &lt;/span&gt;FATAL:&lt;span class="w"&gt; &lt;/span&gt;password&lt;span class="w"&gt; &lt;/span&gt;authentication&lt;span class="w"&gt; &lt;/span&gt;failed&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;user&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;username&gt;"&lt;/username&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I had set up the same username for both the development and the
production environment postgres services, but the passwords were
different. For some reason, Docker Compose wasn’t re-configuring
postgres with the new information when I switched containers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="temporary-fix"&gt;
&lt;h2&gt;Temporary Fix&lt;/h2&gt;
&lt;p&gt;I was able to avoid the problem with some help from the testdriven.io
tutorial mentioned above:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;down&lt;span class="w"&gt; &lt;/span&gt;-v
docker-compose&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;docker-compose.prod.yml&lt;span class="w"&gt; &lt;/span&gt;down&lt;span class="w"&gt; &lt;/span&gt;-v
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-v&lt;/span&gt;&lt;/tt&gt; flag brings down all volumes, i.e. my postgres database.
Then, when bringing the containers back up, Docker Compose would say, “I
don’t have any volume called &lt;tt class="docutils literal"&gt;postgres_data&lt;/tt&gt;. I better make one!”&lt;/p&gt;
&lt;p&gt;This would totally work, but I would lose any data that I’d put in
postgres. In the long term, this will prove to be a headache.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="more-permanent-fix"&gt;
&lt;h2&gt;More Permanent Fix&lt;/h2&gt;
&lt;p&gt;I decided to give each postgres database service it’s own name in my
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yml&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.prod.yml&lt;/span&gt;&lt;/tt&gt; files. Instead of
&lt;tt class="docutils literal"&gt;postgres_data&lt;/tt&gt;, I used &lt;tt class="docutils literal"&gt;postgres_data_dev&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;postgres_data_prod&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="but-wait"&gt;
&lt;h2&gt;But wait!&lt;/h2&gt;
&lt;p&gt;In this process, I received the following warning:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;up

WARNING:&lt;span class="w"&gt; &lt;/span&gt;Service&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"db"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;volume&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/lib/postgresql/data"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;previous&lt;span class="w"&gt; &lt;/span&gt;container.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I had taken down the containers, but since I hadn’t removed them,
changing &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yml&lt;/span&gt;&lt;/tt&gt; tried to use the same part of my
filesystem.&lt;/p&gt;
&lt;p&gt;To fix, I removed the volumes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;down&lt;span class="w"&gt; &lt;/span&gt;-v
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I also heard something about &lt;tt class="docutils literal"&gt;orphan containers&lt;/tt&gt;. The warning in the
command line will help you solve that.&lt;/p&gt;
&lt;p&gt;Featured Image Photo Credit: &lt;a class="reference external" href="https://www.flickr.com/photos/kubina/912714753"&gt;Jeff
Kubina&lt;/a&gt;, unmodified
(&lt;a class="reference external" href="https://creativecommons.org/licenses/by-sa/2.0/legalcode"&gt;CC BY-SA
2.0&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="docker-compose-prod-yml"/><category term="docker-compose-yml"/><category term="environment-variables"/><category term="postgresql"/></entry><entry><title>Caution About Faking Django Migrations</title><link href="https://startcodingnow.com/caution-fake-django-migrations" rel="alternate"/><published>2020-07-22T00:00:00-07:00</published><updated>2020-07-22T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2020-07-22:/caution-fake-django-migrations</id><summary type="html">&lt;p&gt;I ran into a problem recently when trying to reconfigure my Django
project’s database.&lt;/p&gt;
&lt;p&gt;I had a DateTimeField on my model that I was updating to have
&lt;tt class="docutils literal"&gt;null=True&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;blank=True&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;There were no rows in this table. I was able to make the migrations, but
then ran …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I ran into a problem recently when trying to reconfigure my Django
project’s database.&lt;/p&gt;
&lt;p&gt;I had a DateTimeField on my model that I was updating to have
&lt;tt class="docutils literal"&gt;null=True&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;blank=True&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;There were no rows in this table. I was able to make the migrations, but
then ran into this error when running &lt;tt class="docutils literal"&gt;python manage.py migrate&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
TypeError: expected string or bytes-like object
&lt;/pre&gt;
&lt;div class="section" id="what-is-this-typeerror"&gt;
&lt;h2&gt;What is This TypeError?&lt;/h2&gt;
&lt;p&gt;Honestly, after my research, I’m not totally sure. I believe Django was
trying to put a NULL value in that DateTime table column, but since the
migration hadn’t been run, NULL values were still not allowed.&lt;/p&gt;
&lt;p&gt;I found something that made my error message go away… but also
introduced a bug.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="djangos-migrations-table"&gt;
&lt;h2&gt;Django’s Migrations Table&lt;/h2&gt;
&lt;p&gt;Django’s migrations actually create a table in your database where
database updates are tracked. Here’s a look at that table from one of my
newer projects:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
postgres=# \d django_migrations
                                     Table "public.django_migrations"
 Column  |           Type           | Collation | Nullable |                    Default
---------+--------------------------+-----------+----------+-----------------------------------------------
 id      | integer                  |           | not null | nextval('django_migrations_id_seq'::regclass)
 app     | character varying(255)   |           | not null |
 name    | character varying(255)   |           | not null |
 applied | timestamp with time zone |           | not null |
Indexes:
    "django_migrations_pkey" PRIMARY KEY, btree (id)
&lt;/pre&gt;
&lt;p&gt;At least in my simplified view of Django migrations, there are two
things that need to happen:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Django needs to know the migration happened so it doesn’t try to
repeat actions&lt;/li&gt;
&lt;li&gt;The database actually needs to be updated with SQL commands&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This django_migrations table tells Django what has been done.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="faking-migrations"&gt;
&lt;h2&gt;Faking Migrations&lt;/h2&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-admin&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;manage.py&lt;/tt&gt; have &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--fake&lt;/span&gt;&lt;/tt&gt; flag you can add to a
migrate command. This argument tells Django that a migration has
happened, but DOES NOT RUN IT. That is, it creates a row in the
django_migrations table, but does not run the SQL to change the database
structure. &lt;a class="reference external" href="https://docs.djangoproject.com/en/3.0/ref/django-admin/#migrate"&gt;Here’s a link to the
docs.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And here’s another link to &lt;a class="reference external" href="https://realpython.com/digging-deeper-into-migrations/#how-django-detects-changes-to-your-models"&gt;an article on Django Migrations from Real
Python.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So I ran the command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
python manage.py migrate app_name --fake
&lt;/pre&gt;
&lt;p&gt;And I saw no errors! Yay!&lt;/p&gt;
&lt;p&gt;But I had a sneaking suspicion I should do more digging…&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="checking-on-the-table"&gt;
&lt;h2&gt;Checking on the Table&lt;/h2&gt;
&lt;p&gt;Since this was the first time I’ve run the &lt;tt class="docutils literal"&gt;migrate &lt;span class="pre"&gt;--fake&lt;/span&gt;&lt;/tt&gt; command,
and the docs &lt;em&gt;clearly&lt;/em&gt; state, “without actually running the SQL to
change your database schema,” I figured there was a good chance my
DateTime field would still not accept null values.&lt;/p&gt;
&lt;p&gt;Run psql to see the command line interface:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
python manage.py dbshell
&lt;/pre&gt;
&lt;p&gt;or, if you’re &lt;a class="reference external" href="https://startcodingnow.com/psql-in-docker-compose/"&gt;using Docker Compose on a Linux VM like
me…&lt;/a&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo docker-compose exec db_service_name psql -U postgres_username
&lt;/pre&gt;
&lt;p&gt;When in psql, your prompt should change to something like
&lt;tt class="docutils literal"&gt;postgres=#&lt;/tt&gt;. Let’s inspect the table.&lt;/p&gt;
&lt;p&gt;Get a list of all tables:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
postgres=# \d
&lt;/pre&gt;
&lt;p&gt;Then inspect the appropriate table. Mine was a Mail models inside an app
named “mail”, so my table was named mail_mail.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
postgres=# \d mail_mail
                                        Table "public.mail_mail"
    Column    |           Type           | Collation | Nullable |                Default
--------------+--------------------------+-----------+----------+---------------------------------------
 id           | integer                  |           | not null | nextval('mail_mail_id_seq'::regclass)
 to_email     | character varying(254)   |           | not null |
 subject      | character varying(78)    |           | not null |
 body_text    | text                     |           | not null |
 body_html    | text                     |           | not null |
 created      | timestamp with time zone |           | not null |
 sent         | timestamp with time zone |           | not null |
 content_id   | integer                  |           | not null |
 recipient_id | integer                  |           | not null |
&lt;/pre&gt;
&lt;p&gt;The DateTime field I was trying to allow NULL values in is called
“sent”, describing when a mail was sent. The NULL value represents that
it has not been sent, otherwise a DateTime value represents when it was
sent.&lt;/p&gt;
&lt;p&gt;If you notice up there, the “Nullable” column in our psql output tells
us that “sent” cannot be null.&lt;/p&gt;
&lt;p&gt;If we don’t change this, we’ll run into trouble.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="revert-the-table"&gt;
&lt;h2&gt;Revert the Table&lt;/h2&gt;
&lt;p&gt;So again, I’m confused as to why this would happen considering I didn’t
have any data in the table. But no data means we can start from scratch.&lt;/p&gt;
&lt;p&gt;First, unapply your migrations:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
python manage.py migrate mail zero
&lt;/pre&gt;
&lt;p&gt;Then reapply migrations:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
python manage.py migrate mail
&lt;/pre&gt;
&lt;p&gt;If you have trouble, unapply back to “zero” and delete the migrations
files in your app_name/migrations/ directory. These files usually look
like “0001_initial.py”. Then you can &lt;tt class="docutils literal"&gt;makemigrations&lt;/tt&gt; again, then
re-try &lt;tt class="docutils literal"&gt;migrate&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="check-database-to-confirm-success"&gt;
&lt;h2&gt;Check Database to Confirm Success&lt;/h2&gt;
&lt;p&gt;Alright, let’s go back to psql and see if our migration was &lt;em&gt;actually&lt;/em&gt;
applied this time.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
postgres=# \d mail_mail
                                        Table "public.mail_mail"
    Column    |           Type           | Collation | Nullable |                Default
--------------+--------------------------+-----------+----------+---------------------------------------
 id           | integer                  |           | not null | nextval('mail_mail_id_seq'::regclass)
 to_email     | character varying(254)   |           | not null |
 subject      | character varying(78)    |           | not null |
 body_text    | text                     |           | not null |
 body_html    | text                     |           | not null |
 created      | timestamp with time zone |           | not null |
 content_id   | integer                  |           | not null |
 recipient_id | integer                  |           | not null |
 sent         | timestamp with time zone |           |          |
&lt;/pre&gt;
&lt;p&gt;Hey! The “sent” column is updated (as evidenced by the absence of “not
null”).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Hopefully you’ve taken in the word of caution about using &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--fake&lt;/span&gt;&lt;/tt&gt; in
your Django migrations. And hey, maybe you even learned a few ways to
use &lt;tt class="docutils literal"&gt;psql&lt;/tt&gt; in your project!&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="database"/><category term="django"/><category term="migrations"/><category term="postgresql"/><category term="psql"/></entry><entry><title>How to Use psql in Docker Compose</title><link href="https://startcodingnow.com/psql-in-docker-compose" rel="alternate"/><published>2020-07-15T00:00:00-07:00</published><updated>2020-07-15T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2020-07-15:/psql-in-docker-compose</id><summary type="html">&lt;p class="first last"&gt;Get a grasp on these multiple environments&lt;/p&gt;
</summary><content type="html">&lt;p&gt;As a Django newbie, I’ve found it easy to consider the database stuff
“magic” and just leave it alone.&lt;/p&gt;
&lt;p&gt;I’m starting to realize, however reluctantly, that it’s super important
to know how databases work!&lt;/p&gt;
&lt;p&gt;After seeing a software engineer YouTuber talk through a few of his
database tables with some simple commands, I decided that would be a
helpful skill.&lt;/p&gt;
&lt;p&gt;So I ran &lt;tt class="docutils literal"&gt;psql&lt;/tt&gt; and then… obviously it didn’t work because &lt;strong&gt;that
would be too easy.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this post, I’ll talk about how to use &lt;tt class="docutils literal"&gt;psql&lt;/tt&gt; to view your
PostgreSQL database from your Django project.&lt;/p&gt;
&lt;div class="section" id="the-docker-problem"&gt;
&lt;h2&gt;The Docker Problem&lt;/h2&gt;
&lt;p&gt;Docker has been great to me, but it’s also been a huge headache. There’s
just so much going on behind the scenes; it makes reality difficult to
comprehend. Layer that on top of learning Django and you’ve got a clear
path to overwhelm.&lt;/p&gt;
&lt;p&gt;For context, I can attribute my knowledge of Docker and Docker Compose
to Will Vincent from his book “Django for Professionals”. That book
really helped getting going, but deviating from that path is definitely
challenging.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-to-run-the-django-server-locally"&gt;
&lt;h2&gt;How to Run the Django Server Locally&lt;/h2&gt;
&lt;p&gt;Here’s a quick review of my local setup.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dockerfile&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# pull base image
FROM python:3.8

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# set work directory
WORKDIR /code

# install dependenceies
COPY Pipfile Pipfile.lock /code/
RUN pip install pipenv &amp;amp;&amp;amp; pipenv install --system

# copy project
COPY . /code/
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;docker-compose.yml&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
version: "3.7"

services:
  web:
    build: .
    command: python /code/manage.py runserver 0.0.0.0:8000
    env_file:
      - .env
    volumes:
      - .:/code
    ports:
      - 8000:8000
    depends_on:
      - db
  db:
    image: postgres:12
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - "POSTGRES_HOST_AUTH_METHOD=trust"
    env_file:
      - .env
    ports:
      - 5432:5432

volumes:
  postgres_data:
&lt;/pre&gt;
&lt;p&gt;Then to run the local Django server and local PostgreSQL database:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo docker-compose up -d --build
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="accessing-the-python-container"&gt;
&lt;h2&gt;Accessing the Python Container&lt;/h2&gt;
&lt;p&gt;When running management commands with Docker Compose, we have to specify
which service we want to run the command.&lt;/p&gt;
&lt;p&gt;I have two services: &lt;strong&gt;web&lt;/strong&gt; and &lt;strong&gt;db&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When I need to use Django’s &lt;tt class="docutils literal"&gt;manage.py&lt;/tt&gt;, I have to prepend this
command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo docker-compose exec web...
&lt;/pre&gt;
&lt;p&gt;So, for example, when I want to migrate my users app migrations, it
looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo docker-compose exec web python manage.py migrate users
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="python-container-or-database-container"&gt;
&lt;h2&gt;Python Container or Database Container?&lt;/h2&gt;
&lt;p&gt;So there are two ways (that I know) to access the &lt;tt class="docutils literal"&gt;psql&lt;/tt&gt; command.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;python manage.py dbshell&lt;/li&gt;
&lt;li&gt;psql&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="section" id="python-manage-py-dbshell"&gt;
&lt;h3&gt;python manage.py dbshell&lt;/h3&gt;
&lt;p&gt;As specified in the &lt;a class="reference external" href="https://docs.djangoproject.com/en/3.0/ref/django-admin/#dbshell"&gt;Django
docs&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;dbshell&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Runs the command-line client for the database engine specified in
your &lt;a class="reference external" href="https://docs.djangoproject.com/en/3.0/ref/settings/#std:setting-DATABASE-ENGINE"&gt;ENGINE&lt;/a&gt;
setting, with the connection parameters specified in
your &lt;a class="reference external" href="https://docs.djangoproject.com/en/3.0/ref/settings/#std:setting-USER"&gt;USER&lt;/a&gt;, &lt;a class="reference external" href="https://docs.djangoproject.com/en/3.0/ref/settings/#std:setting-PASSWORD"&gt;PASSWORD&lt;/a&gt;,
etc., settings.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;For PostgreSQL, this runs the &lt;strong&gt;psql&lt;/strong&gt; command-line client.&lt;/li&gt;
&lt;li&gt;For MySQL, this runs the &lt;strong&gt;mysql&lt;/strong&gt; command-line client.&lt;/li&gt;
&lt;li&gt;For SQLite, this runs the &lt;strong&gt;sqlite3&lt;/strong&gt; command-line client.&lt;/li&gt;
&lt;li&gt;For Oracle, this runs the &lt;strong&gt;sqlplus&lt;/strong&gt; command-line client.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This sounds like the way to go, so let’s run it.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo docker-compose exec web python manage.py dbshell
&lt;/pre&gt;
&lt;p&gt;And then we get an error:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
CommandError: You appear not to have the 'psql' program installed or on your path.
&lt;/pre&gt;
&lt;p&gt;WHAT!? How is that possible?&lt;/p&gt;
&lt;p&gt;Our PostgreSQL service “db” surely has &lt;tt class="docutils literal"&gt;psql&lt;/tt&gt; installed, but that’s a
different container. Instead of trying to install &lt;tt class="docutils literal"&gt;psql&lt;/tt&gt; inside our
“web” container, let’s try running &lt;tt class="docutils literal"&gt;psql&lt;/tt&gt; directly from the “db”
service.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="psql"&gt;
&lt;h3&gt;psql&lt;/h3&gt;
&lt;p&gt;To run, we’ll have to specify the “db” service:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo docker-compose exec db psql
&lt;/pre&gt;
&lt;p&gt;Annnnnnd we get another error:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
psql: error: could not connect to server: FATAL:  role "root" does not exist
&lt;/pre&gt;
&lt;p&gt;This error is telling us that &lt;tt class="docutils literal"&gt;psql&lt;/tt&gt; is, in fact, available. Confirm
by checking the version.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo docker-compose exec db psql --version
&lt;/pre&gt;
&lt;p&gt;Now the error told us that “role ‘root’ does not exist”. The default
command with no arguments is trying to log in as root. But if we check
out the &lt;tt class="docutils literal"&gt;.env&lt;/tt&gt; file, we can see we specified different values for the
PostgreSQL database name, username, and password:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=supersecretpassword
POSTGRES_HOST=db
POSTGRES_PORT=5432
&lt;/pre&gt;
&lt;p&gt;Well hey then, we don’t NEED to connect from “root”. What if we specify
the username with the -U flag?&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo docker-compose exec db psql -U postgres
&lt;/pre&gt;
&lt;p&gt;Then we get a new prompt:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
postgres=#
&lt;/pre&gt;
&lt;p&gt;This is the &lt;tt class="docutils literal"&gt;psql&lt;/tt&gt; prompt.&lt;/p&gt;
&lt;p&gt;If you’re still getting an error:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
psql: error: FATAL:  database "postgres" does not exist
&lt;/pre&gt;
&lt;p&gt;It’s because psql is trying to login to the database “postgres” as the
user “postgres”, but your database and user are different. You can
specify them both with the -d and -U flags:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo docker-compose exec db psql -d postgres_db -U postgres_username
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="next-steps"&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;Hey congrats! Now you can use psql!&lt;/p&gt;
&lt;p&gt;Or should I say, “Now you have to learn psql commands!”&lt;/p&gt;
&lt;p&gt;Here’s a few to get your started:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;List all databases: &lt;tt class="docutils literal"&gt;\l&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;View all tables in the current database: &lt;tt class="docutils literal"&gt;\d&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Describe a table: &lt;tt class="docutils literal"&gt;\d table_name&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;List all available users: &lt;tt class="docutils literal"&gt;\du&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Quit psql: &lt;tt class="docutils literal"&gt;\q&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Giving Django Project a “Two Scoops” Configuration</title><link href="https://startcodingnow.com/two-scoops-django-config" rel="alternate"/><published>2020-07-01T00:00:00-07:00</published><updated>2020-07-01T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2020-07-01:/two-scoops-django-config</id><summary type="html">&lt;p&gt;Years back I bought a physical copy of &lt;em&gt;Two Scoops of Django 1.11&lt;/em&gt; after
seeing it highly recommended in a book called &lt;a class="reference external" href="https://hellowebbooks.com/learn-django/"&gt;Hello Web
App&lt;/a&gt; by Tracy Osborn. Little
did I know it was way over my head at the time.&lt;/p&gt;
&lt;p&gt;With the release of &lt;a class="reference external" href="https://www.feldroy.com/products/two-scoops-of-django-3-x"&gt;Two Scoops of Django …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Years back I bought a physical copy of &lt;em&gt;Two Scoops of Django 1.11&lt;/em&gt; after
seeing it highly recommended in a book called &lt;a class="reference external" href="https://hellowebbooks.com/learn-django/"&gt;Hello Web
App&lt;/a&gt; by Tracy Osborn. Little
did I know it was way over my head at the time.&lt;/p&gt;
&lt;p&gt;With the release of &lt;a class="reference external" href="https://www.feldroy.com/products/two-scoops-of-django-3-x"&gt;Two Scoops of Django
3.X&lt;/a&gt;, and
with a lot of recent practice, the book is now only &lt;strong&gt;slightly&lt;/strong&gt; over my
head, so I’ve been trying to implement more of the practices recommended
in the book.&lt;/p&gt;
&lt;p&gt;I’ve also been continuing to use Docker and Docker Compose after
learning the basics in &lt;a class="reference external" href="https://djangoforprofessionals.com/"&gt;Django for
Professionals&lt;/a&gt; by Will Vincent.&lt;/p&gt;
&lt;div class="section" id="fixing-problems"&gt;
&lt;h2&gt;Fixing Problems?&lt;/h2&gt;
&lt;p&gt;Early on in &lt;em&gt;Two Scoops&lt;/em&gt;, the authors discuss how the standard project
layout from &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-admin&lt;/span&gt; startproject&lt;/tt&gt; leaves a lot to be desired.&lt;/p&gt;
&lt;p&gt;I definitely have NOT experienced that pain because I don’t have the
skills to make any complex projects. I do, however, see the value in
getting comfortable with a new layout. It teaches you how some of the
module inheritance in Django works. Python import statements are one of
the most challenging things for me, personally, so this is a good way to
train your coding skills.&lt;/p&gt;
&lt;p&gt;Let’s talk about two different project layouts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="standard-layout"&gt;
&lt;h2&gt;Standard layout&lt;/h2&gt;
&lt;p&gt;Here’s the base layout you get from running
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-admin&lt;/span&gt; startproject &lt;project_name&gt; &lt;directory_location&gt;&lt;/directory_location&gt;&lt;/project_name&gt;&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
django-admin&lt;span class="w"&gt; &lt;/span&gt;startproject&lt;span class="w"&gt; &lt;/span&gt;basic_startproject&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt;
&lt;/span&gt;tree.&lt;span class="w"&gt;
&lt;/span&gt;.&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;basic_startproject/&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;asgi.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;__init__.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;settings.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;urls.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;wsgi.py&lt;span class="w"&gt;
&lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;manage.py
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="two-scoops-layout"&gt;
&lt;h2&gt;Two Scoops layout&lt;/h2&gt;
&lt;p&gt;Here’s the directory layout recommended in &lt;em&gt;Two Scoops&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;repository_root&gt;&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;&lt;configuration_root&gt;&lt;span class="w"&gt;
&lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;&lt;django_project_root&gt;
&lt;/django_project_root&gt;&lt;/configuration_root&gt;&lt;/repository_root&gt;&lt;/pre&gt;
&lt;p&gt;Here’s what’s stored in the &lt;tt class="docutils literal"&gt;&lt;configuration_root&gt;&lt;/configuration_root&gt;&lt;/tt&gt; directory&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;configuration_root&gt;&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;&lt;settings&gt;&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;asgi.py&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;urls.py&lt;span class="w"&gt;
&lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;wsgi.py
&lt;/settings&gt;&lt;/configuration_root&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&lt;settings&gt;&lt;/settings&gt;&lt;/tt&gt; folder holds onto your multiple settings files. This
is another notable difference. See the &lt;em&gt;Two Scoops&lt;/em&gt; book for more info
on how to set that up; I found it helpful. Their guidance left me with
the following project structure:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
.&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;config/&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;asgi.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;settings/&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;base.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;__init__.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;local.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;production.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;urls.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;wsgi.py&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;&lt;django_project_root&gt;/&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;&lt;project_name&gt;/&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;       &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;__init__.py&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;docker-compose.yml&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;Dockerfile&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;docs/&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;Pipfile&lt;span class="w"&gt;
&lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;Pipfile.lock
&lt;/project_name&gt;&lt;/django_project_root&gt;&lt;/pre&gt;
&lt;p&gt;Note that I’m using pipenv, Docker, and docker-compose as well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="moving-files-breaks-things"&gt;
&lt;h2&gt;Moving files breaks things&lt;/h2&gt;
&lt;p&gt;Now it’s easy enough to move files around, but if you move things
around, import statements break.&lt;/p&gt;
&lt;div class="section" id="where-to-put-docker-compose-yml"&gt;
&lt;h3&gt;Where to put docker-compose.yml&lt;/h3&gt;
&lt;p&gt;I decided to place the &lt;tt class="docutils literal"&gt;Dockerfile&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yml&lt;/span&gt;&lt;/tt&gt;
alongside the &lt;tt class="docutils literal"&gt;manage.py&lt;/tt&gt; file. This meant I did not need to change
these files from a familiar setup:&lt;/p&gt;
&lt;pre class="code dockerfile literal-block"&gt;
&lt;span class="c"&gt;# Dockerfile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# pull base image&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.8&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# set environment variables&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PYTHONDONTWRITEBYTECODE&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PYTHONUNBUFFERED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# set work directory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/code&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# install dependenceies&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Pipfile&lt;span class="w"&gt; &lt;/span&gt;Pipfile.lock&lt;span class="w"&gt; &lt;/span&gt;/code/&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pipenv&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;pipenv&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--system&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# copy project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;/code/
&lt;/pre&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"3.7"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;python /code/manage.py runserver 0.0.0.0:8000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;.env&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;.:/code&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;8000:8000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;db&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;postgres:12&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_HOST_AUTH_METHOD=trust"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;postgres_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="finding-the-project-app"&gt;
&lt;h3&gt;Finding the project app&lt;/h3&gt;
&lt;p&gt;The first major problem was that I ran into a ModuleNotFoundError when
starting the development server. The module with the name of my Django
project could not be found.&lt;/p&gt;
&lt;p&gt;I found a nice &lt;a class="reference external" href="https://stackoverflow.com/questions/50364758/django-how-to-tell-django-where-it-should-look-for-apps"&gt;Stack Overflow
question&lt;/a&gt;
addressing this. &lt;tt class="docutils literal"&gt;manage.py&lt;/tt&gt; needs to know where to look for the apps.
Since we moved things around, we’ll have to tell the system where to
find that Django project folder.&lt;/p&gt;
&lt;p&gt;In my base settings file, &lt;tt class="docutils literal"&gt;base.py&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# config/settings/base.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&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="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Project root directory&lt;/span&gt;&lt;span class="w"&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="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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&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;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s1"&gt;'&lt;django_project_root&gt;'&lt;/django_project_root&gt;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# replace&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="finding-root-urlconf"&gt;
&lt;h3&gt;Finding &lt;tt class="docutils literal"&gt;ROOT_URLCONF&lt;/tt&gt;&lt;/h3&gt;
&lt;p&gt;Running the development server again left me with this error:
&lt;tt class="docutils literal"&gt;ModuleNotFoundError: No module named &lt;span class="pre"&gt;'&lt;project_name&gt;.urls'&lt;/project_name&gt;&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;Now is a good time to compare our directories again. With the standard
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-admin&lt;/span&gt; startproject&lt;/tt&gt; command, our &lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt; file is in
this relative path:
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;&lt;django_project_root&gt;/&lt;project_name&gt;/settings.py&lt;/project_name&gt;&lt;/django_project_root&gt;&lt;/span&gt;&lt;/tt&gt;. The standard
&lt;tt class="docutils literal"&gt;ROOT_URLCONF&lt;/tt&gt; value is: &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;'&lt;project_name&gt;.urls'&lt;/project_name&gt;&lt;/span&gt;&lt;/tt&gt;. This works because
the project &lt;tt class="docutils literal"&gt;urls.py&lt;/tt&gt; file is in the same directory as the
&lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt; file.&lt;/p&gt;
&lt;p&gt;With our new structure, we’ve separated these files and put them in a
new folder named &lt;tt class="docutils literal"&gt;config/&lt;/tt&gt;, so we need to update this setting.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
.&lt;span class="w"&gt;
&lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;config/&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;settings/&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;base.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;__init__.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;local.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;production.py&lt;span class="w"&gt;
&lt;/span&gt;│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;urls.py&lt;span class="w"&gt;
&lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;&lt;django_project_root&gt;/
&lt;/django_project_root&gt;&lt;/pre&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# config/settings/base.py&lt;/span&gt;&lt;span class="w"&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="s1"&gt;'config.urls'&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="finding-wsgi-application"&gt;
&lt;h3&gt;Finding &lt;tt class="docutils literal"&gt;WSGI_APPLICATION&lt;/tt&gt;&lt;/h3&gt;
&lt;p&gt;Same goes for finding the WSGI application.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# config/settings/base.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;WSGI_APPLICATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'config.wsgi.application'&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="success"&gt;
&lt;h2&gt;Success!&lt;/h2&gt;
&lt;img alt="Proof that our Django project is running locally" src="./images/django-project-running-locally.png"/&gt;
&lt;p&gt;It seems simple, but it took me an entire morning to figure this out!
Hopefully it speeds up your progress.&lt;/p&gt;
&lt;p&gt;Be sure to check out &lt;a class="reference external" href="https://www.feldroy.com/products/two-scoops-of-django-3-x"&gt;Two Scoops of Django
3.X&lt;/a&gt; for
more best practices in Django development.&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="custom-project-layout"/><category term="docker"/><category term="docker-compose-yml"/><category term="multiple-settings-files"/><category term="path-2"/><category term="pathlib"/><category term="two-scoops-of-django"/></entry><entry><title>Fancy New Windows Terminal</title><link href="https://startcodingnow.com/fancy-new-windows-terminal" rel="alternate"/><published>2020-06-07T00:00:00-07:00</published><updated>2020-06-07T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2020-06-07:/fancy-new-windows-terminal</id><summary type="html">&lt;p&gt;I have a buddy who recommended switching to z shell as my terminal. He
showed me some pictures and it looks nice and pretty, so I started
researching.&lt;/p&gt;
&lt;p&gt;Most results suggested installing Cygwin and then using the Cygwin
terminal to install z shell. I went and did all that, it …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have a buddy who recommended switching to z shell as my terminal. He
showed me some pictures and it looks nice and pretty, so I started
researching.&lt;/p&gt;
&lt;p&gt;Most results suggested installing Cygwin and then using the Cygwin
terminal to install z shell. I went and did all that, it seems cool, but
when I tried that &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;apt-cyg&lt;/span&gt; install zsh&lt;/tt&gt;, I received an error that said
the certificate had expired. It wouldn’t install! Let’s try to find
another solution.&lt;/p&gt;
&lt;div class="section" id="enter-windows-subsystem-for-linux"&gt;
&lt;h2&gt;Enter Windows Subsystem for Linux&lt;/h2&gt;
&lt;p&gt;I’ve been wary about trying this. It seems pretty new still, but the
install directly from the Windows store was SO EASY. I have high hopes.&lt;/p&gt;
&lt;p&gt;Just enable Windows Subsystem for Linux, find your Linux distro and
install. I went with Ubuntu because I’m familiar with it.&lt;/p&gt;
&lt;p&gt;I found this article from howtogeek.com to be really helpful: &lt;a class="reference external" href="https://www.howtogeek.com/258518/how-to-use-zsh-or-another-shell-in-windows-10/"&gt;“How to
Use Zsh (or Another Shell) in Windows
10”.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="install-oh-my-zsh"&gt;
&lt;h2&gt;Install oh-my-zsh!&lt;/h2&gt;
&lt;p&gt;As a total newb, I don’t even know what kind of upgrades I could expect
to make to my shell. &lt;a class="reference external" href="https://www.smashingmagazine.com/2015/07/become-command-line-power-user-oh-my-zsh-z/"&gt;Here’s an article on becoming a command line power
user.&lt;/a&gt;
You’ll walk through the new shell, theme, and adding customization.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="install-powerlevel10k"&gt;
&lt;h2&gt;Install PowerLevel10k&lt;/h2&gt;
&lt;p&gt;Here’s a &lt;a class="reference external" href="https://github.com/romkatv/powerlevel10k"&gt;Z shell theme&lt;/a&gt;
recommended to me. I was on board as soon as I saw the pretty colors,
but I’ll probably stay forever due to the Dragon Ball Z nostalgia.&lt;/p&gt;
&lt;p&gt;Check out the manual there as there’s plenty of things you can do. I
would at least install the fonts they recommend and see how you like
them.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="annoying-windows-alert-sound"&gt;
&lt;h2&gt;Annoying Windows Alert Sound&lt;/h2&gt;
&lt;p&gt;I found my Windows was making that terrible &lt;em&gt;du-da-duuunn&lt;/em&gt; sound every
time I double tabbed in the new shell. I figured I probably don’t want
the terminal to play sounds, so I just opened the “volume mixer” in
Windows and muted the console window.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="wrapping-up-and-next-steps"&gt;
&lt;h2&gt;Wrapping Up and Next Steps&lt;/h2&gt;
&lt;p&gt;Turns out &lt;strong&gt;there’s a lot of customization available.&lt;/strong&gt; Definitely look
into different plugins and try using the ones you think you’ll be using
a lot. And I encourage you to get creative with your
&lt;a class="reference external" href="https://codeburst.io/7-super-useful-aliases-to-make-your-development-life-easier-fef1ee7f9b73"&gt;aliases&lt;/a&gt;
as well.&lt;/p&gt;
&lt;/div&gt;
</content><category term="windows"/><category term="command-prompt"/><category term="shell"/><category term="terminal"/><category term="windows"/><category term="windows-subsystem-for-linux"/><category term="z-shell"/><category term="zsh"/></entry><entry><title>Lack of Volume Persistence in kartoza/postgis Docker Container</title><link href="https://startcodingnow.com/volume-persistence-in-kartoza-postgis-docker-container" rel="alternate"/><published>2020-06-06T00:00:00-07:00</published><updated>2020-06-06T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2020-06-06:/volume-persistence-in-kartoza-postgis-docker-container</id><summary type="html">&lt;p class="first last"&gt;Let's find out where that database is located!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I have been working on a Django project that needs maps integration. I
looked into using GeoDjango, but needed to upgrade my current Postgresql
database to add PostGIS.&lt;/p&gt;
&lt;p&gt;This was &lt;strong&gt;totally&lt;/strong&gt; outside what I’ve done before. Here’s how I got it
to work.&lt;/p&gt;
&lt;div class="section" id="starting-point"&gt;
&lt;h2&gt;Starting Point&lt;/h2&gt;
&lt;p&gt;My knowledge of Docker comes entirely from Will Vincent’s &lt;a class="reference external" href="https://djangoforprofessionals.com/"&gt;Django for
Professionals&lt;/a&gt; book. In there,
he builds from &lt;a class="reference external" href="https://hub.docker.com/_/postgres"&gt;the official postgres Docker
image&lt;/a&gt; within a
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yml&lt;/span&gt;&lt;/tt&gt; file.&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"3.7"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;python /code/manage.py runserver 0.0.0.0:8000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;.:/code&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;8000:8000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;db&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;postgres:11&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;postgres_data:/var/lib/postgresql/data/&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_HOST_AUTH_METHOD=trust"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;postgres_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="learning-to-use-geodjango"&gt;
&lt;h2&gt;Learning to Use GeoDjango&lt;/h2&gt;
&lt;p&gt;Luckily, I found a great tutorial over at Real Python to introduce
myself to GeoDjango: &lt;a class="reference external" href="https://realpython.com/location-based-app-with-geodjango-tutorial/"&gt;“Make a Location-Based Web App With Django and
GeoDjango” by Ahmed
Bouchefra.&lt;/a&gt;
If you’re totally new to GeoDjango, I highly recommend this article.&lt;/p&gt;
&lt;p&gt;Ahmed even uses Docker in the article, so super win if you’re coming
from Vincent’s book like me. &lt;a class="reference external" href="https://postgis.net/"&gt;PostGIS&lt;/a&gt; is an
extension of PostgreSQL, so it’s kind of like a standard postgres
database you might use for your Django project, but adds support for
geographic objects.&lt;/p&gt;
&lt;p&gt;In the article, we use a new PostgreSQL image to build our database:
&lt;a class="reference external" href="https://hub.docker.com/r/kartoza/postgis/"&gt;the kartoza/postgis
image.&lt;/a&gt; If you check out
the &lt;a class="reference external" href="https://hub.docker.com/r/kartoza/postgis/tags"&gt;tags&lt;/a&gt;, you’ll see
some have TWO sets of numbers. These are versions for PostgreSQL and
PostGIS. Ex: kartoza/postgis:11.5-2.5 uses PostgreSQL 11.5 and PostGIS
2.5.&lt;/p&gt;
&lt;p&gt;We spin up this database with the following command in the terminal:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="o"&gt;=&lt;/span&gt;postgis&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;user001&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;123456789&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_DBNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gis&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;:5432&lt;span class="w"&gt; &lt;/span&gt;kartoza/postgis:9.6-2.4
&lt;/pre&gt;
&lt;p&gt;What’s all this mean?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;docker run&lt;/tt&gt; means let’s run a Docker command&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--name&lt;/span&gt;&lt;/tt&gt; is the container’s name&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-d&lt;/span&gt;&lt;/tt&gt; means detach this container from the terminal so I don’t have
to open another one&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-e&lt;/span&gt;&lt;/tt&gt; are environment variables&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-p&lt;/span&gt;&lt;/tt&gt; publishes a container’s port to the host&lt;/li&gt;
&lt;li&gt;and the last &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;kartoza/postgis:9.6-2.4&lt;/span&gt;&lt;/tt&gt; is the name of the image
from Docker Hub you want to use to build the container&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once that’s running, the rest of the stuff eventually works and it’s
great! All is well in the world.&lt;/p&gt;
&lt;p&gt;The tutorial, however, doesn’t show me how to use it with my
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yml&lt;/span&gt;&lt;/tt&gt; file. So let’s dive into that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="using-docker-compose-yml"&gt;
&lt;h2&gt;Using docker-compose.yml&lt;/h2&gt;
&lt;p&gt;To get this part going, I started with the PostgreSQL image we made in
&lt;em&gt;Django for Professionals&lt;/em&gt;. The file we had before becomes…&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"3.7"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;python /code/manage.py runserver 0.0.0.0:8000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;.:/code&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;8000:8000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;db&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;# NOTE the new image&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;kartoza/postgis:11.5-2.5&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;postgres_data:/var/lib/postgresql/data/&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_HOST_AUTH_METHOD=trust"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;# NOTE the new environment variables&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;POSTGRES_USER=user001&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;POSTGRES_PASS=123456789&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;POSTGRES_DBNAME=gis&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;postgres_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Bring it all up with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose&lt;/span&gt; up &lt;span class="pre"&gt;-d&lt;/span&gt; &lt;span class="pre"&gt;--build&lt;/span&gt;&lt;/tt&gt; and things should
work! Everything appears to be fine until you bring the containers down
and back up again.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose&lt;/span&gt; down&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose&lt;/span&gt; up &lt;span class="pre"&gt;-d&lt;/span&gt; &lt;span class="pre"&gt;--build&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Could not connect to host in Firefox" src="./images/ubuntu-desktop-firefox-could-not-connect-to-localhost-with-docker-and-django.png"/&gt;
&lt;p class="caption"&gt;Could not connect to host in Firefox&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Our docker logs show us an error message Django is trying to tell us:&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;django.db.utils.OperationalError: could not connect to server: Connection refused&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;Is the server running on host "db" (192.168.96.2) and accepting TCP/IP connections on port 5432?&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;I had previously enabled &lt;tt class="docutils literal"&gt;python manage.py runsslserver&lt;/tt&gt; so that I
could have an SSL connection to work with some public APIs. When I ran
the Django server with &lt;tt class="docutils literal"&gt;runsslserver&lt;/tt&gt;, I get the beautiful Django
DEBUG screen:&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Programming error at /: django.site does not exist" src="./images/django-programming-error-django-site-does-not-exist.png"/&gt;
&lt;p class="caption"&gt;Programming error at /: django.site does not exist&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Note that the exception is in the “django/db/backends/utils.py” file.
It’s a database problem. The database is gone! We just need to migrate
it.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;python manage.py migrate&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose&lt;/span&gt; exec web python manage.py migrate&lt;/tt&gt; (for our
docker-compose Django service named “web”.&lt;/p&gt;
&lt;p&gt;The terminal shows a ton of green “OK” notifications as it applies your
database migrations. Check the site now and the error should be gone.&lt;/p&gt;
&lt;p&gt;But now the main point of the article. Let’s bring the containers down
and then up again.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose&lt;/span&gt; down&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose&lt;/span&gt; up &lt;span class="pre"&gt;-d&lt;/span&gt; &lt;span class="pre"&gt;--build&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;And that error is still there!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="retain-the-local-kartoza-postgis-database"&gt;
&lt;h2&gt;Retain the Local kartoza/postgis Database&lt;/h2&gt;
&lt;p&gt;I started digging into why the database wouldn’t stick around. It worked
when I used the official postgres image, but not with this new one.&lt;/p&gt;
&lt;p&gt;If you look at the Docker Hub page for the postgres image, they say:&lt;/p&gt;
&lt;blockquote&gt;
The default [database file location] is &lt;tt class="docutils literal"&gt;/var/lib/postgresql/data&lt;/tt&gt;.&lt;/blockquote&gt;
&lt;p&gt;That’s already where we store it! But what does the kartoza/postgis
image say?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Docker volumes can be used to persist your data.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;tt class="docutils literal"&gt;mkdir &lt;span class="pre"&gt;-p&lt;/span&gt; ~/postgres_data&lt;/tt&gt;&lt;/div&gt;
&lt;div class="line"&gt;&lt;tt class="docutils literal"&gt;docker run &lt;span class="pre"&gt;-d&lt;/span&gt; &lt;span class="pre"&gt;-v&lt;/span&gt; &lt;span class="pre"&gt;$HOME/postgres_data:/var/lib/postgresql&lt;/span&gt; kartoza/postgis&lt;/tt&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ahhhhh, different location! Let’s try that out. Updated
docker-compose.yml file:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"3.7"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;python /code/manage.py runserver 0.0.0.0:8000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;.:/code&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;8000:8000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;db&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;kartoza/postgis:11.5-2.5&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;# NOTE the new storage location&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;postgres_data:/var/lib/postgresql&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_HOST_AUTH_METHOD=trust"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;# NOTE the new environment variables&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;# NOTE use your correct credentials&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;POSTGRES_USER=user001&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;POSTGRES_PASS=123456789&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;POSTGRES_DBNAME=gis&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;postgres_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Migrate the database, bring the containers down, then back up.&lt;/p&gt;
&lt;p&gt;If you’re lucky, it worked!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This whole scenario is a great example of how touchy coding can be. You
need to specify the right folder. If you can’t quite appreciate that,
start tinkering around with Linux and file permissions. HUGE PAIN. But
valuable experience, too.&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="django"/><category term="docker"/><category term="docker-compose-yml"/><category term="postgis"/><category term="postgresql"/></entry><entry><title>Oops! Removing Secrets from Django Project in Docker</title><link href="https://startcodingnow.com/removing-secrets-from-django-project-in-docker" rel="alternate"/><published>2020-04-22T00:00:00-07:00</published><updated>2020-04-22T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2020-04-22:/removing-secrets-from-django-project-in-docker</id><summary type="html">&lt;p&gt;So you’ve been frantic as you try to fix a bug. You get lost in the work
and totally forget where you are. All of a sudden… SUCCESS! It works!&lt;/p&gt;
&lt;p&gt;Amazing.&lt;/p&gt;
&lt;p&gt;But oh no, you just put your secrets into a tracked file. What to do,
what to do …&lt;/p&gt;</summary><content type="html">&lt;p&gt;So you’ve been frantic as you try to fix a bug. You get lost in the work
and totally forget where you are. All of a sudden… SUCCESS! It works!&lt;/p&gt;
&lt;p&gt;Amazing.&lt;/p&gt;
&lt;p&gt;But oh no, you just put your secrets into a tracked file. What to do,
what to do…&lt;/p&gt;
&lt;div class="section" id="project-overview"&gt;
&lt;h2&gt;Project Overview&lt;/h2&gt;
&lt;p&gt;So I’ve been saying “you”, but I really mean “me”. Here’s a short
summary of my project, then we’ll get into how I fixed it.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Web app coded in Python&lt;/li&gt;
&lt;li&gt;Using the Django web framework&lt;/li&gt;
&lt;li&gt;Using Docker with a Dockerfile and docker-compose.yml for
containerization&lt;/li&gt;
&lt;li&gt;Deployed to Heroku&lt;/li&gt;
&lt;li&gt;Had a problem serving static files on Heroku and not locally, so I
implemented an Amazon S3 bucket for remote file storage, which
requires secrets!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Up until this point, I’ve tracked my &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yml&lt;/span&gt;&lt;/tt&gt; file so that
if I want to work on this project on another computer, it will be easy
to clone and get going. But I’m hosting this code on GitHub and may
share it some day, so I don’t want secrets available.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="fixing-djangos-secret-key"&gt;
&lt;h2&gt;Fixing Django’s &lt;tt class="docutils literal"&gt;SECRET_KEY&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;My &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yml&lt;/span&gt;&lt;/tt&gt; file has my Django &lt;tt class="docutils literal"&gt;SECRET_KEY&lt;/tt&gt; inside it.
When prepping to deploy to Heroku, all I did was generate a new key and
place that in my Heroku app as a config var.&lt;/p&gt;
&lt;img alt="heroku reveal config vars" src="./images/heroku-reveal-config-vars.png"/&gt;
&lt;p&gt;This means that the public version of my site has a hidden
&lt;tt class="docutils literal"&gt;SECRET_KEY&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Beautiful.&lt;/p&gt;
&lt;p&gt;But I didn’t want to try to generate a whole new set of credentials for
my AWS S3 bucket because this is really my first time using it. I have
been debugging this static files problem for over two weeks now and just
want it to work. So instead of messing around with more credentials,
let’s find another solution.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="serving-djangos-static-files-from-aws-s3-bucket"&gt;
&lt;h2&gt;Serving Django’s Static Files from AWS S3 Bucket&lt;/h2&gt;
&lt;p&gt;To solve my static files problems, I went looking for a mentor. Matthew
Freire of JustDjango (&lt;a class="reference external" href="https://justdjango.com/"&gt;site&lt;/a&gt;,
&lt;a class="reference external" href="https://www.youtube.com/channel/UCRM1gWNTDx0SHIqUJygD-kQ"&gt;YouTube&lt;/a&gt;)
suggested that Heroku won’t let me serve my files from the same server
as my Django site and that I should use an S3 bucket.&lt;/p&gt;
&lt;p&gt;I found &lt;a class="reference external" href="https://testdriven.io/blog/storing-django-static-and-media-files-on-amazon-s3/"&gt;a tutorial from
testdriven.io&lt;/a&gt;
that was fairly recent and uses Docker, and that was extremely helpful.&lt;/p&gt;
&lt;p&gt;I needed to commit and push my changes to Heroku to see if this change
would work. When it did, then I realized I needed to remove those keys.
Oops!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="fixing-aws-keys"&gt;
&lt;h2&gt;Fixing AWS Keys&lt;/h2&gt;
&lt;p&gt;After realizing my keys were in my &lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt; file, I set up
environment variables and put the keys in my &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yml&lt;/span&gt;&lt;/tt&gt;
file, which is where I’ve stored all my environment variables.&lt;/p&gt;
&lt;p&gt;But then I realized I’m tracking docker-compose.yml, too! So I went
looking for a new solution.&lt;/p&gt;
&lt;p&gt;I found &lt;cite&gt;a concise article from David Walsh on using
``docker-compose.override.yml`&lt;/cite&gt;. &amp;lt;&lt;a class="reference external" href="https://davidwalsh.name/docker-compose-override"&gt;https://davidwalsh.name/docker-compose-override&lt;/a&gt;&amp;gt;`__
When building your Docker image with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yml&lt;/span&gt;&lt;/tt&gt;, it will
take whatever’s in your &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.override.yml&lt;/span&gt;&lt;/tt&gt; file and use it
to replace old things and add new things.&lt;/p&gt;
&lt;p&gt;Don’t forget to add &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.override.yml&lt;/span&gt;&lt;/tt&gt; to &lt;tt class="docutils literal"&gt;.gitignore&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Then check to make sure it works. Mine does!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-changed-in-the-previous-commit"&gt;
&lt;h2&gt;What Changed in the Previous Commit?&lt;/h2&gt;
&lt;p&gt;For my particular scenario, my previous commit was the one that had the
secrets in it. So I want to amend that one.&lt;/p&gt;
&lt;p&gt;But let’s make sure we’re not missing anything.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;git reflog&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;Outputs hashes of previous commits.&lt;/p&gt;
&lt;img alt="git reflog" src="./images/git-reflog.png"/&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;git diff &lt;hash1&gt; &lt;hash2&gt;&lt;/hash2&gt;&lt;/hash1&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;This will walk you through the differences between the two commits. I
used the command &lt;tt class="docutils literal"&gt;git diff a08db22 04d80e7&lt;/tt&gt;, which says, “What changed
from commit a08db22 to 04d80e7?” Keep hitting enter to scroll.&lt;/p&gt;
&lt;p&gt;I first see that my &lt;tt class="docutils literal"&gt;Pipfile&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;Pipfile.lock&lt;/tt&gt; have changed. This
is good because I wanted to install &lt;tt class="docutils literal"&gt;boto3&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-storages&lt;/span&gt;&lt;/tt&gt; to
get my S3 bucket working.&lt;/p&gt;
&lt;img alt="git diff" src="./images/git-diff.png"/&gt;
&lt;p&gt;Next change I see is in &lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt;, where we’ve added
&lt;tt class="docutils literal"&gt;'storages',&lt;/tt&gt; to my &lt;tt class="docutils literal"&gt;INSTALLED_APPS&lt;/tt&gt;. All good here.&lt;/p&gt;
&lt;img alt="git diff added storages app" src="./images/git-diff-added-storages-app.png"/&gt;
&lt;p&gt;Then, further down, we see the problem. My AWS key variables are pasted
in there! Make a note that we need to make sure that we want to amend
that change in &lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt;.&lt;/p&gt;
&lt;img alt="git diff added aws secrets" src="./images/git-diff-added-aws-secrets.png"/&gt;
&lt;p&gt;There are just a few other changes, but nothing that needs to be hidden.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="amend-previous-commit"&gt;
&lt;h2&gt;Amend Previous Commit&lt;/h2&gt;
&lt;p&gt;So I had four files to change:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yml&lt;/span&gt;&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.override.yml&lt;/span&gt;&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;.gitignore&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But since we ignored the new &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.override.yml&lt;/span&gt;&lt;/tt&gt;, then only
three files should show up as changed when we ask git to check.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;git status&lt;/tt&gt;&lt;/p&gt;
&lt;img alt="git status three modified files" src="./images/git-status-three-modified-files.png"/&gt;
&lt;p&gt;Perfect, our three files show up. Now let’s check what is different
since the last commit.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;git diff&lt;/tt&gt;&lt;/p&gt;
&lt;img alt="git diff get aws secrets from environment" src="./images/git-diff-get-aws-secrets-from-environment.png"/&gt;
&lt;p&gt;Nice! Environment variables! Repeatedly hit ENTER to make sure
everything is good.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;git add &lt;span class="pre"&gt;-A&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;Stage these modified files.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;git commit &lt;span class="pre"&gt;--amend&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;Amend the previous commit. A screen will pop up with the message you
gave the commit. Adjust as needed, save, and exit.&lt;/p&gt;
&lt;img alt="git commit amend" src="./images/git-commit-amend.png"/&gt;
&lt;p&gt;Success!&lt;/p&gt;
&lt;p&gt;One last note: maybe push your changes to your remote repository :)&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;git push origin master&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;All set!&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="gitignore"/><category term="aws"/><category term="aws-s3"/><category term="django"/><category term="django-3-0"/><category term="docker-compose-override-yml"/><category term="docker-compose-yml"/><category term="environment-variables"/><category term="heroku"/><category term="python"/><category term="python-3"/><category term="s3"/><category term="secret_key"/><category term="secrets"/></entry><entry><title>10 Gigabit Networking Between Home Server and Computer</title><link href="https://startcodingnow.com/10-gigabit-networking" rel="alternate"/><published>2020-01-13T00:00:00-07:00</published><updated>2020-01-13T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2020-01-13:/10-gigabit-networking</id><summary type="html">&lt;p&gt;In my other life, I film a lot of &lt;a class="reference external" href="https://www.youtube.com/lancegoyke"&gt;videos on
fitness&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After a recent lecture a few friends and I did for charity, I found out…
I’m out of storage space!&lt;/p&gt;
&lt;p&gt;I built a server for my home to not only store the videos I film, but
also …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In my other life, I film a lot of &lt;a class="reference external" href="https://www.youtube.com/lancegoyke"&gt;videos on
fitness&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After a recent lecture a few friends and I did for charity, I found out…
I’m out of storage space!&lt;/p&gt;
&lt;p&gt;I built a server for my home to not only store the videos I film, but
also as a media server to store and watch some of the bluray and DVD
content I’ve purchased over the years.&lt;/p&gt;
&lt;p&gt;Problem is I have about 5TB of my own storage. And I have a few
computers lying around that can help with the job of transcoding my
ripped bluray and DVD media.&lt;/p&gt;
&lt;p&gt;But if I do that on multiple devices, I’ll still have to transfer them
back to the server.&lt;/p&gt;
&lt;p&gt;So I decided to set up 10 gigabit ethernet between my main computer and
my server.&lt;/p&gt;
&lt;div class="topic"&gt;
&lt;p class="topic-title"&gt;&lt;strong&gt;Bits vs Bytes&lt;/strong&gt;
Networking often describes speed in bits.
In our daily computing, we usually use bytes.
One byte is comprised of eight bits.&lt;/p&gt;
&lt;p&gt;Ex 1) 1 gigabit networking means 125 megabytes per second
Ex 2) 10 gigabit networking means 1.25 gigabytes per second&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="inventory-what-youll-need-to-get-this-going"&gt;
&lt;h2&gt;Inventory - what you’ll need to get this going&lt;/h2&gt;
&lt;p&gt;I will outline what I bought exactly at the end of this section, but
here’s an overview so you can pick and choose what you need.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;At least two computers&lt;/li&gt;
&lt;li&gt;Two networking interfaces&lt;/li&gt;
&lt;li&gt;A 10 gigabit switch&lt;/li&gt;
&lt;li&gt;The appropriate cables (one for each computer)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="network-card"&gt;
&lt;h3&gt;Network card&lt;/h3&gt;
&lt;p&gt;My server is in a huge tower (Fractal Design Define XL R2) with a huge
motherboard (Supermicro X9DRH-7F). There is a variant of this
motherboard with onboard 10 gigabit ethernet, but the cost was double at
the time of purchase, so I opted for this cheaper one.&lt;/p&gt;
&lt;p&gt;To get 10 gigabit networking, I will need a 10 gigabit network adapter.
I can use one of my six PCIe 3.0 x8 slots, with a &lt;a class="reference external" href="http://www.pearsonitcertification.com/articles/article.aspx?p=2731934&amp;amp;seqNum=24"&gt;max transfer rate of
about
8GBps&lt;/a&gt;,
is plenty fast enough to take a 10 gigabit network adapter card. There
could even be two or four 10GbE interfaces on that card and it would
still work, since 10 Gb = 1.25 GB and 40 Gb = 5 GB.&lt;/p&gt;
&lt;p&gt;Now there are two major types of connectors used for 10 gigabit
ethernet: the standard-looking RJ45, and the intimidating SFP+.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="connector-type"&gt;
&lt;h3&gt;Connector type&lt;/h3&gt;
&lt;p&gt;There are four major methods for home use&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Direct Attach Copper (DAC) cable – cheap but only works for short
distances&lt;/li&gt;
&lt;li&gt;LC connector for multi-mode fiber optic cable – better for longer
distances across the home&lt;/li&gt;
&lt;li&gt;LC connector for single-mode fiber optics – good for REALLY long
distances, but doesn’t seem to make sense for home use&lt;/li&gt;
&lt;li&gt;RJ45 – only really makes sense if you already have at least Cat 6a
ethernet cables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I found &lt;a class="reference external" href="https://youtu.be/MDiiHN0MPdA"&gt;this video from Craft Computing on
YouTube&lt;/a&gt; very helpful here. More on
this when we talk about the 10 gigabit switch in a few sections.&lt;/p&gt;
&lt;p&gt;My server is sitting right underneath my work desk, so I didn’t need
long cables. I was going to pick an RJ45 connector until I discovered…&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="thunderbolt-3-to-10-gigabit-adapter"&gt;
&lt;h3&gt;Thunderbolt 3 to 10 Gigabit Adapter&lt;/h3&gt;
&lt;p&gt;It would have been easy to just buy two of those network cards above and
call it day, but my main computer is actually a Lenovo X1 Extreme Gen 1
laptop. I can’t just plug in a traditional PCIe network card.&lt;/p&gt;
&lt;p&gt;This laptop is very capable (read: expensive) and comes with Thunderbolt
3, which is EVEN FASTER than 10 GbE, running 40 Gbps through the proper
cables. But I need an adapter to get the 10 GbE into the TB 3 port on my
laptop.&lt;/p&gt;
&lt;p&gt;QNAP makes just such an adapter – one for SFP+ connections, and one for
RJ45 connections. The SFP+ adapter was cheaper, so I chose that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="gigabit-switch"&gt;
&lt;h3&gt;10 Gigabit Switch&lt;/h3&gt;
&lt;p&gt;I had intended to just plug the two computers together and call it a
day, but I decided a much more elegant solution would be to plug each
machine into a switch and let the switch facilitate the connections.&lt;/p&gt;
&lt;p&gt;The aforementioned YouTube video from Craft Computing detailed a super
affordable product: the Mikrotik CRS305-1G-4S+IN. It comes with a power
supply, is nearly silent when running, and has one gigabit port to hook
up to the main router (if desired) and four 10 gigabit ports to use as a
switch.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="summary-purchase-list"&gt;
&lt;h3&gt;Summary Purchase List&lt;/h3&gt;
&lt;p&gt;This is assuming your have a laptop with Thunderbolt 3 and a tower
server with open PCIe lanes.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://amzn.to/2R0ZJNP"&gt;Mikrotik CRS305-1G-4S+IN&lt;/a&gt; - 10 gigabit
switch&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.ebay.com/itm/MNPA19-XTR-10GB-MELLANOX-CONNECTX-2-PCI-E-10G-SFP-NETWORK-CARD-W-BOTH-BRACKET/131634470127?ssPageName=STRK%3AMEBIDX%3AIT&amp;amp;_trksid=p2057872.m2749.l2649"&gt;Mellanox
MNPA19-XTR&lt;/a&gt;
- 10 gigabit SFP+ network card for server
(&lt;a class="reference external" href="https://www.ebay.com/itm/Mellanox-MNPH29D-XTR-ConnectX-Dual-Port-10Gigabit-Network-PCI-E-Card-w-2x-SFP/402027133923?epid=16021419048&amp;amp;hash=item5d9aaf3be3:g:jLcAAOSwJ2pbrm72"&gt;2-port&lt;/a&gt;
and &lt;a class="reference external" href="https://amzn.to/2T8Kc0Z"&gt;4-port&lt;/a&gt; variants)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://amzn.to/2N8HYew"&gt;QNAP QNA-T310G1S&lt;/a&gt; - 10 gigabit to
Thunderbolt 3 network adapter for laptop&lt;/li&gt;
&lt;li&gt;(2x) &lt;a class="reference external" href="https://amzn.to/2R3w7zk"&gt;DAC cable&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-it-working-not-quite-plug-and-play"&gt;
&lt;h2&gt;Getting It Working - NOT quite plug and play&lt;/h2&gt;
&lt;p&gt;One thing I need to make perfectly clear: I am not a networking guru. I
just like to tinker around and learn new stuff. So when that Craft
Computing video said this solution was “plug and play”, it left me a
little confused when I plugged everything in and it wasn’t working.&lt;/p&gt;
&lt;p&gt;I just needed to realize how my computers were connecting.&lt;/p&gt;
&lt;div class="section" id="samba-shares"&gt;
&lt;h3&gt;Samba Shares&lt;/h3&gt;
&lt;p&gt;When I first got my server running, I set up a Samba SMB share to my
main storage folder so that I had a new place to move my old files.&lt;/p&gt;
&lt;p&gt;That worked pretty quickly. I was happy with that.&lt;/p&gt;
&lt;p&gt;So I plugged in all my 10 gigabit hardware and tried transferring files…&lt;/p&gt;
&lt;p&gt;I was able to transfer files, but only at a dismal 40-50 MBps. That was
not even saturating the write speed on my HDD, which I expected to be at
MINIMUM 80 MBps.&lt;/p&gt;
&lt;img alt="dismal 40-50 MBps transfer rate" src="./images/47MBps-transfer-rate.png"/&gt;
&lt;p&gt;The problem was that after setting up my 10 gigabit connections, I kept
using that plain ol’ 1 gigabit connection I had already been using.
Notice what is highlighted on the left side of the following two
screenshots.&lt;/p&gt;
&lt;a class="reference external image-reference" href="./images/FLEXION-server-selected-in-Windows-Explorer.png"&gt;
&lt;img alt="FLEXION server selected in Windows Explorer sidebar" class="image-process-bottom-left-thumbnail" sizes="(min-width: 557px) 525px, (min-width: 375px) 343px, 343px" src="./images/derivatives/bottom-left-thumbnail/525w/FLEXION-server-selected-in-Windows-Explorer.png" srcset="./images/derivatives/bottom-left-thumbnail/343w/FLEXION-server-selected-in-Windows-Explorer.png 343w, ./images/derivatives/bottom-left-thumbnail/525w/FLEXION-server-selected-in-Windows-Explorer.png 525w"/&gt;
&lt;/a&gt;
&lt;a class="reference external image-reference" href="./images/IP-address-network-drive-selected-in-Windows-Explorer.png"&gt;
&lt;img alt="192.168.88.51 server selected in Windows Explorer sidebar" class="image-process-bottom-left-thumbnail" sizes="(min-width: 557px) 525px, (min-width: 375px) 343px, 343px" src="./images/derivatives/bottom-left-thumbnail/525w/IP-address-network-drive-selected-in-Windows-Explorer.png" srcset="./images/derivatives/bottom-left-thumbnail/343w/IP-address-network-drive-selected-in-Windows-Explorer.png 343w, ./images/derivatives/bottom-left-thumbnail/525w/IP-address-network-drive-selected-in-Windows-Explorer.png 525w"/&gt;
&lt;/a&gt;
&lt;p&gt;Both of those network locations appear the same. When I click one of
them in the sidebar on my Windows 10 laptop, it shows the same root
folders. Navigating through shows the same folders as well.&lt;/p&gt;
&lt;p&gt;The difference is that when connecting, I typed in a different IP
address.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="setting-up-windows-network-share"&gt;
&lt;h2&gt;Setting Up Windows Network Share&lt;/h2&gt;
&lt;p&gt;First, we need to find addresses. The Mikrotik CRS305-1G-4S+IN comes
configured with an IP of 192.168.88.1. I decided to move my Mellanox 10
GbE server card and my QNAP QNA-T310G1S 10 GbE laptop adapter to the
192.168.88.xxx subnet.&lt;/p&gt;
&lt;div class="section" id="configure-ip-on-windows-adapter"&gt;
&lt;h3&gt;Configure IP on Windows Adapter&lt;/h3&gt;
&lt;p&gt;Right click your networking icon in your Windows task tray and select
“Open Network &amp;amp; Internet settings”.&lt;/p&gt;
&lt;img alt="Open Network &amp;amp; Internet settings" src="./images/configure-ip-on-windows-adapter-1.png"/&gt;
&lt;p&gt;Select “Change Adapter Options”.&lt;/p&gt;
&lt;a class="reference external image-reference" href="./images/configure-ip-on-windows-adapter-2.png"&gt;
&lt;img alt="Change Adapter Options" class="image-process-bottom-left-thumbnail" sizes="(min-width: 557px) 525px, (min-width: 375px) 343px, 343px" src="./images/derivatives/bottom-left-thumbnail/525w/configure-ip-on-windows-adapter-2.png" srcset="./images/derivatives/bottom-left-thumbnail/343w/configure-ip-on-windows-adapter-2.png 343w, ./images/derivatives/bottom-left-thumbnail/525w/configure-ip-on-windows-adapter-2.png 525w"/&gt;
&lt;/a&gt;
&lt;p&gt;Right-click the 10 gigabit network adapter and select “Properties”.&lt;/p&gt;
&lt;a class="reference external image-reference" href="./images/configure-ip-on-windows-adapter-3.png"&gt;
&lt;img alt="Right-click the 10 gigabit network adapter and select “Properties”" class="image-process-top-left-thumbnail" sizes="(min-width: 557px) 525px, (min-width: 375px) 343px, 343px" src="./images/derivatives/top-left-thumbnail/525w/configure-ip-on-windows-adapter-3.png" srcset="./images/derivatives/top-left-thumbnail/343w/configure-ip-on-windows-adapter-3.png 343w, ./images/derivatives/top-left-thumbnail/525w/configure-ip-on-windows-adapter-3.png 525w"/&gt;
&lt;/a&gt;
&lt;p&gt;Make sure “Internet Protocol Version 4 (TCP/IPv4)” is enabled and double
click it.&lt;/p&gt;
&lt;img alt="Checkbox is selected next to “Internet Protocol Version 4”" src="./images/configure-ip-on-windows-adapter-4.png"/&gt;
&lt;p&gt;Configure it manually to use the same sub-network as your switch. If you
bought the Mikrotik one and didn’t change it, the subnet is
192.168.88.xxx. You just have to fill in the last number to give this
network adapter it’s own location.&lt;/p&gt;
&lt;p&gt;Since I’m not connecting to the internet, I just left the default
gateway and DNS server addresses blank because I don’t need them. My
Internet traffic is routed through my Gigabit ethernet or my WiFI
adapter.&lt;/p&gt;
&lt;img alt="Manually configuring IP address" src="./images/configure-ip-on-windows-adapter-5.png"/&gt;
&lt;p&gt;Accept the changes and move to your server.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configure-ip-on-ubuntu"&gt;
&lt;h3&gt;Configure IP on Ubuntu&lt;/h3&gt;
&lt;p&gt;Go to network settings.&lt;/p&gt;
&lt;a class="reference external image-reference" href="./images/settings-for-linux-10gbe-card-1.png"&gt;
&lt;img alt="Click the arrow in the top right of the Ubuntu desktop, click on the network card and select “Wired Settings”" class="image-process-top-right-thumbnail" sizes="(min-width: 557px) 525px, (min-width: 375px) 343px, 343px" src="./images/derivatives/top-right-thumbnail/525w/settings-for-linux-10gbe-card-1.png" srcset="./images/derivatives/top-right-thumbnail/343w/settings-for-linux-10gbe-card-1.png 343w, ./images/derivatives/top-right-thumbnail/525w/settings-for-linux-10gbe-card-1.png 525w"/&gt;
&lt;/a&gt;
&lt;p&gt;Click the settings gear icon for your 10 gigabit network card.&lt;/p&gt;
&lt;img alt="Network settings" src="./images/network-settings.png"/&gt;
&lt;p&gt;In IPv4, add an IP address on the same sub-network you selected earlier.
If you went with the default for the Mikrotik switch, then the first
three numbers have to be 192.168.88.&lt;/p&gt;
&lt;img alt="Configure the correct subnet" src="./images/setting-ipv4.png"/&gt;
&lt;/div&gt;
&lt;div class="section" id="set-up-a-samba-share-on-linux"&gt;
&lt;h3&gt;Set up a Samba Share on Linux&lt;/h3&gt;
&lt;p&gt;Now we need to have a folder that we want to share. In my case, I have
four 10 TB HDDs that I have mirrored and pooled together. So two serve
as a RAID 1 redundant mirror for the other two, which are pooled
together to be displayed on my file browser as one folder. It’s pretty
neat and ensures I’ll always have my stuff even if a drive fails.&lt;/p&gt;
&lt;p&gt;I have mounted this pooled virtual drive in my /mnt folder. The easiest
and most visual way to share this folder on my local network is to
navigate to it in my Ubuntu Desktop file browser. The default file
browser is “Nautilus”.&lt;/p&gt;
&lt;img alt="See Samba folder in Linux desktop file browser" src="./images/selecting-samba-folder.png"/&gt;
&lt;p&gt;Right-click the folder and select “Local Network Share”.&lt;/p&gt;
&lt;a class="reference external image-reference" href="./images/select-local-network-share.png"&gt;
&lt;img alt="Samba folder right clicked and highlighting “Local Network Share”" class="image-process-bottom-right-thumbnail" sizes="(min-width: 557px) 525px, (min-width: 375px) 343px, 343px" src="./images/derivatives/bottom-right-thumbnail/525w/select-local-network-share.png" srcset="./images/derivatives/bottom-right-thumbnail/343w/select-local-network-share.png 343w, ./images/derivatives/bottom-right-thumbnail/525w/select-local-network-share.png 525w"/&gt;
&lt;/a&gt;
&lt;p&gt;Configure your sharing settings. I kept the name of the folder and
allowed more access to it so that people on my local network dont need
my help getting logged into the folder.&lt;/p&gt;
&lt;img alt="Folder sharing dialog with name of “Intel 660P”" src="./images/create-intel660p-share.png"/&gt;
&lt;p&gt;Then hit “Create Share”.&lt;/p&gt;
&lt;p&gt;If you run into an error that says
&lt;tt class="docutils literal"&gt;Nautilus needs to add some permissions to your folder&lt;/tt&gt; in order to
share it, try to add them automatically (if that is indeed what you want
to do).&lt;/p&gt;
&lt;p&gt;If you’re hit with another error that says
&lt;tt class="docutils literal"&gt;Could not change the permissions of folder&lt;/tt&gt;, then we need superuser
privileges to get things straightened out.&lt;/p&gt;
&lt;p&gt;To confirm this is actually the problem, right-click on your desired
share folder and select “Properties” then “Permissions”. Here’s a
screenshot from another folder of mine that I want to share.&lt;/p&gt;
&lt;img alt="Intel660P permissions dialog box set to root" src="./images/root-permissions.png"/&gt;
&lt;p&gt;This is showing us that the folder is owned by the user “root”. Since I
am logged in as “lance”, I cannot change root’s folder. We’ll need to go
to the terminal and tell it “I am root”.&lt;/p&gt;
&lt;p&gt;Hit Ctrl + Alt + T to open up the Linux Terminal.&lt;/p&gt;
&lt;img alt="Empty Linux terminal" src="./images/terminal.png"/&gt;
&lt;p&gt;We need superuser privileges to change the permissions on this folder.
And we need to know the path of the folder. Mine is /mnt/Intel660P.
First we’ll change the owner of the directory and all of its contents.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;--recursive&lt;span class="w"&gt; &lt;/span&gt;username:username&lt;span class="w"&gt; &lt;/span&gt;/path/to/folder
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;sudo&lt;/tt&gt; gives us superuser privileges (telling the terminal “I am
root”)&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;chown&lt;/tt&gt; means “change the owner”&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--recursive&lt;/span&gt;&lt;/tt&gt; means “do this on all the sub-directories and contained
files”&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;username:username&lt;/tt&gt; represents the user and the group that owns this
folder. Each user has a group that is the same as their username, and
that’s what I want to set my folder to.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;/path/to/folder&lt;/tt&gt; designates the file or folder we want to change&lt;/p&gt;
&lt;p&gt;Putting it all together, here is what I wrote to change the owner of my
folder&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;--recursive&lt;span class="w"&gt; &lt;/span&gt;lance:lance&lt;span class="w"&gt; &lt;/span&gt;/mnt/Intel660P
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we can check to see if it worked by refreshing our Nautilus file
viewing and checking the Properties &amp;gt; Permissions again.&lt;/p&gt;
&lt;img alt="Intel660P permissions dialog box set to “me”" src="./images/chown-folder-results.png"/&gt;
&lt;p&gt;Nice! Now, I need others to be able to access the folder, so we’ll need
to change the permissions of this folder and the enclosed files.&lt;/p&gt;
&lt;p&gt;Easiest way on Ubuntu desktop is to change the “access” for “group” and
for “others”.&lt;/p&gt;
&lt;img alt="Intel660P permissions dialog box set with “Change Permissions for Enclosed Files” highlighted" src="./images/changing-permissions.png"/&gt;
&lt;p&gt;Then click on “Change Permissions for Enclosed Files…” and change to the
settings you want.&lt;/p&gt;
&lt;img alt="Owner, group, and others permissions for files and folders" src="./images/changing-permissions-for-enclosed-files.png"/&gt;
&lt;p&gt;Check out the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Chmod"&gt;chmod&lt;/a&gt; command if
you want to do it in the terminal.&lt;/p&gt;
&lt;p&gt;You can get a lot fancier with permissions if you set up samba shares.
&lt;a class="reference external" href="https://tutorials.ubuntu.com/tutorial/install-and-configure-samba#0"&gt;Click here for the basics of setting up samba
shares&lt;/a&gt;
and &lt;a class="reference external" href="https://help.ubuntu.com/community/Samba/SambaServerGuide"&gt;click here to get a more advanced samba
configuration.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="create-new-network-share-on-windows"&gt;
&lt;h3&gt;Create New Network Share on Windows&lt;/h3&gt;
&lt;p&gt;NOW! This step was my big problem. I already had a folder on my Linux
server marked as a share. And I already had a link to that share set up
on my Windows computer. This gave my Windows computer access to the
storage on my Linux server.&lt;/p&gt;
&lt;p&gt;The problem was that this link was made BEFORE installing the 10 gigabit
network cards. So it was using plain ol’ gigabit (which is honestly fine
for my home use, but doesn’t seem as cool so I “needed” to upgrade).&lt;/p&gt;
&lt;p&gt;Let’s create a new network share on Windows that now uses this new
connection.&lt;/p&gt;
&lt;p&gt;On your Windows computer, hit WIN + E to open up Windows Explorer.&lt;/p&gt;
&lt;img alt="Open Windows Explorer" src="./images/create-network-share-windows-1.png"/&gt;
&lt;p&gt;Right-click “This PC” in the left sidebar and select “Add a Network
Location”. Note: you can also select “Map network drive…” if you want it
to be stored as a letter.&lt;/p&gt;
&lt;img alt="Right-click dialgo with “Add a Network Location” highlighted" src="./images/create-network-share-windows-2.png"/&gt;
&lt;p&gt;This will open the Add Network Location Wizard.&lt;/p&gt;
&lt;img alt="Add network location wizard" src="./images/create-network-share-windows-3.png"/&gt;
&lt;p&gt;Hit Next to continue.&lt;/p&gt;
&lt;p&gt;Select “Choose a custom network location” and select Next.&lt;/p&gt;
&lt;img alt="Custom network location" src="./images/create-network-share-windows-4.png"/&gt;
&lt;p&gt;Now is time to tell Windows where to look. You need your server’s 10
gigabit IPv4 address for this step. This was the last IP address we set
just a few steps ago. We will write…&lt;/p&gt;
&lt;pre class="literal-block"&gt;
\\IPA.DDR.ESS.XXX\SHARED_FOLDER
&lt;/pre&gt;
&lt;p&gt;Mine looks like this…&lt;/p&gt;
&lt;img alt="Internet or network address: \\192.168.88.51:raw-latex:`\Intel`660P" src="./images/create-network-share-windows-5.png"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="success"&gt;
&lt;h2&gt;Success!&lt;/h2&gt;
&lt;img alt="Transfer file rate of 222MBps" src="./images/create-network-share-windows-6.png"/&gt;
&lt;p&gt;Check out those transfer speeds! This is from a local &lt;a class="reference external" href="https://www.samsung.com/semiconductor/ssd/client-ssd/MZVLB1T0HALR/"&gt;Samsung NVMe
drive&lt;/a&gt;,
through a Direct Attach Copper cord, into a 10 gigabit switch, out
another DAC cord, and writing onto one of two spinning hard disks in my
server.&lt;/p&gt;
&lt;p&gt;As with most technology-based projects, this was TREMENDOUSLY
FRUSTRATING and then TREMENDOUSLY REWARDING. Hope this helps one of you
out there.&lt;/p&gt;
&lt;p&gt;What are you using your 10 gigabit network for? Anybody doing any photo
or video editing through it? Leave a comment below if you want an excuse
for a humble brag.&lt;/p&gt;
&lt;/div&gt;
</content><category term="linux"/><category term="10-gbe"/><category term="10-gigabit-ethernet"/><category term="10-gigabit-networking"/><category term="10-gigabit-switch"/><category term="bits"/><category term="bytes"/><category term="dac-cable"/><category term="direct-attach-copper"/><category term="linux"/><category term="mikrotik"/><category term="nautilus"/><category term="network-share"/><category term="networking"/><category term="samba"/><category term="share"/><category term="switch"/><category term="transfer-speeds"/><category term="ubuntu"/><category term="ubuntu-18-04"/><category term="windows"/></entry><entry><title>Fixing Bad Blocks Finder burnin “ERROR - unable to open device: /dev/sda [Device or resource is busy]”</title><link href="https://startcodingnow.com/fixing-bad-blocks-finder-burnin-error-device-busy" rel="alternate"/><published>2019-12-20T00:00:00-07:00</published><updated>2019-12-20T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2019-12-20:/fixing-bad-blocks-finder-burnin-error-device-busy</id><summary type="html">&lt;p class="first last"&gt;Help me access this device!&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="my-situation"&gt;
&lt;h2&gt;My situation&lt;/h2&gt;
&lt;p&gt;I had four equivalent drives.&lt;/p&gt;
&lt;p&gt;I had 2 x RAID 1 mirrors created with mdadm
(&lt;a class="reference external" href="https://www.digitalocean.com/community/tutorials/how-to-create-raid-arrays-with-mdadm-on-ubuntu-18-04#creating-a-raid-1-array"&gt;ref&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;I had both of these RAID 1 mirrors pooled together using mergerfs
(&lt;a class="reference external" href="https://blog.linuxserver.io/2017/06/24/the-perfect-media-server-2017/#settingupthedrivesusingmergerfs"&gt;ref&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-problem"&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;I tried to run a burnin with bad blocks finder.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;bbf&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;mydevicecaptchacode&gt;&lt;span class="w"&gt; &lt;/span&gt;burnin&lt;span class="w"&gt; &lt;/span&gt;/dev/sda
&lt;/mydevicecaptchacode&gt;&lt;/pre&gt;
&lt;p&gt;I received an error.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
ERROR&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;open&lt;span class="w"&gt; &lt;/span&gt;device:&lt;span class="w"&gt; &lt;/span&gt;/dev/sda&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;resource&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;busy&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="unmounting-the-drives"&gt;
&lt;h2&gt;Unmounting the drives&lt;/h2&gt;
&lt;p&gt;I went in and unmounted my mergerfs pool and both of my mdadm arrays
from their respective spots in my /mnt folder.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;/mnt/storage&lt;span class="w"&gt;
&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;/mnt/mirror1&lt;span class="w"&gt;
&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;/mnt/mirror2
&lt;/pre&gt;
&lt;p&gt;I tried the &lt;tt class="docutils literal"&gt;burnin&lt;/tt&gt; again here and was still receiving the ERROR.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="deactivating-the-mdadm-array"&gt;
&lt;h2&gt;Deactivating the mdadm array&lt;/h2&gt;
&lt;p&gt;I then stopped the mdadm arrays, zeroed their superblocks, commented out
persistent references to the array in &lt;tt class="docutils literal"&gt;/etc/fstab&lt;/tt&gt; file, commented out
the array definition in my &lt;tt class="docutils literal"&gt;/etc/mdadm/mdadm.conf&lt;/tt&gt; file, and updated
initramfs so that the early boot process doesn’t try to find these
arrays.
(&lt;a class="reference external" href="https://www.digitalocean.com/community/tutorials/how-to-create-raid-arrays-with-mdadm-on-ubuntu-18-04#resetting-existing-raid-devices"&gt;ref&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="happily-ever-after"&gt;
&lt;h2&gt;Happily ever after&lt;/h2&gt;
&lt;p&gt;Now my first 10TB drive is doing a burnin… only 28 more hours to go
&lt;strong&gt;?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hopefully this helps someone!&lt;/p&gt;
&lt;p&gt;Special thanks to &lt;em&gt;trapexit&lt;/em&gt; for not only his help figuring out the
issue, but for developing these tools in the first place. Check out his
&lt;a class="reference external" href="http://spawn.link"&gt;website&lt;/a&gt; or his
&lt;a class="reference external" href="https://github.com/trapexit"&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/trapexit/bbf/issues/20"&gt;Here’s a link to the original issue a opened on
Github.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="linux"/><category term="burn-in"/><category term="devices"/><category term="drives"/><category term="fstab"/><category term="mdadm"/><category term="mergerfs"/><category term="raid"/><category term="raid-1"/><category term="trapexit"/></entry><entry><title>Start Learning Web Development</title><link href="https://startcodingnow.com/start-learning-web-development" rel="alternate"/><published>2019-11-05T00:00:00-07:00</published><updated>2019-11-05T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2019-11-05:/start-learning-web-development</id><summary type="html">&lt;p class="first last"&gt;Some thoughts on learning and advancing your web development
skills.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="#tldr"&gt;Skip to tl;dr&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There is a LOT of good information out there. Tutorials, videos, paid
courses, free courses… so much good stuff.&lt;/p&gt;
&lt;p&gt;When I decided I wanted to learn to code, I just started doing whatever
looked interesting to me. It’s easy to fall into the trap of trying to
pick the “optimal” syllabus for your personal learning journey. I wanted
to &lt;strong&gt;start coding now.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I actually started years ago – overnight in an airport in Bergen, Norway
– doing the Django polls tutorial. This is a good one, but my goodness
it was a hard time.&lt;/p&gt;
&lt;p&gt;Yes, we can attribute that partially to pulling an all-nighter. I
definitely get dumber without sleep. But the real problem was that I
just didn’t understand much of anything about web development. Plus, my
Python was &lt;em&gt;meh&lt;/em&gt; at best.&lt;/p&gt;
&lt;p&gt;The Django polls tutorial is good, but probably not where you want to
start.&lt;/p&gt;
&lt;div class="section" id="first-learn-python"&gt;
&lt;h2&gt;First, learn Python&lt;/h2&gt;
&lt;p&gt;I found that whole experience kind of frustrating. I made it through the
whole app, but I didn’t know any programming and didn’t really
understand how this web development framework even works. I didn’t
understand any of the anatomy – the structure – of the code, so it was
all too mysterious.&lt;/p&gt;
&lt;p&gt;And when you don’t know how it works, you can’t learn.&lt;/p&gt;
&lt;p&gt;The best thing I did when learning to code was treating myself like more
of a newbie than I originally felt.&lt;/p&gt;
&lt;p&gt;I bought Zedd Shaw’s &lt;a class="reference external" href="https://learnpythonthehardway.org/python3/"&gt;Learn Python 3 the Hard
Way&lt;/a&gt; and started toiling
away. I think it took me about a week to make it through nearly
everything. I attempted to go slowly, making sure I researched anything
that was unclear. I even filled up a few dozen pages of a notebook,
taking actual handwritten notes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="second-learn-django"&gt;
&lt;h2&gt;Second, learn Django&lt;/h2&gt;
&lt;p&gt;Since I knew coding was not going to be a full-time job for me, I was
wary about learning a JavaScript framework. They seem to change faster
than I learn! I wanted to find something old and boring. Something
nearly set in stone. Something I could rely upon.&lt;/p&gt;
&lt;p&gt;That’s why I picked Django.&lt;/p&gt;
&lt;p&gt;Django is proven. It’s not terribly flashy or hyped up, but it gets the
job done while handling a lot of best practices for you. I like code
that doesn’t change because I didn’t even know how ANY frameworks work,
much less the specifics on Django.&lt;/p&gt;
&lt;p&gt;It’s easy to pick up a random tutorial on someone’s blog or on YouTube,
but when you’re at this early stage, you cannot afford to be random.&lt;/p&gt;
&lt;p&gt;If you jump around from tutorial to tutorial, you miss the basics. You
want to build skills like a pyramid: foundation first, then layer by
layer bring the rest together. What we end up doing in this Age of
(Free) Information is building our skills more like a puzzle. We piece
together sections as we see them, and then eventually try to tie the
whole thing together.&lt;/p&gt;
&lt;p&gt;If you lay the foundation first, you learn the bits that teach you the
most. It gives you a broad view which you can then deepen as you need to
learn new skills.&lt;/p&gt;
&lt;p&gt;The most comprehensive piece I’ve found on learning Django is &lt;a class="reference external" href="https://djangoforbeginners.com/"&gt;Django
for Beginners&lt;/a&gt; by Will Vincent.&lt;/p&gt;
&lt;blockquote&gt;
If you don’t know HTML or CSS, you may want to do any old book on
those, too.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="third-go-further"&gt;
&lt;h2&gt;Third, Go Further&lt;/h2&gt;
&lt;p&gt;Once you’ve laid your Django foundation, you have to start building
depth.&lt;/p&gt;
&lt;p&gt;The recommended next step is either:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Try to build your own thing that fits the structure of a project
you’ve already built&lt;/li&gt;
&lt;li&gt;Go through another book that’s more advanced&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you feel comfortable, you can try to build your own stuff. I did that
because I didn’t have a book in mind. And it worked for a little while,
but I was unsuccessful. I just didn’t have the mental model of web
development ingrained in my brain. You might have more success than me.&lt;/p&gt;
&lt;p&gt;What saved me was a follow-up to Django for Beginners: &lt;a class="reference external" href="https://djangoforprofessionals.com/"&gt;Django for
Professionals&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This book wasn’t published when I was ready for it, but it came out only
a few short weeks later. When I snatched it up, I felt really hopeful
about my future as a coder.&lt;/p&gt;
&lt;p&gt;So hopeful, in fact, that I actually developed an app I could use with
some of my fitness mentoring clients.&lt;/p&gt;
&lt;p&gt;Django for Professionals teaches you Docker right away, gets you a ton
more practice with Django in general, and brings you through the whole
process of deploying an app professionally (i.e. keeping private things
private).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="fourth"&gt;
&lt;h2&gt;Fourth…&lt;/h2&gt;
&lt;p&gt;After that, start building your own project! Now that you have your
foundation set, you can deepen your skills.&lt;/p&gt;
&lt;p&gt;Come up with a project idea that is super duper simple. It’s okay if
someone has already done it before. You can steal their structure and
try to recreate it.&lt;/p&gt;
&lt;p&gt;Write out your plans on a napkin or Post-It note. Start building and
&lt;a class="reference external" href="https://startcodingnow.com/learning-django-past-the-documentation/"&gt;search for
help&lt;/a&gt;
when you need it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="tl-dr"&gt;
&lt;h2&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;There’s no reason to re-create the wheel when we already have steps that
work. Use these affordable resources to level up your programming skills
right away.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Do &lt;a class="reference external" href="https://learnpythonthehardway.org/python3/"&gt;Learn Python 3 the Hard
Way&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Do &lt;a class="reference external" href="https://djangoforbeginners.com/"&gt;Django for Beginners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Do &lt;a class="reference external" href="https://djangoforprofessionals.com/"&gt;Django for Professionals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Do &lt;a class="reference external" href="https://startcodingnow.com/django-projects-ideas-for-beginners/"&gt;your own
project&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Start &lt;strong&gt;now&lt;/strong&gt;. Put in the work and you will be rewarded.&lt;/p&gt;
&lt;p&gt;Have you tried out any of these resources? Are there other ones you
found helpful? Leave a comment below or &lt;a class="reference external" href="https://startcodingnow.com/submit-your-article/"&gt;submit your own version of this
article.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="css"/><category term="django"/><category term="django-for-beginners"/><category term="django-for-professionals"/><category term="html"/><category term="learn-python-3-the-hard-way"/><category term="project-idea"/><category term="project-ideas"/><category term="project-planning"/><category term="will-vincent"/><category term="zedd-shaw"/></entry><entry><title>Django Projects - 99 ideas for beginners</title><link href="https://startcodingnow.com/django-projects-ideas-for-beginners" rel="alternate"/><published>2019-10-01T00:00:00-07:00</published><updated>2019-10-01T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2019-10-01:/django-projects-ideas-for-beginners</id><summary type="html">&lt;p class="first last"&gt;Need to practice your skills and build your portfolio?&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Learning Django, I found it really helpful to go through some beginner
tutorials.&lt;/p&gt;
&lt;p&gt;But eventually, you have to fly on your own. You have to learn how to
plan out a project and how to &lt;a class="reference external" href="https://startcodingnow.com/learning-django-past-the-documentation/"&gt;research what you don’t
know&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We believe in learn by doing. And that there’s no better time to start
than now.&lt;/p&gt;
&lt;p&gt;It doesn’t matter if it already exists. Your not building software to
change the world, you’re building software to learn web development.&lt;/p&gt;
&lt;p&gt;Here are some ideas to get you started.&lt;/p&gt;
&lt;div class="section" id="beginner-django-projects"&gt;
&lt;h2&gt;99 Beginner Django Projects&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Track sleep&lt;/li&gt;
&lt;li&gt;Track customized habit performance&lt;/li&gt;
&lt;li&gt;Track customized habits of users&lt;/li&gt;
&lt;li&gt;Send daily reminders in email&lt;/li&gt;
&lt;li&gt;Send daily reminders in SMS&lt;/li&gt;
&lt;li&gt;Store a list of current projects and log entries&lt;/li&gt;
&lt;li&gt;Freelance time tracking on current projects&lt;/li&gt;
&lt;li&gt;Freelance project tracking with view ability for project owners&lt;/li&gt;
&lt;li&gt;Freelance project tracking with comment ability for project owners&lt;/li&gt;
&lt;li&gt;Send daily inspirational quote to your email&lt;/li&gt;
&lt;li&gt;Send daily inspirational quote in SMS&lt;/li&gt;
&lt;li&gt;Make a blog like WordPress&lt;/li&gt;
&lt;li&gt;Make an e-commerce store like Gumroad&lt;/li&gt;
&lt;li&gt;Make someone else a blog&lt;/li&gt;
&lt;li&gt;Make someone else an e-commerce store&lt;/li&gt;
&lt;li&gt;Collect your thoughts on books&lt;/li&gt;
&lt;li&gt;Make an e-commerce store with Amazon Affiliate links&lt;/li&gt;
&lt;li&gt;Make an e-commerce store that automatically pulls Amazon product
info&lt;/li&gt;
&lt;li&gt;Make a list with reviews of your favorite notebooks&lt;/li&gt;
&lt;li&gt;Make a list with reviews of your favorite kitchen utensils&lt;/li&gt;
&lt;li&gt;Catalog your favorite meal recipes&lt;/li&gt;
&lt;li&gt;Pin a list of DIY projects&lt;/li&gt;
&lt;li&gt;Pin a list of DIY projects that lists needed items from Amazon
product API&lt;/li&gt;
&lt;li&gt;Remake an app from a tutorial you’ve done&lt;/li&gt;
&lt;li&gt;Pin a list of your favorite single origin coffees and put them on a
map&lt;/li&gt;
&lt;li&gt;Pin a list of camping gear you recommend for overnight car camping&lt;/li&gt;
&lt;li&gt;Pin a list of camping gear you recommend for backpacking&lt;/li&gt;
&lt;li&gt;Pin a list of climbing gear you recommend for beginners&lt;/li&gt;
&lt;li&gt;Create a forum like Discourse&lt;/li&gt;
&lt;li&gt;Make your own personal Wiki&lt;/li&gt;
&lt;li&gt;Make a portfolio of projects&lt;/li&gt;
&lt;li&gt;Compress and crop uploaded photos&lt;/li&gt;
&lt;li&gt;Install &lt;a class="reference external" href="https://wagtail.io/"&gt;Wagtail&lt;/a&gt; and see if you can get it
to work&lt;/li&gt;
&lt;li&gt;Test out
&lt;a class="reference external" href="https://django-filter.readthedocs.io/en/master/"&gt;django-filter&lt;/a&gt;
on one of your old projects&lt;/li&gt;
&lt;li&gt;Track your hours of programming&lt;/li&gt;
&lt;li&gt;Track your hours of working out&lt;/li&gt;
&lt;li&gt;Track your medical condition&lt;/li&gt;
&lt;li&gt;Store research about your medical condition&lt;/li&gt;
&lt;li&gt;Log stories from your favorite non-fiction books and write your own
reflections&lt;/li&gt;
&lt;li&gt;Invite others to log their stories&lt;/li&gt;
&lt;li&gt;Log your favorite songs and the memories they make&lt;/li&gt;
&lt;li&gt;Invite others to log their favorite songs&lt;/li&gt;
&lt;li&gt;List out business ideas, why they would work, and why they wouldn’t
work&lt;/li&gt;
&lt;li&gt;List out the building blocks or DIY projects for your home&lt;/li&gt;
&lt;li&gt;Make a Django source code highlighter for your site&lt;/li&gt;
&lt;li&gt;Make a form builder for non-coders&lt;/li&gt;
&lt;li&gt;Make a list of coding interview questions and practice&lt;/li&gt;
&lt;li&gt;Make a list of certification questions and practice&lt;/li&gt;
&lt;li&gt;Generate reports on your progress&lt;/li&gt;
&lt;li&gt;Create timelines and anticipated milestones for entrepreneurial
projects&lt;/li&gt;
&lt;li&gt;Log your workouts&lt;/li&gt;
&lt;li&gt;Track your favorite workouts&lt;/li&gt;
&lt;li&gt;Track your favorite exercises&lt;/li&gt;
&lt;li&gt;Create your own RSS reader&lt;/li&gt;
&lt;li&gt;RSS track your favorite blogs AND YouTube videos&lt;/li&gt;
&lt;li&gt;Email yourself price alerts for a desired online product&lt;/li&gt;
&lt;li&gt;Grab local air quality data daily&lt;/li&gt;
&lt;li&gt;Grab local weather forecast daily&lt;/li&gt;
&lt;li&gt;Grab weather forecast by user inputted location&lt;/li&gt;
&lt;li&gt;Grab air quality by user inputted location&lt;/li&gt;
&lt;li&gt;Grab other surrounding day weather forecast&lt;/li&gt;
&lt;li&gt;Grab other surrounding day air quality forecast&lt;/li&gt;
&lt;li&gt;Grab water contaminant data daily&lt;/li&gt;
&lt;li&gt;Grab water contaminant data for user inputted location&lt;/li&gt;
&lt;li&gt;Auto-generate a workout&lt;/li&gt;
&lt;li&gt;Generate a body part workout based on selected body part&lt;/li&gt;
&lt;li&gt;Generate a body part workout based on selected exercise&lt;/li&gt;
&lt;li&gt;Generate a random conditioning workout&lt;/li&gt;
&lt;li&gt;Generate a random conditioning workout based on selected duration&lt;/li&gt;
&lt;li&gt;Log your runs&lt;/li&gt;
&lt;li&gt;Track average mile speed during runs&lt;/li&gt;
&lt;li&gt;Make a journal online&lt;/li&gt;
&lt;li&gt;Make a simple payment platform to collect freelance payments using
Stripe&lt;/li&gt;
&lt;li&gt;Generate invoices for freelance work&lt;/li&gt;
&lt;li&gt;Generate invoices and allow clients to pay right there&lt;/li&gt;
&lt;li&gt;Track weather in desired vacation destinations&lt;/li&gt;
&lt;li&gt;Track weather in national parks&lt;/li&gt;
&lt;li&gt;Track weather in state parks&lt;/li&gt;
&lt;li&gt;Track weather in coldest areas of the world&lt;/li&gt;
&lt;li&gt;Log podcast episodes and memorable quotes&lt;/li&gt;
&lt;li&gt;Log podcast episodes and show all quotes for a given show’s
collection of episodes&lt;/li&gt;
&lt;li&gt;Log songs you’re trying to learn on the guitar&lt;/li&gt;
&lt;li&gt;Log the hours you practice on the guitar by song&lt;/li&gt;
&lt;li&gt;Pin guitar scale exercises&lt;/li&gt;
&lt;li&gt;Pin music theory notes&lt;/li&gt;
&lt;li&gt;Pin your favorite guitars&lt;/li&gt;
&lt;li&gt;Implement guitar search by color, brand, price, and style&lt;/li&gt;
&lt;li&gt;Track desired travel destinations&lt;/li&gt;
&lt;li&gt;Share travel destinations with viewers&lt;/li&gt;
&lt;li&gt;Share travel destinations with travel buddies or significant other&lt;/li&gt;
&lt;li&gt;List average weather for potential travel destinations based on user
inputted date&lt;/li&gt;
&lt;li&gt;Make a photo sharing app&lt;/li&gt;
&lt;li&gt;Add sorting to photo sharing app based on DSLR metadata&lt;/li&gt;
&lt;li&gt;Add ability to download various photo sizes and qualities&lt;/li&gt;
&lt;li&gt;Link photos to your previous travel destinations to create a picture
journal&lt;/li&gt;
&lt;li&gt;Make a flashcard app like Quizlet&lt;/li&gt;
&lt;li&gt;Make a polling app with user submitted answer&lt;/li&gt;
&lt;li&gt;Add upvoting to polling app&lt;/li&gt;
&lt;li&gt;Make a simple app you can &lt;a class="reference external" href="https://startcodingnow.com/submit-your-article/"&gt;submit as your own
tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There you go!&lt;/p&gt;
&lt;p&gt;Did you finish any of these projects? Or have your own project to share?
Leave a comment below.&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="brainstorming"/><category term="django-project"/><category term="project"/><category term="project-idea"/><category term="project-ideas"/><category term="project-planning"/><category term="projects"/></entry><entry><title>Embedding Videos Into Django Project</title><link href="https://startcodingnow.com/embedding-videos-into-django-project" rel="alternate"/><published>2019-09-24T00:00:00-07:00</published><updated>2019-09-24T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2019-09-24:/embedding-videos-into-django-project</id><summary type="html">&lt;p class="first last"&gt;Here's how I embed videos into one of my Django projects.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Have you ever tried to embed a video into your Django website so find
that it wouldn’t work?&lt;/p&gt;
&lt;p&gt;Well, I have.&lt;/p&gt;
&lt;p&gt;Let’s go over the solution I found to this problem.&lt;/p&gt;
&lt;div class="section" id="the-problem"&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;I’ve been making an app for some other people that I mentor. I decided
it would be easier on me and more beneficial to my mentees if I answered
their questions in video instead of writing; speeds things along and I
can increase my output while conveying my non-verbal communication.&lt;/p&gt;
&lt;p&gt;I expected to just hold a Vimeo URL in the database and plug it into a
set embed code in my template.&lt;/p&gt;
&lt;p&gt;Long story short: that didn’t work. So I went searching for a new
answer.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="answer-django-embed-video"&gt;
&lt;h2&gt;Answer: django-embed-video&lt;/h2&gt;
&lt;p&gt;I found the solution in a lovely plugin package,
&lt;a class="reference external" href="https://django-embed-video.readthedocs.io/en/latest/"&gt;django-embed-video&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="install-the-package"&gt;
&lt;h3&gt;Install the package&lt;/h3&gt;
&lt;pre class="code shell literal-block"&gt;
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;django-embed-video
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="change-settings-py"&gt;
&lt;h3&gt;Change &lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt;&lt;/h3&gt;
&lt;pre class="code python literal-block"&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="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;'embed_video'&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;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="add-the-field-to-your-model"&gt;
&lt;h3&gt;Add the field to your model&lt;/h3&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# app_name/models.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&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="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;embed_video.fields&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EmbedVideoField&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelName&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="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EmbedVideoField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank&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="c1"&gt;# an empty field is fine for me&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="load-the-tags-at-the-top-of-your-template"&gt;
&lt;h3&gt;Load the tags at the top of your template&lt;/h3&gt;
&lt;pre class="literal-block"&gt;
{# for me, this was app_name/model_name_detail.html #}
{% load embed_video_tags %}
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="call-the-video-in-the-template"&gt;
&lt;h3&gt;Call the video in the template&lt;/h3&gt;
&lt;pre class="literal-block"&gt;
{# still app_name/model_name_detail.html #}
{# the string at the end is the size of the video #}
{# options: tiny, small, medium, large, huge #}
{% video model_name.video 'medium' %}
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="other-features"&gt;
&lt;h2&gt;Other features&lt;/h2&gt;
&lt;p&gt;You can also &lt;a class="reference external" href="https://django-embed-video.readthedocs.io/en/latest/examples.html#admin-mixin-examples"&gt;add your new model field to the Django
admin&lt;/a&gt;
and &lt;a class="reference external" href="https://django-embed-video.readthedocs.io/en/latest/examples.html#custom-backends"&gt;customize the video
backends&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Plus, you can embed audio from SoundCloud if video isn’t your thing.&lt;/p&gt;
&lt;p&gt;My favorite part about it is that it will auto-detect which backend to
use based on the link. Super simple in case I end up switching from
Vimeo to YouTube.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="alternative-solutions"&gt;
&lt;h2&gt;Alternative Solutions?&lt;/h2&gt;
&lt;p&gt;That’s it! If you’ve ever solved this problem, tell us a story in the
comments below. I’d love to hear what other solutions are out there.&lt;/p&gt;
&lt;p&gt;Likewise, if you’ve ever written your own custom backend, share the
tale!&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="django"/><category term="django-packages"/><category term="django-plugins"/><category term="embed-audio"/><category term="embed-video"/><category term="embedding"/><category term="soundcloud"/><category term="vimeo"/><category term="youtube"/></entry><entry><title>Some Shortcuts in Atom Text Editor</title><link href="https://startcodingnow.com/some-shortcuts-in-atom-text-editor" rel="alternate"/><published>2019-09-23T00:00:00-07:00</published><updated>2019-09-23T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2019-09-23:/some-shortcuts-in-atom-text-editor</id><summary type="html">&lt;p class="first last"&gt;I use these all the time!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;One of the things that blew me away when I started to get back into
coding in this new day and age was the text editors.&lt;/p&gt;
&lt;p&gt;I knew about Notepad and Notepad++, but working with
&lt;a class="reference external" href="https://atom.io/"&gt;Atom&lt;/a&gt; &lt;strong&gt;really&lt;/strong&gt; sped up the code writing process.&lt;/p&gt;
&lt;p&gt;Here are a few shortcuts that I couldn’t live without.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Delete line (Ctrl + Shift + K)&lt;/li&gt;
&lt;li&gt;Duplicate line (Ctrl + Shift + D)&lt;/li&gt;
&lt;li&gt;Select whole term (Ctrl + D)&lt;/li&gt;
&lt;li&gt;Select next copy of this term (Ctrl + D)&lt;/li&gt;
&lt;li&gt;Close window (Ctrl + W)&lt;/li&gt;
&lt;li&gt;Comment out line or group of lines (Ctrl + /)&lt;/li&gt;
&lt;li&gt;Toggle sidebar visibility (Ctrl + \)&lt;/li&gt;
&lt;li&gt;Snippets! (Type something, then hit TAB)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can type “html” then hit TAB and it will give you a bunch of
boilerplate.&lt;/p&gt;
&lt;p&gt;Duplicating lines is good when you have different, but related
functions.&lt;/p&gt;
&lt;p&gt;I’ve even been writing a few articles in Atom, working locally and
easily making lists with (“ol” + TAB), (“ul” + TAB), and (“li” + TAB).&lt;/p&gt;
&lt;p&gt;Which ones did I miss that &lt;strong&gt;you&lt;/strong&gt; can’t live without?&lt;/p&gt;
</content><category term="developer experience"/><category term="atom"/><category term="keyboard-shortcuts"/><category term="notepad"/><category term="shortcuts"/><category term="text-editor"/><category term="text-editors"/></entry><entry><title>Learning Django Past the Documentation</title><link href="https://startcodingnow.com/learning-django-past-the-documentation" rel="alternate"/><published>2019-09-20T00:00:00-07:00</published><updated>2019-09-20T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2019-09-20:/learning-django-past-the-documentation</id><summary type="html">&lt;p class="first last"&gt;Some thoughts on taking the next step to learn Django if you can't find what you're looking for in the documentation.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;After you’ve read documentation from a bunch of different projects,
you’ll come to realize the &lt;a class="reference external" href="https://docs.djangoproject.com/"&gt;Django
Documentation&lt;/a&gt; is fantastic. It’s
concise and contains lots of examples and suggested use cases, which I
have found invaluable as a n00b.&lt;/p&gt;
&lt;p&gt;But sometimes it still leaves you with wtf feels.&lt;/p&gt;
&lt;p&gt;When you get to a point where something you think you need to know is
far outside your scope, what do you do?&lt;/p&gt;
&lt;div class="section" id="realize-you-probably-dont-need-it"&gt;
&lt;h2&gt;Realize you probably don’t need it&lt;/h2&gt;
&lt;p&gt;Most of the books, well-written documentation, and tutorials you’ve read
and watched cover the parts of Django that are most useful or best
practice. If you haven’t heard about something, it’s because most people
aren’t using it. And there’s usually a reason for that. You may want to
ask a professional’s opinion.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="google-search"&gt;
&lt;h2&gt;Google search&lt;/h2&gt;
&lt;p&gt;There may be a rando somewhere who truly DOES think this is useful to
know, and they may have written something about it somewhere. There are
two main types of search results in my head:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Private sites, such as this one&lt;/li&gt;
&lt;li&gt;Stack Overflow questions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A third, less likely scenario is that your google search actually points
you to a “hidden-to-you” part of the documentation that actually answers
your question.&lt;/p&gt;
&lt;p&gt;Take your search results and start tinkering around. There will likely
be some differences, but hopefully not TOO many since you chose an “old”
(read: stable) framework like Django. Even lessons or questions that are
five or more years old can still be helpful.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-pen-and-paper"&gt;
&lt;h2&gt;Use pen and paper&lt;/h2&gt;
&lt;p&gt;Don’t let your propensity for computers bias you.&lt;/p&gt;
&lt;p&gt;Writing a blog on the computer is nice because you can type faster than
you can handwrite. Your brain moves faster than your muscles can, so it
helps you get in that state of &lt;a class="reference external" href="https://amzn.to/2NY3TpT"&gt;flow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That’s no help when you don’t know what you need to do, though.&lt;/p&gt;
&lt;p&gt;When my mind is racing and unfocused, that’s when I reach for a pen and
paper. The goal is to just start writing anything and whittle it down
into something that makes sense.&lt;/p&gt;
&lt;p&gt;Especially write down questions you don’t have answers to. That will
help you focus your research.&lt;/p&gt;
&lt;p&gt;And if you DO figure something out, write that down, too.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="django-source-code"&gt;
&lt;h2&gt;Django source code&lt;/h2&gt;
&lt;p&gt;This feels like the “long way” to do things, but when you consider that
the “short way” doesn’t actually get you anywhere, it’s appeal grows.&lt;/p&gt;
&lt;p&gt;Head over to the &lt;a class="reference external" href="https://github.com/django/django"&gt;Django GitHub
repository&lt;/a&gt;. Find the files you’re
importing and start reading through the classes you’re using. They
probably inherit from many other classes, so you’ll have to read those,
too.&lt;/p&gt;
&lt;p&gt;Getting into the guts of the code can elucidate new pieces you didn’t
know about.&lt;/p&gt;
&lt;p&gt;If you’ve ever worked out, it’s kind of like that: you can better
understand what your exercises do when you know a little bit of anatomy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;The docs don’t contain everything you need. There is always some
implicit information flying under the radar that &lt;strong&gt;really&lt;/strong&gt; simplifies a
topic. When trying to learn a new-to-you piece of Django…&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Figure out if you REALLY need to know this thing now&lt;/li&gt;
&lt;li&gt;Search it on Google and read through other people’s problems&lt;/li&gt;
&lt;li&gt;Write out your questions with pen and paper&lt;/li&gt;
&lt;li&gt;Start reading the Django source code&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="django"/><category term="django"/><category term="django-source-code"/><category term="flow"/><category term="handwriting"/><category term="learning-theory"/><category term="n00b"/><category term="pen-and-paper"/><category term="stack-overflow"/></entry><entry><title>Making Your Own Email Templates in Django</title><link href="https://startcodingnow.com/making-your-own-email-templates-in-django" rel="alternate"/><published>2019-09-19T00:00:00-07:00</published><updated>2019-09-19T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2019-09-19:/making-your-own-email-templates-in-django</id><summary type="html">&lt;p class="first last"&gt;If you want to send emails with long text from your Django application, you don't have to store all of the text in your views. You can make your own email templates!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I’m looking to make my app notify the user when their object is updated
by an admin.&lt;/p&gt;
&lt;p&gt;Problem is that I had &lt;strong&gt;no idea&lt;/strong&gt; how to use my own email templates.&lt;/p&gt;
&lt;p&gt;But I got it working! Here’s how you can put it into your Django
project.&lt;/p&gt;
&lt;p&gt;I’m using Django 2.2.5.&lt;/p&gt;
&lt;div class="section" id="email-settings-in-settings-py"&gt;
&lt;h2&gt;Email settings in &lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;There are two useful ways that I know of to send emails:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Logging directly to the console window&lt;/li&gt;
&lt;li&gt;Sending via SMTP&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first is much quicker, but doesn’t actually send emails. It’s good
for testing.&lt;/p&gt;
&lt;p&gt;The second is what you need if you need to actually send emails to a
user’s inbox.&lt;/p&gt;
&lt;div class="section" id="send-to-console"&gt;
&lt;h3&gt;Send to console&lt;/h3&gt;
&lt;p&gt;To log emails to the console, you just have to set &lt;tt class="docutils literal"&gt;EMAIL_BACKEND&lt;/tt&gt; in
&lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
&lt;/pre&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.djangoproject.com/en/2.2/topics/email/#console-backend"&gt;Here’s the documentation for
reference.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="send-via-smtp"&gt;
&lt;h3&gt;Send via SMTP&lt;/h3&gt;
&lt;p&gt;There are a few settings you’ll need to take care of if you want to send
live SMTP emails:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;EMAIL_BACKEND&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;EMAIL_HOST&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;EMAIL_PORT&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;EMAIL_HOST_USER&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;EMAIL_HOST_PASSWORD&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;EMAIL_USE_TLS&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;EMAIL_USE_SSL&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re ready to give this a try, I would recommend making an account
on &lt;a class="reference external" href="http://sendgrid.com"&gt;SendGrid&lt;/a&gt;. Even when your free trial
expires, you’ll have 100 free emails to send every day. Not enough to
run a big project, but plenty of leeway to learn how to use it.&lt;/p&gt;
&lt;p&gt;Once you’re signed up, you want to find the “Send Your First Email”
instructions to set up SMTP.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Under “Send Your First Email”, click the Start button next to “Integrate using our Web API or SMTP relay”" src="./images/send_email_with_sendgrid.png"/&gt;
&lt;/div&gt;
&lt;p&gt;Then choose SMTP relay, not Web API.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Click Choose under SMTP Relay" src="./images/send_email_with_sendgrid2.png"/&gt;
&lt;p class="caption"&gt;Click Choose under SMTP Relay&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Then you need to create a SendGrid API key. This will be your
&lt;tt class="docutils literal"&gt;EMAIL_HOST_PASSWORD&lt;/tt&gt; in your &lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt; file.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="In step one, type in a name for your key and click the button “Create Key”. In step two, you’ll see listed settings. These will go into your settings.py file." src="./images/send_email_with_sendgrid3.png"/&gt;
&lt;/div&gt;
&lt;p&gt;After naming and creating your key, step two will autopopulate. These
are what you’ll use in your &lt;tt class="docutils literal"&gt;settings.py&lt;/tt&gt; file. &lt;a class="reference external" href="https://sendgrid.com/docs/for-developers/sending-email/django/"&gt;Here’s a link to the
SendGrid
documentation&lt;/a&gt;
on exactly how to set it up.&lt;/p&gt;
&lt;p&gt;Make sure all the settings outlined at the beginning of this section are
set, then you can try to actually send an email.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="from-django-core-mail-import-send-mail"&gt;
&lt;h2&gt;&lt;em&gt;from&lt;/em&gt; django.core.mail &lt;em&gt;import&lt;/em&gt; send_mail()&lt;/h2&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;send_mail()&lt;/tt&gt; function has been in Django for a while now. It
takes a few parameters and sends an email. Very simple.&lt;/p&gt;
&lt;p&gt;You must supply:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;subject&lt;/tt&gt;: string&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;message&lt;/tt&gt;: string&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;from_email&lt;/tt&gt;: string&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;recipient_list&lt;/tt&gt;: list of strings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Worth noting: &lt;tt class="docutils literal"&gt;message&lt;/tt&gt; is the plain text body content of the email.
If you want to send an HTML email, you want to set &lt;tt class="docutils literal"&gt;html_message&lt;/tt&gt;.
We’ll do both to get some practice with templates.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.djangoproject.com/en/2.2/topics/email/#send-mail"&gt;Here’s a link to the Django documentation for
reference.&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="sending-a-plain-email"&gt;
&lt;h3&gt;Sending a plain email&lt;/h3&gt;
&lt;p&gt;Let’s practice. I’m assuming you have your &lt;tt class="docutils literal"&gt;EMAIL_BACKEND&lt;/tt&gt; all set up.
If you can’t get SMTP working, make it log to console to get the
templates working first. It’s much more encouraging than sitting there
trying to troubleshoot why SMTP isn’t sending.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
&lt;/pre&gt;
&lt;p&gt;So for my app, I want to send an email when my &lt;tt class="docutils literal"&gt;UpdateView&lt;/tt&gt; form is
submitted. This will notify the user related to that model object that
they should come check it out. So I will override the &lt;tt class="docutils literal"&gt;form_valid()&lt;/tt&gt;
method to send an email when the &lt;tt class="docutils literal"&gt;UpdateView&lt;/tt&gt; form is submitted.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# app_name/views.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.mail&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_mail&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UpdateView&lt;/span&gt;&lt;span class="w"&gt;

&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;ModelName&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelNameUpdateView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UpdateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ModelName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'field_1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'field_2'&lt;/span&gt;&lt;span class="p"&gt;,]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'app_name/model_name_update.html'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_valid&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;form&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&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;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;      &lt;span class="n"&gt;from_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'me@email.com'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;      &lt;span class="n"&gt;recipient_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'yourpersonalemail@email.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# put your real email here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;      &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Subject text'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;      &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'This is the body of the email.'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;      &lt;span class="n"&gt;send_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recipient_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&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="n"&gt;ModelNameUpdateView&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Update your model with your &lt;tt class="docutils literal"&gt;UpdateView&lt;/tt&gt; and check your console or
inbox (depending on your &lt;tt class="docutils literal"&gt;EMAIL_BACKEND&lt;/tt&gt;) to see if it worked!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="from-django-template-loader-import-render-to-string"&gt;
&lt;h2&gt;&lt;em&gt;from&lt;/em&gt; django.template.loader &lt;em&gt;import&lt;/em&gt; render_to_string()&lt;/h2&gt;
&lt;p&gt;Now that emails are working, we can discuss the templates. Email
templates work just like your traditional Django templates that load
pages: we put the content in its own file, then we point to that file.&lt;/p&gt;
&lt;div class="section" id="create-templates"&gt;
&lt;h3&gt;Create templates&lt;/h3&gt;
&lt;p&gt;We’re going to make three templates:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;app_name/email/model_name_update_email_subject.txt&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;app_name/email/model_name_update_email_message.txt&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;app_name/email/model_name_update_email_message.html&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Make those three files in their respective folders, then move your
strings from &lt;tt class="docutils literal"&gt;views.py&lt;/tt&gt; to each respective template.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;app_name/email/model_name_update_email_subject.txt&lt;/tt&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Subject text
&lt;/pre&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;app_name/email/model_name_update_email_message.txt&lt;/tt&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
This is the body of the email.
&lt;/pre&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;app_name/email/model_name_update_email_message.html&lt;/tt&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
This is the html body of the email.
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="call-render-to-string"&gt;
&lt;h3&gt;Call render_to_string()&lt;/h3&gt;
&lt;p&gt;Now we move back to &lt;tt class="docutils literal"&gt;views.py&lt;/tt&gt; where we want to point to these
template files.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# app_name/views.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.template.loader&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render_to_string&lt;/span&gt;  &lt;span class="c1"&gt;# new&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.mail&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_mail&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UpdateView&lt;/span&gt;&lt;span class="w"&gt;

&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;ModelName&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelNameUpdateView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UpdateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ModelName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'field_1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'field_2'&lt;/span&gt;&lt;span class="p"&gt;,]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'app_name/model_name_update.html'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_valid&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;form&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&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;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;      &lt;span class="n"&gt;from_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'me@email.com'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;      &lt;span class="n"&gt;recipient_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'yourpersonalemail@email.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# put your real email here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;      &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;template_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'app_name/email/model_name_update_email_subject.txt'&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;template_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'app_name/email/model_name_update_email_message.txt'&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;html_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;template_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'app_name/email/model_name_update_email_message.html'&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;send_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recipient_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&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="n"&gt;ModelNameUpdateView&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;render_to_string()&lt;/tt&gt; is kind of like the &lt;tt class="docutils literal"&gt;render()&lt;/tt&gt; function you may
have used before, but returns the template as a string instead of an
&lt;tt class="docutils literal"&gt;HttpResponse&lt;/tt&gt; object.&lt;/p&gt;
&lt;p&gt;Give that a shot and see if it works… hint: it probably won’t.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="python-strip-function"&gt;
&lt;h2&gt;Python strip() function&lt;/h2&gt;
&lt;p&gt;I got a variety of errors here.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;When using the console backend, I just didn’t see any email at all.&lt;/li&gt;
&lt;li&gt;When using the SMTP backend, I received a &lt;tt class="docutils literal"&gt;BadHeaderError&lt;/tt&gt; that
says “Header values can’t contain newlines”.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You might be thinking “I don’t want any newlines in there!”&lt;/p&gt;
&lt;p&gt;Well, we have a solution: strip the whitespace off the ends with the
python &lt;tt class="docutils literal"&gt;strip()&lt;/tt&gt; function. Update your &lt;tt class="docutils literal"&gt;views.py&lt;/tt&gt; file…&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# app_name/views.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;  &lt;span class="n"&gt;template_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'app_name/email/model_name_update_email_subject.txt'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# new&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;And try to trigger your email again.&lt;/p&gt;
&lt;p&gt;And it should be successful!&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="badheadererror"/><category term="django"/><category term="django-2-0"/><category term="django-templates"/><category term="email"/><category term="email-templates"/><category term="email_backend"/><category term="form_valid"/><category term="render_to_string"/><category term="render"/><category term="send_mail"/><category term="sendgrid"/><category term="smtp"/><category term="smtp-relay"/><category term="strip"/><category term="templates"/><category term="updateview"/></entry><entry><title>Django 3.0 Alpha Released</title><link href="https://startcodingnow.com/django-3-0-alpha-released" rel="alternate"/><published>2019-09-18T00:00:00-07:00</published><updated>2019-09-18T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2019-09-18:/django-3-0-alpha-released</id><summary type="html">&lt;p class="first last"&gt;Updates in the big Django 3.0 release.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Big news! The alpha version of Django 3.0 has been released.&lt;/p&gt;
&lt;p&gt;This is not suitable for production!&lt;/p&gt;
&lt;p&gt;BUT, this &lt;strong&gt;is&lt;/strong&gt; suitable for sandboxing. Feel free to install it and
see if it breaks anything. And if you notice something, make sure to
&lt;a class="reference external" href="https://code.djangoproject.com/newticket"&gt;submit a bug&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Or you can try to get up and running with
&lt;a class="reference external" href="https://docs.djangoproject.com/en/dev/howto/deployment/asgi/"&gt;ASGI&lt;/a&gt;.
How exciting!&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.djangoproject.com/weblog/2019/sep/10/django-30-alpha-1-released/"&gt;Here’s a link to the official release
statement.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And &lt;a class="reference external" href="https://docs.djangoproject.com/en/dev/releases/3.0/"&gt;here’s a link to the Django 3.0 release
notes.&lt;/a&gt;&lt;/p&gt;
</content><category term="django"/><category term="alpha-version"/><category term="asgi"/><category term="django-3-0"/></entry><entry><title>Limit Access to Only Superusers in Django</title><link href="https://startcodingnow.com/limit-access-to-only-superusers-in-django" rel="alternate"/><published>2019-09-18T00:00:00-07:00</published><updated>2019-09-18T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2019-09-18:/limit-access-to-only-superusers-in-django</id><summary type="html">&lt;p class="first last"&gt;How to restrict button display and access to a view to only superusers in Django.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;So in my other life as a strength and conditioning coach, I have a few
online mentees. They basically email me questions and I answer them.&lt;/p&gt;
&lt;p&gt;I’m using Django to make an interface for them to submit questions and
see all the other questions that other members have asked. Plus adding
some searching so the old stuff doesn’t just die.&lt;/p&gt;
&lt;p&gt;I am using the generic &lt;tt class="docutils literal"&gt;UpdateView&lt;/tt&gt; to answer the questions. This view
is marking the question “published” and storing my answer in the
database.&lt;/p&gt;
&lt;p&gt;My issue is that I don’t want the users to be able to see this because
then they could be adding answers to questions and people are paying for
ME to answer the question.&lt;/p&gt;
&lt;p&gt;This entails two major parts:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Adding a link to the &lt;tt class="docutils literal"&gt;UpdateView&lt;/tt&gt; in the &lt;tt class="docutils literal"&gt;DetailView&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Blocking permissions to access the form with &lt;tt class="docutils literal"&gt;UserPassesTestMixin&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s walk through it.&lt;/p&gt;
&lt;div class="section" id="add-link-to-updateview"&gt;
&lt;h2&gt;Add link to UpdateView&lt;/h2&gt;
&lt;p&gt;Order doesn’t particularly matter here. You’ll have to do each step. I
like to start with the URL, so first, we need to add the URL to our
UpdateView to our &lt;tt class="docutils literal"&gt;urls.py&lt;/tt&gt; file.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# app_name/urls.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AppNameUpdateView&lt;/span&gt; &lt;span class="c1"&gt;# make sure to import your view&lt;/span&gt;&lt;span class="w"&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="w"&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# other URL patterns&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&lt;int:pk&gt;/update/'&lt;/int:pk&gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AppNameUpdateView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'model_name_update'&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;/pre&gt;
&lt;p&gt;Once we have that, let’s go ahead and make this &lt;tt class="docutils literal"&gt;UpdateView&lt;/tt&gt; in our
&lt;tt class="docutils literal"&gt;views.py&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# app_name/views.py&lt;/span&gt;&lt;span class="w"&gt;
&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;ModelName&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppNameUpdateView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UpdateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ModelName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'field_1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'field_2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;']&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'app_name/model_name_update.html'&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Now we just need to get the template running.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{# app_name/model_name_update.html #}
&lt;h2&gt;Update&lt;/h2&gt;
&lt;form action="{% url 'model_name_update' pk=obj.pk %}" method="post"&gt;
  {% csrf_token %}
  {{ form.as_p}}
  &lt;button type="submit"&gt;Update&lt;/button&gt;
&lt;/form&gt;
&lt;/pre&gt;
&lt;p&gt;Check to make sure it works before moving on.&lt;/p&gt;
&lt;p&gt;Once that skeleton is working, let’s link to this page on the
&lt;tt class="docutils literal"&gt;DetailView&lt;/tt&gt; page. Remember we only want admins to be able to get to
this &lt;tt class="docutils literal"&gt;UpdateView&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{# app_name/templates/app_name/model_name_update.html #}
&lt;!-- meat of the page above this line --&gt;
{% if user.is_superuser %}
&lt;a href="{% url 'model_name_update' pk=obj.pk %}"&gt;
  Update
&lt;/a&gt;
{% endif %}
&lt;/pre&gt;
&lt;p&gt;Awesome, now we have a working link to the &lt;tt class="docutils literal"&gt;UpdateView&lt;/tt&gt; so we don’t
have to hard type it directly into our browser.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="adding-permissions"&gt;
&lt;h2&gt;Adding permissions&lt;/h2&gt;
&lt;p&gt;Now what if a user finds or guesses the link? They’ll still be able to
see the &lt;tt class="docutils literal"&gt;UpdateView&lt;/tt&gt; because we’ve only authenticated the vision of
the link to the page, not the view and its logic.&lt;/p&gt;
&lt;p&gt;Fortunately, Django makes this super easy. You just have to know about
the &lt;tt class="docutils literal"&gt;UserPassesTestMixin&lt;/tt&gt;. &lt;a class="reference external" href="https://docs.djangoproject.com/en/2.2/topics/auth/default/#limiting-access-to-logged-in-users-that-pass-a-test"&gt;Here’s a link to the
documentation.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;UserPassesTestMixin&lt;/tt&gt; is designed to work with class-based views. All
we need to do is&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;define the test as a function within the view by overriding
&lt;tt class="docutils literal"&gt;test_func()&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;define the &lt;tt class="docutils literal"&gt;login_url&lt;/tt&gt; attribute to tell Django where to redirect
an unauthorized user&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;# app_name/views.py&lt;/span&gt;&lt;span class="w"&gt;
&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;ModelName&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppNameUpdateView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UpdateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ModelName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'field_1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'field_2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;']&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'app_name/model_name_update.html'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;login_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reverse_lazy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'login'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# new&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_func&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="c1"&gt;# new&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&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;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;/pre&gt;
&lt;p&gt;And there we go!&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="authorization"/><category term="detailview"/><category term="django"/><category term="django-urls"/><category term="permissions"/><category term="templates"/><category term="updateview"/><category term="urls-py"/><category term="userpassestestmixin"/><category term="views-py"/></entry><entry><title>Django Has Changed Over Time</title><link href="https://startcodingnow.com/django-has-changed-over-time" rel="alternate"/><published>2019-09-17T00:00:00-07:00</published><updated>2019-09-17T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2019-09-17:/django-has-changed-over-time</id><summary type="html">&lt;p class="first last"&gt;To help save you time, I want to outline some of the major differences
I’ve run into while learning Django.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Repetition is king for learning. Working with models, views, and
templates over and over gets repetitive, but that’s a good thing.&lt;/p&gt;
&lt;p&gt;Have you ever seen a tutorial online and thought, “That code doesn’t
look very familiar.” No repetition, no knowledge.&lt;/p&gt;
&lt;p&gt;Usually these differences are not WAY off. The similarities give you
sense for what you have to do. But after you give it a shot, you get an
error.&lt;/p&gt;
&lt;p&gt;One of the biggest reasons to start your web development journey by
learning Django is because it is OLD. Old things are set in their ways.
They won’t change so much on you as you slog along.&lt;/p&gt;
&lt;p&gt;In fact, this was one of the biggest reasons I decided to pick Django as
my learning language. You can be sure that you won’t lose much on your
investment because what you learn will still be relevant in the future.&lt;/p&gt;
&lt;p&gt;But still, things have changed in Django. And this makes it difficult to
learn on the internet. There’s a lot of wasted time. And time is more
valuable than money.&lt;/p&gt;
&lt;p&gt;To help save you time, I want to outline some of the major differences
I’ve run into while learning Django.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/div&gt;
&lt;div class="line"&gt;When reading a tutorial, attempt to find out the version of Django the
author is using. You can view changes in the &lt;a class="reference external" href="https://docs.djangoproject.com/en/2.2/releases/"&gt;Django release
notes&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="urlpatterns"&gt;
&lt;h2&gt;urlpatterns&lt;/h2&gt;
&lt;p&gt;Before version 2.0, Django used a &lt;tt class="docutils literal"&gt;url()&lt;/tt&gt; function to add a URL to
your &lt;tt class="docutils literal"&gt;urlpatterns&lt;/tt&gt; list. &lt;a class="reference external" href="https://docs.djangoproject.com/en/1.11/ref/urls/#url"&gt;Here’s a link to the 1.11 django.conf.urls
documentation.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Django 2.0 added &lt;tt class="docutils literal"&gt;path()&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;re_path()&lt;/tt&gt; functions for putting URL
items in your &lt;tt class="docutils literal"&gt;urlpatterns&lt;/tt&gt; list in &lt;tt class="docutils literal"&gt;urls.py&lt;/tt&gt;. &lt;a class="reference external" href="https://docs.djangoproject.com/en/2.0/ref/urls/"&gt;Here’s a link to the
2.0 django.urls
documentation.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;They function similarly, but &lt;tt class="docutils literal"&gt;url()&lt;/tt&gt; will likely be deprecated in the
future.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="import-statements"&gt;
&lt;h2&gt;Import statements&lt;/h2&gt;
&lt;p&gt;Import statements have been the single biggest challenge for me. With
all of this DRY, reusable code, there is this complex web of python
files. It’s kind of like navigating through a maze: you can see the path
ahead of you, but you need a memory sense of everything around you if
you’re going to make it out of the maze.&lt;/p&gt;
&lt;p&gt;The design is fantastic, but it’s difficult to pick up.&lt;/p&gt;
&lt;p&gt;If you get an &lt;tt class="docutils literal"&gt;ImportError&lt;/tt&gt;, it’s possible the file you’re looking for
has just been moved. Make sure to read the error because it might tell
you what’s going on. Then, as always, Google is your friend. You may
even want to &lt;a class="reference external" href="https://docs.djangoproject.com/en/2.2/search/?q="&gt;search directly on the Django
Documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;An example to note that tripped me up recently: mapping urls.&lt;/p&gt;
&lt;p&gt;You may have noticed in the previous section that the import path is
different for those URL functions.&lt;/p&gt;
&lt;p&gt;Previously your import statement at the top of your &lt;tt class="docutils literal"&gt;urls.py&lt;/tt&gt; file
would be:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from django.conf.urls import url
&lt;/pre&gt;
&lt;p&gt;In Django 2.0, it looks like:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from django.urls import path
&lt;/pre&gt;
&lt;p&gt;Another thing to note: the &lt;tt class="docutils literal"&gt;include()&lt;/tt&gt; function for including other
urls.py urlpatterns has followed this move, located at
&lt;tt class="docutils literal"&gt;django.urls.include()&lt;/tt&gt; in Django 2.0.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="python-print-statements"&gt;
&lt;h2&gt;Python print statements&lt;/h2&gt;
&lt;p&gt;Python 3 had a major update in print statements. Here’s the differences
listed directly from the &lt;a class="reference external" href="https://docs.python.org/3/whatsnew/3.0.html"&gt;Python 3 Release
Notes&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Old: print "The answer is", 2*2
New: print("The answer is", 2*2)

Old: print x,           # Trailing comma suppresses newline
New: print(x, end=" ")  # Appends a space instead of a newline

Old: print              # Prints a newline
New: print()            # You must call the function!

Old: print &amp;gt;&amp;gt;sys.stderr, "fatal error"
New: print("fatal error", file=sys.stderr)

Old: print (x, y)       # prints repr((x, y))
New: print((x, y))      # Not the same as print(x, y)!
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="help-us-all"&gt;
&lt;h2&gt;Help us all!&lt;/h2&gt;
&lt;p&gt;Got another example? Leave a comment below because &lt;strong&gt;others are having
the same problem!&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/><category term="django"/><category term="django-1-11"/><category term="django-2-0"/><category term="django-urls"/><category term="dry"/><category term="importerror"/><category term="mapping-urls"/><category term="path"/><category term="print-statements"/><category term="print"/><category term="python"/><category term="python-2"/><category term="python-3"/><category term="re_path"/><category term="url"/><category term="urlpatterns"/><category term="urls-py"/></entry><entry><title>Why I Wanted to Learn to Code</title><link href="https://startcodingnow.com/why-i-wanted-to-learn-to-code" rel="alternate"/><published>2019-09-14T00:00:00-07:00</published><updated>2019-09-14T00:00:00-07:00</updated><author><name>Lance Goyke</name></author><id>tag:startcodingnow.com,2019-09-14:/why-i-wanted-to-learn-to-code</id><summary type="html">&lt;p class="first last"&gt;Time is scarce, so what will you do with your life? I chose to
learn to code. Here's why.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Every answer is at your fingertips, but time is scarce. What will you
choose to do?&lt;/p&gt;
&lt;p&gt;I encourage everyone I meet to take the mindset of the “lifelong
learner”. If you aren’t a little embarrassed by who you were ten years
ago, then you’re probably not improving. And the best way to help the
world is by first helping yourself.&lt;/p&gt;
&lt;p&gt;But why coding?&lt;/p&gt;
&lt;div class="section" id="backstory"&gt;
&lt;h2&gt;Backstory&lt;/h2&gt;
&lt;p&gt;My first love was actually ice hockey. I grew up in Michigan, USA, where
hockey is a big deal. I’ve been on ice since I was three years old.&lt;/p&gt;
&lt;p&gt;Hockey taught me work ethic. I couldn’t be the best if I didn’t work
hard. But it also taught me what it’s like to feel superior, to win.&lt;/p&gt;
&lt;p&gt;And I had to learn how to lose. The hard way.&lt;/p&gt;
&lt;p&gt;Ego fragility paved the path of lifelong learning. The only reason you
would seek to change is if you’d like to see something &lt;em&gt;different&lt;/em&gt; about
yourself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="practice"&gt;
&lt;h2&gt;Practice&lt;/h2&gt;
&lt;p&gt;I got a heavier dose of repeated practice just before high school when I
picked up my first guitar. How cool would it be to make those sounds
that all my favorite bands make.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;There’s that ego again.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Guitar is much different than sports. You don’t have the same physical
inputs. You have to listen to what your ears are telling you to know if
you’re doing it well or not.&lt;/p&gt;
&lt;p&gt;Monotonous. Repetition. Repetition. Repetition. Over and over.&lt;/p&gt;
&lt;p&gt;You can practice for an hour and not get much better.&lt;/p&gt;
&lt;p&gt;But if you practice for an hour every day for six months…&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="fitness"&gt;
&lt;h2&gt;Fitness&lt;/h2&gt;
&lt;p&gt;Once in high school, I started working out. And my oh my was that fun.&lt;/p&gt;
&lt;p&gt;If you like to be rewarded for your effort, start working out. The
strength you gain in the first few months, known colloquially as “newbie
gains,” is perhaps the single best investment anyone can make.&lt;/p&gt;
&lt;p&gt;You see, your body is capable of magical things. But your brain slows
you down, fearing injury. There are stories of women lifting cars off of
their children. Now &lt;strong&gt;that&lt;/strong&gt; is motivation.&lt;/p&gt;
&lt;p&gt;What brakes are you riding with?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="career"&gt;
&lt;h2&gt;Career&lt;/h2&gt;
&lt;p&gt;I ended up turning fitness into a career, a job title I still carry to
this day. I’ve learned a lot in the field, coaching for 10+ years and
even acquiring a masters degree in Anatomy &amp;amp; Cell Biology.&lt;/p&gt;
&lt;p&gt;If you’ve never dedicated time to help someone out, do it. Talk about
rewarding. It’s especially important if you’re the type of person who
isn’t quite sure that you’re &lt;em&gt;worth&lt;/em&gt; anything. We’ve all been there.
When you teach someone something new, that doubt disappears
(temporarily, so remember to practice that skill).&lt;/p&gt;
&lt;p&gt;What can you teach?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="coding"&gt;
&lt;h2&gt;Coding&lt;/h2&gt;
&lt;p&gt;I started to ask myself, “How can I make a greater impact?” Though the
impact is great, coaching people to be stronger and fitter doesn’t scale
well. I can help 30-50 people, but any more than that and there’s no way
I’ll be able to sustain it with how I operate. I think in our
Information Age, a personal touch goes a long way. How can I free up
resources to allow for interpersonality?&lt;/p&gt;
&lt;p&gt;That’s why I look to tech.&lt;/p&gt;
&lt;p&gt;I always liked math in grade school. And I picked up computers pretty
quickly, at least relative to the people I knew. Maybe coding could be
the answer?&lt;/p&gt;
&lt;p&gt;I tried it in college and failed. I didn’t know how to teach it to
myself. And I did not have a very involved teacher. I fell behind and
never caught up.&lt;/p&gt;
&lt;p&gt;But in the last couple years, I decided to pick it back up again. I
wanted to tinker with my personal website. I already had a little
sandbox to play in. Low risk, high reward; might as well try it out.&lt;/p&gt;
&lt;p&gt;So I taught myself HTML and CSS. I learned how WordPress worked. Then I
learned how use PHP to change up a WordPress website. I learned how the
web works and started a few websites for some fitness friends.&lt;/p&gt;
&lt;p&gt;Then that started to get boring. I can help others spread their
messages, but surely there’s a more powerful way.&lt;/p&gt;
&lt;p&gt;That’s when I started diving into Python and the Django web framework.
Maybe I can start making websites that &lt;em&gt;aren’t&lt;/em&gt; WordPress websites?&lt;/p&gt;
&lt;p&gt;I considered a few coding bootcamps and even going back to college. But
it didn’t make sense to me to totally abandon a career that is still
working for me. I know some people are there, but that’s not what drives
me.&lt;/p&gt;
&lt;p&gt;What drives me is getting better. Learning new things. Developing
myself.&lt;/p&gt;
&lt;p&gt;If you want to improve, one of the easiest things to learn is basic
coding.&lt;/p&gt;
&lt;p&gt;And don’t wait for the time to be right. The time is never right.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start coding now.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"/></entry></feed>