Validate on save, stricter settings, and more
Alright, I've been busy with some other projects and I didn't realize how many changes have piled up since the last update. I guess this is the benefit of using the framework myself — it's always moving forward! There are also some big changes in "security" and local dev, but I'll save those for the next update.
I'll try to be succinct... here's what we've got:
- Automatic validation on model save
- Stricter settings
- Time zones, always
plain.loginlink
- Template emails
- Borrowing the
dd
function - Switch from Poetry to UV
- Updates to
plain.vendor
- Next steps towards 1.0
Automatic validation on model save
I'm consistently surprised in Django when I save something manually, and later realize that it should have caused a ValidationError
.
There are plenty of good (?) reasons why Django works that way, but surprises in a framework feel like a bad thing, so I finally changed it!
Now when you save()
in Plain it will run full_clean()
by default.
x = MyModel.objects.get(id=1)
x.something = "changed!"
x.save()
And if you want to save without validation, you can do that too.
x = MyModel.objects.get(id=1)
x.something = "changed!"
x.save(clean_and_validate=False)
Personally, I find that using models this way is not a bad thing, and that forms (where validation usually runs) aren't always necessary.
Stricter settings
When you want to define a new setting in settings.py
, you have to prefix it with APP_
. This adds some clarity between settings that are part of installed packages and settings that are custom to your app.
# settings.py
INSTALLED_APPS = [
# ...
]
AUTH_USER_MODEL = "users.User"
AUTH_LOGIN_URL = "login"
# Custom settings
APP_GIT_SHA = os.environ.get("HEROKU_SLUG_COMMIT", "dev")
APP_OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
Not only that, but now Plain can also tell the difference too! If a package is updated and a setting was removed or renamed, you'll immediately know it.
When you add this to the required and type-checked settings, Plain can actually do a lot to validate your settings at startup.
Time zones, always
The USE_TZ
setting has been removed. It just seemed like there wasn't much reason to set this to False
in a new app. Bad idea? Time will tell.
plain.loginlink
I was working on a project that needed "magic link" login, and decided to rough out a package for it. One of the visions for Plain is to separate packages per authentication method, so you can pick and choose the ones you actually need.
Plain now has:
plain.auth
(the base package)plain.passwords
plain.oauth
plain.loginlink
And I expect to add:
plain.totp
plain.passkeys
Template emails
For years I've been copying and pasting a class that rendered HTML emails (and optionally plaintext) from templates. It's simple, but I decided to formalize this pattern in plain.mail
.
from plain.mail import TemplateEmail
TemplateEmail(
template="loginlink",
subject="Your link to log in",
to=["[email protected]"],
context={"link": "https://..."},
).send()
<!-- templates/mail/loginlink.html -->
<p>Click <a href="{{ link }}">here</a> to log in.</p>
Now packages can use a consistent pattern, giving users the option to customize the email templates. (And you can use it yourself too.)
Borrowing the dd
function
One downside of using a honcho-style all-in-one dev command is that it's harder to simply drop in a import pdb; pdb.set_trace()
to debug, or even find a print()
if there's a lot of output.
I've always liked the dd
function in Laravel and I thought it'd be interesting to just try it out.
(ResponseException
makes this possible, where you can "abort" a request at any time, regardless of where you are in the stack.)
Switch from Poetry to UV
The starter kits now use uv. Honestly, uv is changing so fast that it feels early to make this a default, but it's the direction I hope the broader ecosystem goes.
Updates to plain.vendor
The plain.vendor
package "vendors" CSS and JS files, typically from a CDN. This has existed for a while too, but I added support for sourcemaps and reworked the CLI a bit. More in this tweet if you're interested: https://x.com/davegaeddert/status/1834288804246814963
Next steps towards 1.0
As much as I'd like Plain to get some traction beyond myself, I'm not in a huge rush to get to 1.0 just yet. I'm still enjoying the pre-1.0 phase where I can make significant changes without seriously impacting anyone but myself. That said, the list of blocking changes is getting shorter and shorter, so we'll see how it goes!