Harry Khanna
Khanna Law
Note type
Random

Today I Learned

When I learn a random, useful thing I write it down. It's usually tweet-length and captures the insight I gleaned that day. I group them by year.

2022

  • Peculiarities about django-hosts.
    • The hosts file identified by ROOT_HOSTCONF is used to match an incoming request.
    • If there is a matching hostname, it will use the urlconf specified in the ROOT_HOSTCONF file.
    • If there is no matching hostname, it will use the host specified by DEFAULT_HOSTfor the incoming request.
    • When reverse resolving a url name using {% host_url %} tags or reverse from from django_hosts import reverse, if you don’t pass a host= parameter, it will default to whatever is in DEFAULT_HOST.
    • I don’t believe ROOT_URLCONF is used for anything, although it is still required for some reason, and I think the django-hosts documentation is out of date.
    • A blank regex will match nothing, so you must use \w* for matching nothing (or anything). Otherwise, it will never match incoming requests.
  • The POST/redirect/GET pattern in general recommends that you 302 GET redirect after a POST to avoid browsers attempting to submit forms twice.
    • There's an issue that I can't seem to find an authoritative answer to: Unsuccessful POSTs, like in the case of a form that fails validation on the server side. If you redirect to a GET after an unsuccessful POST, you lose the submitted information in the form that was invalid and then you can't show the errors to the user.
    • Googling around suggests that the POST/Redirect/GET pattern should only be used on successful POSTs, the thinking being that unsuccessful POSTs shouldn't mutate data anyway and so they are harmless.
    • However, I don't think that's right. If the validity of a form submission can change based on factors outside that same form, then a user clicking the back button might accidentally save and overwrite the information on that form inadvertently.
    • An example: you have a form where you can submit restaurants, descriptions, and the date you visited. If the date is in the future, the form is invalid. But if the date occurs and you click the back button, it will successfully submit the form, even though it was invalid the first time you submitted that. That's unexpected, and if you submitted the restaurant with a description in a different tab or device, it will overwrite your second submission with your first one, potentially without even warning you.
    • A solution for invalid forms: always redirect on POST, no matter what, successful or not. For invalid forms, save the form to the user's session in some way and the redirected GET view should rehydrate it and display it to the user. This isn't the most elegant solution I can think of, but it's the only one I can think of that doesn't have obvious drawbacks.
    • You can probably get away with just rendering and not redirecting on POST if (1) the POST "fails", by which I mean that the database isn't mutated and there aren't any side effects, and (2) you are very sure that the POST can't suddenly succeed in the future due to actions taken outside of the specific user intention associated with the failing POST request. This can be tricky and not totally obvious!
      • For example, you POST to an endpoint that has side effects like sending emails, but the POST fails because some form in another part of the app is invalid. The app helpfully renders the form in its invalid state and tells you to fix it. You update that form to make it valid and submit it so its now in a valid state. But you haven't yet decided to re-trigger the side effect from the initial POST. You click the back button, and accidentally trigger it! The action of updating the form is different than the action of the user to trigger the POST that has the side effect that sends the email.
      • If, on the other hand, the actions are one in the same -- i.e., it's the valid submission of the form that triggers the side effect -- you can probably get away without the PRG pattern.

2021

  • To install Office 365 on Linux with Crossover Office, you have to download the "offline installer" via the My Account page on account.microsoft.com. But if you're on Linux, it won't let you download it. It keeps telling you to refresh the page. You can get around this by spoofing your User Agent in your browser with a browser extension to make it look like you're on Microsoft Windows. Once you have the .iso file, mount it. The Crossover installer should then be able to see it and install from it. I got to the end of the install and it wouldn't work, so I abandoned it. Will use LibreOffice for now.

How a Django Form chooses what to display in its fields.

  • In ModelForm, initial is constructed by taking the instances' dictionarified attributes and overriding them with the initial provided.
  • If you pass data, on cleaning, it puts the field cleaned data in form.cleaned_data, but this is actually irrelevant for form rendering purposes, but ModelForm.save will save the cleaned_data to the instances' fields.
  • Iterating over a form's fields actually yields a BoundField instance because of how Field.__getitem__ is written. I think BoundField is kind of a misnomer and it should really be named BindableField since it is yielded even if the form is not bound with any data.
  • Rendering a form field renders a BoundField.data if the Form is bound, otherwise it renders BoundField.initial.
  • The key is where BoundField gets initial and data.
    • BoundField gets initial by reaching back into the form instance (which it gets in its __init__).
    • BoundField gets data by reaching back into the form instance in the following way: BoundField passes a copy of the widget to a function on the form, which then reaches into the widget with the form's data to pull out the data value sitting on the widget.
  • This whole scheme works because when you iterate into a form, it instantiates BoundField instances with 3 arguments: the form instance itself, the field instance, and the name of variable the field instance is assigned to in the model.
    • E.g., ice_cream = forms.CharField() would provide the parent form, the instance of CharField() and the string ice_cream.
  • That BoundField instance -- and even the Field instances -- don't actually know anything about the data represented by the Field or BoundField. BoundField just contains some functions for reaching back into the Form to see if any data is sitting on a widget or in initial, depending on if the form is bound or not.

2020

  • There appears to be a bug in Google Analytics 4 where linked analytics.js properties don't properly receive pageview events if your GA tracking ID is the GA4 id. The flip side works fine though: If your GA tracking ID is the UA-XXX-type ID, linked GA 4 properties receive pageview events fine.
  • When you are trying to JOIN models in Django that are not related via the ORM, you have to use a Subquery and OuterRef and can only join one column at a time. #django
  • When telling a story, a useful tactic is to describe "how did I feel then?" and "how do I feel now?" #storytelling
  • When telling a story, give us a quirk of each character in a story. For example, if I were telling a story about my friend Jared, I would comment on his fascination with law firm bathrooms. #storytelling
  • usePaginatedQuery is the same as useQuery but status is always success after the first load and resolvedData and latestData take the place of data. #reactjs
  • A ManytoMany relationship in Django will not allow duplicate relations. I.e., the same exact relation can't appear twice in the join table. If you use Model.add() to add the same relation twice, it will just ignore it and not duplicate the relation. #django
  • In Django Rest Framework, rest_framework.exceptions.ValidationError === rest_framework.serializers.ValidationError. If you raise the error in a serializer, you get the non_field_error/field formulation. If you raise it outside, you just get whatever you raised. According to inline comments in DRF, you should actually always reference serializers.ValidationError to avoid confusion with the django named ValidationError. #django
  • Django Rest Framework Serializers
    • Serializer(data=XXX) used to run validation on writable fields and pass a .validated_data to .create() of those writable fields. That same .validated_data gets run back through to_representation to get a .data to return back to the browser.
    • Serializer(instance) does not validate and should be used when (1) your fetching an instance from the db and returning it serialized or (2) you just need to normalize an object into a different representation (e.g., converting account_ids from a pure python object to account slugs).
    • Passing both instance and data to Serializer is for PUT/update operations.
    • .validated_data is generated from to_internal_value. .data is generated from to_representation.