Ask

Our logout links stopped working after a framework upgrade, logging out now needs a POST and I do not understand why

One thing to check after you convert: any place that redirects to logout, or links to it from an email, or has it in a bookmark.

Session-expiry handlers that redirect to the logout URL are the common one and they are easy to miss because they only fire on a path nobody tests. Same for a mobile app or a script hitting the endpoint.

Grep the whole codebase for the URL name rather than only the templates. The forms are the visible half of the change.

1 · in/fullstack ·

How do I get a dropdown into a form whose options depend on the current user or another field?

The correct place is the form's initialiser, and the pattern is standard enough that it is worth learning once and reusing forever.

The shape:

  1. The form's initialiser accepts an extra keyword argument: the user, the organisation, whatever the options depend on.
  2. It removes that argument before calling the parent initialiser, because the parent does not know about it and will reject it.
  3. It calls the parent.
  4. It then narrows the field's option source using the value it kept.

The view passes the extra argument when it constructs the form. That is the whole pattern.

Why the initialiser and not the class body. The class body runs once, when the module is imported - at startup, in a process that will serve thousands of requests for different users. Anything you evaluate there is shared across all of them. That is exactly why a class-level definition cannot be user-specific, and it is also the source of a related bug people hit: putting a call to "today's date" or a database query at class level and finding it frozen at process start.

The initialiser runs per form instance, which is per request. That is the right scope.

Why not in the view. You can assign the field's option source from the view and it works. But then the constraint lives outside the form, so any other view using that form silently has no constraint - and this is a security constraint. Keeping it in the form means the form is safe wherever it is used, and there is one place to audit.

Why definitely not in the template. Filtering the displayed options in the template changes only what is displayed. The form still accepts any value in the full set, so anyone can submit an option they were not shown and it validates. That is the data leak you were worried about, still fully present, now invisible.

This is the most important point here: narrowing the choices is a validation change, not a presentation change. The form must reject values outside the allowed set, and it only does that if the field's option source itself is narrowed.

A related case worth handling at the same time: a dropdown whose options depend on another field in the same form, pick a country, then a city. That cannot be done server-side in one render, because the first field's value is not known until the user chooses. The options are:

  • Two requests. The page reloads or fetches the dependent options when the first field changes. Simple and robust.
  • Send everything and filter client-side. Fine for small sets, and it leaks the whole set to the client, so never for anything access-controlled.

Either way, validate the combination on the server on submit. Whatever the browser was shown is not a constraint.

30 · in/fullstack ·

Verification fails with "jwt malformed", the token looks fine when I print it

Once it is fixed, make sure a malformed token produces a clean 401 rather than a 500.

The library throws, and if nothing catches it, an unauthenticated request with a junk header takes down the request with a server error and a stack trace. That is noise in your monitoring, it can leak internals in the response, and it means anyone can generate error-rate alerts by sending nonsense.

Catch the verification errors explicitly and map them to a 401 with a generic message. Distinguish expiry internally if you want to prompt a refresh, but do not tell the caller which of the checks failed.

14 · in/sessions-vs-jwt ·

Is adding an expensive tier nobody buys, so the middle one looks reasonable, a dark pattern or just pricing?

One practical observation from having watched this go wrong: a top tier that is wildly out of proportion can damage trust rather than anchor.

If the plans run 10, 30, 90 and then 2000, the last number does not make 90 look reasonable - it makes people wonder what the real price is and whether they are being played. The anchoring effect has a range beyond which it turns into suspicion.

In the cases I have seen, the useful top tier is somewhere around two to four times the target plan and is clearly aimed at an identifiable kind of customer. Beyond that you are not anchoring, you are performing.

12 · in/pricing-tiers ·

Nobody can agree on the wording for the "you have unsaved changes" dialog and I have rewritten it six times

Consider whether you need the dialog at all, because the best version of this problem is the one you deleted.

Two alternatives that remove it:

  • Autosave the draft. Closing then loses nothing; reopening restores what they typed. This is strictly better whenever it is feasible, and it turns an interruption into a non-event.
  • Make closing undoable. Close immediately, show a brief "Draft discarded - Undo" notice. The user is not blocked, the mistake is recoverable, and the common case, where they genuinely meant to close, costs them nothing.

Confirmation dialogs are a tax on every correct action to protect against an occasional incorrect one. When the incorrect one can be made recoverable instead, that is nearly always the better trade.

20 · in/onboarding-flow ·

Four lifts, all serving every floor, and everyone crowds into the same one: why does that happen?

There is a cheaper intervention worth knowing about, which is to stop all four cars serving all floors identically.

Splitting the bank - two cars serving lower floors, two serving upper, or one car reserved for the top floors and roof: reduces the average number of stops per trip without any new hardware or any new interface. It is why tall buildings have sky lobbies and express cars.

The trade is fairness: somebody's floor is now served by two lifts instead of four, and they will notice. In a residential building that is a political problem rather than an engineering one, which is often why it does not happen.

18 · in/curiosities ·