Syllabus Lesson 76 of 156 · Privacy by design
Privacy by design

Collect less

Why it matters Data you never collect cannot leak, cannot be stolen, cannot be misused later and never needs deleting. The cheapest privacy work on any site is a shorter form. In this exercise you trim a real-looking sign-up form down to what the product actually uses.

When you rent a bike, the shop needs your phone number in case you vanish with the bike. It does not need your birthday, your job or your mother's maiden name. A form that asks for those is not being thorough; it is collecting things that can only cause trouble.

Data minimisation: collecting only the data a feature really needs, keeping it only as long as it needs it, and storing the answer instead of the raw data when the answer is enough.

Three questions for every field

  1. Which feature uses it? If you cannot name one the product has today, drop the field. "Might be useful one day" is not a feature.
  2. Does it need to be required? A delivery phone number can be optional. Required fields push people to type fake data, which helps nobody.
  3. Can you keep the answer instead? To check that someone is old enough, you need a yes or no, not their date of birth. Work out the answer at sign-up and store only that.

Working with dates in Python

Dates in forms usually arrive as text like "2009-03-14" (year, month, day). The standard library turns them into date objects you can compare:

from datetime import date
born = date.fromisoformat("2009-03-14")
today = date.fromisoformat("2026-10-30")
print(born.year, born.month, born.day)   # 2009 3 14

Age in whole years is the difference in years, minus one if this year's birthday has not happened yet. Comparing (month, day) pairs does that check neatly, because Python compares tuples item by item:

had_birthday = (today.month, today.day) >= (born.month, born.day)
age = today.year - born.year - (0 if had_birthday else 1)
print(age)   # 17

Python treats True as 1 and False as 0 in arithmetic, so today.year - born.year - ((today.month, today.day) < (born.month, born.day)) is the same thing in one line.

A common misconception: "we can hash the birth date to make it safe". There are only about 36,500 dates in a century, so a computer can hash every one and match yours in a blink. You saw this with hashed emails in Leg 2. If you only need "old enough", keep only that.

In other languages
// JavaScript
function isOldEnough(birth, today, minAge) {
  const b = new Date(birth), t = new Date(today);
  let age = t.getUTCFullYear() - b.getUTCFullYear();
  if (t.getUTCMonth() < b.getUTCMonth() ||
      (t.getUTCMonth() === b.getUTCMonth() && t.getUTCDate() < b.getUTCDate())) age--;
  return age >= minAge;
}
// C#
int age = today.Year - born.Year;
if (today < born.AddYears(age)) age--;
bool oldEnough = age >= minAge;
# Django: keep only the answer
class Account(models.Model):
    email = models.EmailField(unique=True)
    old_enough = models.BooleanField()   # no birth_date column at all

Recap

  • Every field needs a feature the product has today; otherwise drop it.
  • Store the answer (old enough: yes) instead of the raw data (the birth date) when the answer is enough.
  • Hashing small sets such as dates does not protect them.
Your turn

ShopD Kitchen sells online cooking classes. Its sign-up form, FIELDS, has eight fields; each says which features would use it ("needed_by"). The product really has only the features in FEATURES, and classes are for people aged MIN_AGE or over. Write three functions:

  1. keep_fields(fields, features): return the list of field "name"s, in the original order, whose "needed_by" list shares at least one feature with features.
  2. is_old_enough(birth_date, today, min_age): both dates are text like "2009-03-14". Return True if the person's age in whole years on today is at least min_age (someone whose birthday is today has just turned the new age).
  3. clean_signup(form, kept, today, min_age): form is a dict of what someone typed. Return a new dict with only the keys that are in kept, except that "birth_date" is never stored: if it is kept, store "old_enough" (the result of is_old_enough) in its place.
Print the kept field names. Tests use other forms, features and dates.

Spotted a problem in this lesson? Report it

Code · runs in your browser
Output