-
The Week #31
by- As I've been public blogging about ๐Tanzawa I've had more people emailing me, responding to my posts, or DMing me directly about it than any past project. One of the things I still have difficulty talking about succinctly is the IndieWeb, Tanzawa, and how it all fits together. I think I may be getting better at it though.
This week I was explaining the building blocks of the IndieWeb to my internet buddy Frey. My explanation seemed to click as he's added rss to his blog and joined micro.blog!
Each month at BeProud there's a monthly meet-up where employees give presentations and afterwards there's sushi, beer, and chitchatting. It's great fun. When you first join the company, it's tradition for you give a 10-minute self-introduction presentation. The meetup still happens with covid, but remotely over zoom.
As we've grown we realized that newer employees don't have an opportunity to learn about the longer-tenured employees without asking them directly, which is difficult. This week it was my turn to re-introduce myself to the company. This time I was able to speak for 10 minutes in front of half the company without much effort or really thinking too much. Much different than my first time almost 4 years ago.
- I discovered you can use VideoLan to play videos off YouTube. You could probably use this to download videos, but I'm not doing that. I'm just enjoying being able to have a native app play music without all of the bloat and trackers running in the background.
- Kai wrote a post with some tips and tricks in the terminal (Japanese). Improving my fluency in the shell is one of my goals for this year. This week I'm going to try and use some of these.
- Leo's started to recognize when he's wet his diaper and tells us about it. I think this means the days of changing diapers will be coming to an end soon. ๐๐ป
-
Aggregating Nested Values with JSONFields
byNote: I originally wrote this for the Kwoosh Workshop blog. Since it's no longer around and I'm posting it here.
Integrating with 3rd party APIs you'll often need to implement a webhook for your application to respond to external events. It's useful to take the event data and store it in your Database for future reference.
As Kwoosh is run out of Texas, users in Texas need to pay an 8.25% sales tax. To my surprise Stripe's
dashboard doesn't seem to offer an answer to a simple question: How much did I collect that was taxable
and how much do I need to remit to the state for last quarter's sales.Armed with our event log data and Python we can quickly get that the numbers we're looking for.
Our data looks something like this. Naturally a real event would have a lot more data, but for our purposes
today this should suffice.{
"data": {
"object": {
"tax": 25
...
}
}
}This data is sent with each invoice.payment_successful event and it's saved in aa JSONField in our database. Using the KeyTransform we can extract values from our JSONField and annotate them
to our QuerySet. Even better, as of Django 1.11 we can nest our KeyTransform so we can extract values
that are multiple levels deep.Our plan of attack is to annotate the value, sum them together, and return them with the period.
Unfortunately we have to sum them in Python until bug #29139
is fixed. We're not summing millions of rows so it's still quick enough.from functools import reducefrom django.contrib.postgres.fields.jsonb import KeyTransform
from accounts.models import StripeEvent
def accumulate_tax(start_date, end_date):
"""
Can't aggregate KeyTransformed objects directly, so summing manually
Reference: https://code.djangoproject.com/ticket/29139
"""
events = StripeEvent.objects.filter(
created__range=(start_date, end_date),
type='invoice.payment_succeeded'
).annotate(
tax=KeyTransform('tax', KeyTransform('object', KeyTransform('data', 'data'))),
).values('tax')if events:
tax_total = reduce(lambda x, y: dict((k, (v or 0) + (y[k] or 0),)
for k, v in x.items()), events)
else:
tax_total = {'tax': 0}for key, value in tax_total.items():
tax_total[key] = '${:,.2f}'.format(value / 100)return {'start': start_date, 'end': end_date, 'totals': tax_total}
And just like that we can aggregate values from our JSONFields. If you want to sum other fields
you can simply add another line like this, but replace "tax" with the field you want to sum.To make it a bit more useful, I built a second function that uses this function to give me quarterly sums.
import datetimeQUARTERS = (
((1, 1), (3, 31)),
((4, 1), (6, 30)),
((7, 1), (9, 30)),
((10, 1), (12, 31)),
)def calculate_quarters():
today = datetime.date.today()
last_year = today.year - 1for start, end in QUARTERS:
quarter_start = datetime.date(year=today.year, month=start[0], day=start[1])
year = today.year
if quarter_start > today:
year = last_yearyield accumulate_tax(datetime.datetime(year=year, month=start[0],
day=start[1], hour=0, minute=0,
second=0),
datetime.datetime(year=year, month=end[0],
day=end[1], hour=23, minute=59,
second=59))We can simply iterate over this function and generate a simple report that shows us a quarterly breakdown
of our sales and taxes collected. -
byI got the IndieAuth authorization api working. Initially I had built it out using the rest_framework.authtoken. However I realized their models only allow one token per user.ย This would make integrating with multiple clients impossible, so I'm opting to generate / manage tokens myself.
I can almost complete a sign in with OwnYourSwarm. I think my token endpoint response is wrong, which is preventing it from completing. Hopefully tomorrow I can get the complete signin working so I can move on to the micropub endpoint.
I feel like I'm starting to break small bits here and there inadvertently . So perhaps before building out the micropub endpoint and potentially breaking more things, I should take a step back and start adding some unit tests for my apis. It'll cost me a day today, but save me days of headaches over the course of the project. -
byGot the start of the indieauth authorization flow working and the base models setup.
Indieauth auth screen -
UX Design Textbook
byAs part of of the "team UX" at work we're doing a bookclub to make sure we all have good foundations in UX before making it a company offering. We're starting with two books: The Design of Everyday Things by Don Norman, and UXใใถใคใณใฎๆ็งๆธ (The UX Design Textbook) by Masaya Andou.
While I work reading and writing Japanese all day (when I'm not slinging code), I haven't read a book in Japanese in ages. This is mostly because what I'm interested in ( technology, the web etc...) is usually written about in English well before Japanese. And since I can read English natively, it's natural for me to pick those books.
Starting to read UXใใถใคใณใฎๆ็งๆธ yesterday and the first thing that hit me is how much my Japanese has improved in the last 4 years. I used to struggle reading texts written for native-speakers as I had large gaps in my kanji recognition abilities. Gaps still exist. But now they're small enough that reading a single page doesn't take 20 minutes as I look up every 5th word. Only taken me...20 years(!) to come the far.
-
byJust a small update today. I shipped the login screen I shared yesterday and planned out what I need to build to support IndieAuth. I think I can use DRF to help handle token generation and authentication using the token.
-
byWith webmentions working, next up I want to tackle support for check-ins. Currently I'm using OwnYourSwarm to backfeed my checkins to my main site and I'd like to continue doing so with Tanzawa.
Doing so requires that I add support for IndieAuth (so I can login using just my domain) and Micropub. I'm starting on the IndieAuth implementation, which means I need to actually start with a login page for users to authenticate (thus far I've been using the django admin).
This is what it's looking like so far. Something feels off in the design of it, but I can't quite place my finger on what it is.The base login form -
A short list of things cooler than Clubhouse
byInspired by Seth's Chasing the cool kids, I've made a short list of things that are cooler than Clubhouse.
- Not caring what the "cool" kids are doing and doing your own thing.
- Building and learning about the systems that make the world go around.
- Not uploading your entire contact list to some random company so you can eavesdrop on the "cool" kids.
- Fighting for an open web so that you and future generations can access the world without gatekeepers.
- Owning your content.
The urgent advice usually ends with โblogs are dead"If you always have to mention that "blogs are dead", perhaps they aren't actually dead. They never were dead. They're just not "cool" anymore. The people who made blogging cool and fun? They're mostly still blogging.
Publish. Consistently. With patience. Own your assets. Donโt let a middleman be your landlord. Yell at Google for blocking your emails and hope itโll work eventually. Continually push for RSS and an open web. With patience.Yes.
-
byYesterday when I shipped sending webmentions I ran into an error that didn't occur in development. Posting the webmention would work, but my response would timeout when trying to save content.
When updating a post you're supposed to send webmentions, update your content, then send webmentions again. As I'm trying to keep server requirements as simple as possible I'm doing all of the sending inline.
Looking at my logs what appeared to be happening was everything would just lock, then once my timeout occurred, I'd see my initial post request to Tanzawa come through, the timeout, and then Wordpress making a request to Tanzawa to retrieve it's mention.
This was happening because gunicorn only has 2 workers by default, which wasn't enough to handle processing a long request simultaneously with an incoming request. Increasing the workers from 2 to 4 solved the issue. -
byToday marks 1 month since I got the first instance of Tanzawa live. ๐
Today I shipped support for sending webmentions. So now when I link to a post, Tanzawa will send webmentions. It's all done inline, so there's now a slow-down when saving. I should do it in the background, but I'm not sure I want to introduce redis/celery and all that complexity quite yet (or ever?).A webmention from Tanzawa
I have a table you can view in the admin where you can see which posts sent which webmentions and if they were successful or not.
I also shipped a small update to webmention receiving. When an existing webmention is updated, I now also update how the webmention is displayed. In the case of an update, the comment must be re-moderated.
The final small "quality-of-life" update is when I save a post I now show a link in the success message to view the post.Small quality of life update