What devastating news! The North Carolina Zoo will permanently shutter its aviary, by far the best attraction! I've taken many photos over the years in the aviary, as it was one of the most photogenic places at the zoo (and, one could argue, in all of North Carolina). I get that the building is likely in bad shape, but can't we set up a GoFundMe or something to build a replacement? What a terrible loss.

I've only recently discovered the music of Rush, a band which I've previously overlooked. Geddy Lee's voice takes some getting used to, but the musicianship of this band is astounding. I'm really enjoying hearing their albums for the first time.

Hemispheres is my favorite album of theirs that I've heard so far. The highlight track is La Villa Strangiato, an instrumental piece that clocks in at over nine and a half minutes.

The entire album can be heard on YouTube, and is included below. Check it out!

Minor Site Tweaks

Mar 30, 2022

This shouldn't affect too many viewers to this site, but I've tweaked the stylesheets (and markup) slightly to improve the reading experience on mobile devices. If you spot anything that's obviously broken, let me know.

Django REST Framework (DRF) is, on the surface, a neat piece of software. It provides web interactivity (for free!) to your REST interfaces, and can make generating those interfaces pretty quick to do. It has baked in authentication and permission handling. With just a few lines of code you're up and running. Or are you?

As you dig deeper into their tutorial, you'll find that this framework is abstraction layer on top of abstraction layer. Using naked Django style views, I could easily write a listing routine for a specific model in my application. Let's take an example model (all code in this post will omit imports, for brevity):

class Person(models.Model):
    email = models.CharField(max_length=60, unique=True)
    first_name = models.CharField(max_length=60)
    last_name = models.CharField(max_length=60)
    display_name = models.CharField(blank=True, max_length=120)
    manager = models.ForeignKey('self', related_name='direct_reports', on_delete=models.CASCADE)

    def __str__(self):
        return (self.display_name if self.display_name
                else f"{self.first_name} {self.last_name}")

This model is simply a few key pieces of data on a person inside my application. A simple view to get the list of people known to my application might look like this:

class PersonList(View):
    def get(self, request):
        people = []
        for x in Person.objects.select_related('manager').all():
            obj = {
                'email': x.email,
                'first_name': x.first_name,
                'last_name': x.last_name,
                'display_name': str(x),
                'manager': str(x.manager),
            }
            people.append(obj)
        return JsonResponse({'people': people})

This, I would argue, is simple and easy to read. It may be a little verbose for me, the programmer, I admit. But if another programmer comes along behind me, they're fairly likely to understand exactly what's going on here; especially if they are a junior programmer. Maintenance of this code therefore becomes trivial.

Let's now look at a DRF example. First I need a serializer:

class PersonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        fields = '__all__'
        depth = 1

This looks good, but it doesn't handle the display_name case correctly, because I want the str() method output for that field, not the field value itself. The same goes for the manager field. So I now have to write some field getters for both. Here's the updated serializer code:

class PersonSerializer(serializers.ModelSerializer):
    display_name = serializers.SerializerMethodField()
    manager = serializers.SerializerMethodField()

    class Meta:
        model = Person
        fields = '__all__'
        depth = 1

    def get_display_name(self, obj):
        return str(obj)

    def get_manager(self, obj):
        return str(obj.manager)

Once my serializer is complete, I still need to set up the view that will be used to actually load the list:

class PersonList(generics.ListAPIView):
    queryset = Person.objects.select_related('manager').all()
    serializer_class = PersonSerializer

I'll admit, this is pretty lean code. You cannot convince me, however, that it's more maintainable. The junior programmer is going to come in and look at this and wonder:

  • Why do only two fields in the serializer have get routines?
  • What even is a SerializerMethodField?
  • Why is the depth value set on this serializer?
  • What does the ListAPIView actually return?
  • Can I inject additional ancillary data into the response if necessary? If so, how?

DRF feels like it was designed by Java programmers (does anyone else get that vibe?). REST interfaces always have weird edge cases, and I'd much rather handle them in what I would consider the more pythonic way: simple, readble, naked views. After all, according to the Zen of Python:

  • Simple is better than complex.
  • Readability counts.

Wordle Helper 2

Mar 23, 2022

My previous Wordle helper has now been supplanted by a newer variant, which adds a number of nice new features. Here's a screenshot of the new UI:

Wordle Helper 2

The top five rows represent the slots for each character of the five-letter word. Each row consists of four controls:

  • The left-most column displays the possible remaining choices for that position.
  • The column with a red background is where you specify letters that are in the word, but not in the given position.
  • The column with a green background is where you specify letters that are in the word and are in the given position.
  • The final column shows the subset of possibilities using only the letters known to be in the word.

As long as the body of the page has focus, you may simply type letters to remove them from play. Holding shift while typing a letter will add that letter to the pool of known letters. Holding the control key will remove the letter from the pool (if you make a mistake). Be careful with this, however; some browser shortcuts cannot reliably be trapped (ctrl + w being one of them).

As an alternative to typing, you can click the on-screen keyboard to remove letters, and Shift + click letters to add them. Here's a second screenshot showing the tool while in use:

Wordle Helper 2 (in use)

Date Nut Balls

Mar 13, 2022

This family favorite is a staple of the holiday season. It's one of my all-time favorite snacks, and you truly cannot eat just one!

  • 1 stick butter (or margarine)
  • 1 cup light brown sugar
  • 8 oz. package of dates, cut fine
  • 4 oz. flaky coconut
  • 1 cup chopped nuts (peanuts or walnuts)
  • 2 cups Rice Krispies
  • Powdered sugar

Combine the butter, brown sugar, dates, and coconut in a heavy saucepan. Start a timer when you put it on the stove and cook for 6 minutes. Do not overcook!

After the timer sounds, add the chopped nuts and the Rice Krispies, mix well, and let it cool. Shape the mixture into balls, and roll in powdered sugar while still warm.

Where possible, my family tries to reduce the amount of plastic we consume. One example of this is our choosing not to purchase tubs of spreadable butter, a food staple we eat plenty of. Instead, we convert sticks of butter (which come in paper containers) into spreadable butter. The recipe we use to do this is shown below. It's an easy way to prevent extra plastics from entering the waste stream.

  • Two sticks unsalted butter (room temperature)
  • 1/2 tsp salt
  • 2/3 cup oil

Bring the sticks of butter to room temperature by leaving them out overnight. Place the butter into a bowl, along with the salt and oil of your choosing. We use avocado oil, as it has a pretty neutral taste. Olive oil is also a good choice, but be aware that olive oil can have a fairly overpowering taste.

Mix the ingredients well with a hand-mixer. Pour the contents into a container (we reuse old, plastic butter tubs) and refrigerate. Two sticks of butter fit nicely into a 15-ounce butter container.

I love Sublime Text and use it across a number of my computers. Occasionally, one of my systems becomes down-level, especially in terms of the packages I'm using. I know I can sync my user profiles via Git (or something similar), but occasionally it's good to reset things and start from a fresh profile.

So that I can keep track of what I primarily use, I'm writing down the packages I use on my primary development system.

Last Updated: May 16, 2024

Wordle Helper

Feb 23, 2022
Note that this has been replaced by a newer version.

I've been enjoying Wordle, despite the transition to The New York Times (I expect the game to disappear behind the paywall sometime in the near future). Some of the variants of Wordle have also been enjoyable, my favorite among them being Quordle.

One strategy I've used as I've played over the past few weeks is to use a text-editor to keep track of the letters I know are valid, along with their positions. This aids me in figuring out potential words to guess, and others to rule out. Being the programmer that I am, I turned this manual process into a tiny, self-contained web application:

Wordle Helper Screenshot

You enter the characters you know are present in the word in the top box. As you learn about the positions of each character, you also fill in that information. In the example shown above, the word I'm trying to guess is "power." I know the o is in the second position, and I know the w is not in the first position. The resulting choices set at the bottom of each column helps me figure out what it might be.

It was fun to put together this tiny little script.

I don't open multiple file handles at once very often in Python, so it surprised me to find out this morning that you have to use the old-school line continuation hack to make it work (in 3.9 or earlier):

# Note the trailing backslash on the next line...
with open('a.txt', 'w') as file_a, \
     open('b.txt', 'w') as file_b:

    # Do something with file_a
    # Do something with file_b

Happily, Python 3.10 fixes this by adding Parenthesized Context Managers, which allows us to use parentheses like you would expect to:

# Only in Python 3.10+
with (
    open('a.txt', 'w') as file_a,
    open('b.txt', 'w') as file_b
):

    # Do something with file_a
    # Do something with file_b

The project I'm working on is still on Python 3.9, but it's good to know this was improved, and is a motivator to upgrade the version I'm using.

Flying Blind

Feb 6, 2022

I love almost any video that Tom Scott puts out. His latest, from his Tom Scott Plus channel, is fantastic. The teaser: "She's blind. I'm blindfolded. We're going to fly a plane." Check it out!

Every Day Meatloaf

Feb 1, 2022

This is a recipe I've been meaning to transcribe for a while now. It's delicious and the sauce is what really makes it (we sometimes double the sauce recipe).

  • 2 slices of dry bread, broken
  • 1 cup milk
  • 1.5 pounds ground beef
  • 2 beaten eggs
  • 1 onion, diced
  • 1 tsp. salt
  • 1/2 tsp. sage
  • Dash of pepper

Soak bread in milk while assembling the other ingredients. Add meat, eggs, onion, sage, salt, and pepper to a bowl; mix well. Place in baking pan and cover with piquant sauce. Bake in a moderate oven (350 degrees F) for 45 minutes to 1 hour.

Piquant Sauce

  • 3 Tbsp. brown sugar
  • 1/4 cup ketchup
  • 1/4 tsp. nutmeg
  • 1 tsp. dry mustard

Combine ingredients in a small bowl.

This new Technology Connections video is great, and has me wanting to buy a new can opener.

Circus Peanuts

Jan 14, 2022

I am one of the few people on this planet who enjoys circus peanuts (though, given that multiple companies manufacture them, I can't be alone, right?). In fact, I bought a small bag of them tonight at the grocery store. My wife, who dislikes them immensely, tried a bit of one to remind herself of their taste. As expected, she still hated it. "It tastes like styrofoam," she said. I guess that means there's more for me!

Circus peanuts; yum!

Anderson Bruford Wakeman Howe were a progressive rock band comprising 4 of the 5 members of Yes. They only released a single, self-titled studio album, and it might as well be considered a "lost" Yes album. Released in 1989, well after the 90125 album that redefined their sound, this particular album is really nice. It has a number of great melodies, terrific musicianship, and varied musical themes. Give it a listen here on YouTube (I apparently can't embed the video, so the link will have to suffice).

I like using Google Photos, as it has nearly all of the features I care about. However, the search capability is puzzlingly lacking, especially from a company whose core foundation is based on searching for stuff. One of the features that Google touts for Photos is their AI-enhanced searching capability. For instance, if you type a color into the search box, you'll see a listing of photos that feature that color; pretty neat! However, if you search for specific text that appears in your image descriptions, it won't provide any results.

One of things I tend to do for my wildlife pictures is enter the animal's identification into the image description: for example, the description for a picture of a male northern cardinal might look something like Northern Cardinal (Male). Later, if I happen to search for "northern cardinal," no results will be returned! I'd love to be able to search the meta-data (and maybe even the comments) for my photos. It would allow me to use the photos as a database of sorts, which is as it should be.

I Hate FedEx

Dec 28, 2021

A little over a month ago, I ordered an UpLift Desk for my office. All three packages were shipped via FedEx, and it was a colossal headache. The top arrived damaged, so I had the company send me another, which they graciously did. That one also arrived slightly damaged, though not as badly as the first. I decided to use the second top, just to avoid having to deal with another support ticket (the desk, by the way, is terrific).

The FedEx tracking updates, while packages are en route, appear to be updated only once daily. Compare this to UPS or Amazon, who update their tracking information immediately. FedEx's estimations for when the packages would arrive were also all incorrect (only one of the three packages ended up arriving the day it was forecast).

Nearly a week ago, my wife's phone broke. We ordered a replacement direct from Google, and guess who it's shipping through? We ordered it December 22, and it was scheduled to be delivered December 26, by the end of the day. The last tracking update was December 23; nothing further appeared until this morning. Today's update shows that the package is in Charlotte, NC, but with this caveat: Delivery exception: Damaged, handling per shipper instructions.

I guess at least they're honest. It's not clear to me, however, what happens next. Will FedEx deliver the damaged package as is? Will it get returned to the shipper, only to have a new one shipped again? I contacted Google about it, but my support ticket is still being processed (apparently the Google support folks don't even know what happens).

FedEx has miles to go to catch up to their competition. I've never had these kind of issues with UPS or Amazon shipping. It amazes me that a company with this kind of service remains in business.

Gmail Spam Filtering

Dec 21, 2021

Is it just me, or has Gmail's spam filtering gotten worse? In the past month or so, I've had more false positives end up in the spam folder than I've ever had while using their service. Emails from all sorts of valid places:

  • My online pharmacy orders
  • Electronic receipts from The Home Depot
  • Bug reports on GitHub / GitLab
  • Notifications from NewsBlur, my RSS feed reader of choice

Every time one of these false positives occurs, I click the "Not Spam" button, but it doesn't seem to make much of a difference. Has anyone else seen anything similar? It's really rather frustrating.

Musical Output

Dec 12, 2021

I haven't yet seen the Get Back documentary about The Beatles, but I've been watching various clips of it online. One of the surprising things about what I've seen so far is the rejection of some of George Harrison's songs: specifically All Things Must Pass, which is one of George's best all-time compositions. Like many of his other songs, however, John and Paul dismiss it.

I genuinely don't understand this mindset. My best guess is that there's a lot of ego going into these decisions (and likely jealousy). As a member of a band, whose very existence relies on making music, shouldn't the group welcome every single song or idea a member comes up with? If I were in a band, I feel like I'd want to embrace every idea that came along. More ideas lead to more songs; that, in turn, leads to more albums, more sales, and more touring possibilities. It seems like a no-brainer to me. Think of all the great music the world has been denied because of petty squabbles between people in a musical group. What a shame.

How Optical Mice Work

Dec 4, 2021

I had a general idea about how optical mice work, but I didn't know the specifics. This nicely animated video gives a short, high-level overview of exactly what's happening. It's remarkably well produced.