From 6c15e9369be3339ee300d7eee626b2deebf13206 Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Sun, 13 Sep 2020 10:14:06 +0300
Subject: [PATCH 01/21] chore: initialize KE localflavor

---
 localflavor/ke/__init__.py | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 localflavor/ke/__init__.py

diff --git a/localflavor/ke/__init__.py b/localflavor/ke/__init__.py
new file mode 100644
index 000000000..e69de29bb

From 77ce9d113d224fcd9b07d238288ce9f71d4809bc Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Sun, 13 Sep 2020 10:14:23 +0300
Subject: [PATCH 02/21] chore: initialize KE county choices

---
 localflavor/ke/ke_counties.py | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 localflavor/ke/ke_counties.py

diff --git a/localflavor/ke/ke_counties.py b/localflavor/ke/ke_counties.py
new file mode 100644
index 000000000..e69de29bb

From 3ec98ec403237617134819e7fcbf4aa5a9eeef56 Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Sun, 13 Sep 2020 10:46:49 +0300
Subject: [PATCH 03/21] chore: define county choices

---
 localflavor/ke/ke_counties.py | 55 +++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/localflavor/ke/ke_counties.py b/localflavor/ke/ke_counties.py
index e69de29bb..e94e1ea25 100644
--- a/localflavor/ke/ke_counties.py
+++ b/localflavor/ke/ke_counties.py
@@ -0,0 +1,55 @@
+"""
+Kenya Counties Data
+"""
+
+from django.utils.translation import gettext_lazy as _
+
+COUNTY_CHOICES= (
+    ('MOMBASA', _('MOMBASA')),
+    ('KWALE', _('KWALE')),
+    ('KILIFI', _('KILIFI')),
+    ('TANA RIVER', _('TANA RIVER')),
+    ('LAMU', _('LAMU')),
+    ('TAITA-TAVETA', _('TAITA-TAVETA')),
+    ('GARISSA', _('GARISSA')),
+    ('WAJIR', _('WAJIR')),
+    ('MANDERA', _('MANDERA')),
+    ('MARSABIT', _('MARSABIT')),
+    ('ISIOLO', _('ISIOLO')),
+    ('MERU', _('MERU')),
+    ('THARAKA-NITHI', _('THARAKA-NITHI')),
+    ('EMBU', _('EMBU')),
+    ('KITUI', _('KITUI')),
+    ('MACHAKOS', _('MACHAKOS')),
+    ('MAKUENI', _('MAKUENI')),
+    ('NYANDARUA', _('NYANDARUA')),
+    ('NYERI', _('NYERI')),
+    ('KIRINYAGA', _('KIRINYAGA')),
+    ('MURANGA', _('MURANGA')),
+    ('KIAMBU', _('KIAMBU')),
+    ('TURKANA', _('TURKANA')),
+    ('WEST POKOT', _('WEST POKOT')),
+    ('SAMBURU', _('SAMBURU')),
+    ('TRANS-NZOIA', _('TRANS-NZOIA')),
+    ('UASIN GISHU', _('UASIN GISHU')),
+    ('ELGEYO-MARAKWET', _('ELGEYO-MARAKWET')),
+    ('NANDI', _('NANDI')),
+    ('BARINGO', _('BARINGO')),
+    ('LAIKIPIA', _('LAIKIPIA')),
+    ('NAKURU', _('NAKURU')),
+    ('NAROK', _('NAROK')),
+    ('KAJIADO', _('KAJIADO')),
+    ('KERICHO', _('KERICHO')),
+    ('BOMET', _('BOMET')),
+    ('KAKAMEGA', _('KAKAMEGA')),
+    ('VIHIGA', _('VIHIGA')),
+    ('BUNGOMA', _('BUNGOMA')),
+    ('BUSIA', _('BUSIA')),
+    ('SIAYA', _('SIAYA')),
+    ('KISUMU', _('KISUMU')),
+    ('HOMA BAY', _('HOMA BAY')),
+    ('MIGORI', _('MIGORI')),
+    ('KISII', _('KISII')),
+    ('NYAMIRA', _('NYAMIRA')),
+    ('NAIROBI', _('NAIROBI')),
+)

From 1f1ebb2a9d441a465dc57c05d964bbfed7e6d7d5 Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Sun, 29 Nov 2020 21:56:10 +0300
Subject: [PATCH 04/21] chore: map out TODO forms and select widgets

---
 localflavor/ke/forms.py | 86 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)
 create mode 100644 localflavor/ke/forms.py

diff --git a/localflavor/ke/forms.py b/localflavor/ke/forms.py
new file mode 100644
index 000000000..b8757c1cc
--- /dev/null
+++ b/localflavor/ke/forms.py
@@ -0,0 +1,86 @@
+"""Kenya-specific Form Helpers"""
+
+from django.forms import ValidationError
+from django.forms.fields import RegexField, Select
+from django.utils.translation import gettext_lazy as _
+
+from .ke_counties import COUNTY_CHOICES
+
+
+class KEPostalCodeField(RegexField):
+    """
+    A form field that validates its input as a Kenyan Postal Code.
+
+    """
+    default_error_messages = {
+        "invalid":_("Enter a valid Postal code in the format XXXXX")
+    }
+
+    def __init__(self, **kwargs) -> None:
+        """
+        TODO
+        """
+        super().__init__(r'()', **kwargs)
+
+
+class KEKraPinNumber(RegexField):
+    """
+    TODO 
+
+    Kenya Revenue Authority PIN Number
+
+    Validates 2 different formats:
+
+        POXXXXXXXX - Company/Institutions
+
+        AXXXXXXXXX - Individuals
+    """
+    ...
+
+
+class KEIDNumber(RegexField):
+    """
+    TODO
+
+    Kenya National ID Number
+
+    """
+    ...
+
+class KEPassportNumber(RegexField):
+    """
+    TODO
+
+    Kenya Passport Number
+    """
+    ...
+
+
+class KENSSFNumber(RegexField):
+    """
+    TODO
+
+    Kenya National Social Security Fund
+    """
+    ...
+
+
+class KENHIFNumber(RegexField):
+    """
+    TODO
+
+    Kenya National Hospital Insurance Fund 
+    """
+    ...
+
+class KECompanyRegNumber(RegexField):
+    """
+    Kenya Companies Reg. Number
+    """
+
+class KECountySelect(Select):
+    """
+    A Select widget listing Kenyan Counties as the choices
+    """
+    def __init__(self, attrs=None) -> None:
+        super().__init__(attrs, choices=COUNTY_CHOICES)
\ No newline at end of file

From c278b2a40cb7ee9e35fa8e97c03632be58a5cb96 Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Sun, 29 Nov 2020 22:08:46 +0300
Subject: [PATCH 05/21] chore: update KE form fields

---
 localflavor/ke/forms.py | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/localflavor/ke/forms.py b/localflavor/ke/forms.py
index b8757c1cc..16cd5c47d 100644
--- a/localflavor/ke/forms.py
+++ b/localflavor/ke/forms.py
@@ -12,20 +12,21 @@ class KEPostalCodeField(RegexField):
     A form field that validates its input as a Kenyan Postal Code.
 
     """
+
     default_error_messages = {
-        "invalid":_("Enter a valid Postal code in the format XXXXX")
+        "invalid": _("Enter a valid Postal code in the format XXXXX")
     }
 
     def __init__(self, **kwargs) -> None:
         """
         TODO
         """
-        super().__init__(r'()', **kwargs)
+        super().__init__(r"()", **kwargs)
 
 
 class KEKraPinNumber(RegexField):
     """
-    TODO 
+    TODO
 
     Kenya Revenue Authority PIN Number
 
@@ -35,6 +36,7 @@ class KEKraPinNumber(RegexField):
 
         AXXXXXXXXX - Individuals
     """
+
     ...
 
 
@@ -45,14 +47,17 @@ class KEIDNumber(RegexField):
     Kenya National ID Number
 
     """
+
     ...
 
+
 class KEPassportNumber(RegexField):
     """
     TODO
 
     Kenya Passport Number
     """
+
     ...
 
 
@@ -62,6 +67,7 @@ class KENSSFNumber(RegexField):
 
     Kenya National Social Security Fund
     """
+
     ...
 
 
@@ -69,18 +75,22 @@ class KENHIFNumber(RegexField):
     """
     TODO
 
-    Kenya National Hospital Insurance Fund 
+    Kenya National Hospital Insurance Fund
     """
+
     ...
 
+
 class KECompanyRegNumber(RegexField):
     """
     Kenya Companies Reg. Number
     """
 
+
 class KECountySelect(Select):
     """
     A Select widget listing Kenyan Counties as the choices
     """
+
     def __init__(self, attrs=None) -> None:
         super().__init__(attrs, choices=COUNTY_CHOICES)
\ No newline at end of file

From 4fa5cef3817f0a81b05c8f4b2bd3ca1692fa76e4 Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Sun, 29 Nov 2020 22:09:24 +0300
Subject: [PATCH 06/21] chore: format KE Counties Info

---
 localflavor/ke/ke_counties.py | 96 +++++++++++++++++------------------
 1 file changed, 48 insertions(+), 48 deletions(-)

diff --git a/localflavor/ke/ke_counties.py b/localflavor/ke/ke_counties.py
index e94e1ea25..bfe326302 100644
--- a/localflavor/ke/ke_counties.py
+++ b/localflavor/ke/ke_counties.py
@@ -4,52 +4,52 @@
 
 from django.utils.translation import gettext_lazy as _
 
-COUNTY_CHOICES= (
-    ('MOMBASA', _('MOMBASA')),
-    ('KWALE', _('KWALE')),
-    ('KILIFI', _('KILIFI')),
-    ('TANA RIVER', _('TANA RIVER')),
-    ('LAMU', _('LAMU')),
-    ('TAITA-TAVETA', _('TAITA-TAVETA')),
-    ('GARISSA', _('GARISSA')),
-    ('WAJIR', _('WAJIR')),
-    ('MANDERA', _('MANDERA')),
-    ('MARSABIT', _('MARSABIT')),
-    ('ISIOLO', _('ISIOLO')),
-    ('MERU', _('MERU')),
-    ('THARAKA-NITHI', _('THARAKA-NITHI')),
-    ('EMBU', _('EMBU')),
-    ('KITUI', _('KITUI')),
-    ('MACHAKOS', _('MACHAKOS')),
-    ('MAKUENI', _('MAKUENI')),
-    ('NYANDARUA', _('NYANDARUA')),
-    ('NYERI', _('NYERI')),
-    ('KIRINYAGA', _('KIRINYAGA')),
-    ('MURANGA', _('MURANGA')),
-    ('KIAMBU', _('KIAMBU')),
-    ('TURKANA', _('TURKANA')),
-    ('WEST POKOT', _('WEST POKOT')),
-    ('SAMBURU', _('SAMBURU')),
-    ('TRANS-NZOIA', _('TRANS-NZOIA')),
-    ('UASIN GISHU', _('UASIN GISHU')),
-    ('ELGEYO-MARAKWET', _('ELGEYO-MARAKWET')),
-    ('NANDI', _('NANDI')),
-    ('BARINGO', _('BARINGO')),
-    ('LAIKIPIA', _('LAIKIPIA')),
-    ('NAKURU', _('NAKURU')),
-    ('NAROK', _('NAROK')),
-    ('KAJIADO', _('KAJIADO')),
-    ('KERICHO', _('KERICHO')),
-    ('BOMET', _('BOMET')),
-    ('KAKAMEGA', _('KAKAMEGA')),
-    ('VIHIGA', _('VIHIGA')),
-    ('BUNGOMA', _('BUNGOMA')),
-    ('BUSIA', _('BUSIA')),
-    ('SIAYA', _('SIAYA')),
-    ('KISUMU', _('KISUMU')),
-    ('HOMA BAY', _('HOMA BAY')),
-    ('MIGORI', _('MIGORI')),
-    ('KISII', _('KISII')),
-    ('NYAMIRA', _('NYAMIRA')),
-    ('NAIROBI', _('NAIROBI')),
+COUNTY_CHOICES = (
+    ("MOMBASA", _("MOMBASA")),
+    ("KWALE", _("KWALE")),
+    ("KILIFI", _("KILIFI")),
+    ("TANA RIVER", _("TANA RIVER")),
+    ("LAMU", _("LAMU")),
+    ("TAITA-TAVETA", _("TAITA-TAVETA")),
+    ("GARISSA", _("GARISSA")),
+    ("WAJIR", _("WAJIR")),
+    ("MANDERA", _("MANDERA")),
+    ("MARSABIT", _("MARSABIT")),
+    ("ISIOLO", _("ISIOLO")),
+    ("MERU", _("MERU")),
+    ("THARAKA-NITHI", _("THARAKA-NITHI")),
+    ("EMBU", _("EMBU")),
+    ("KITUI", _("KITUI")),
+    ("MACHAKOS", _("MACHAKOS")),
+    ("MAKUENI", _("MAKUENI")),
+    ("NYANDARUA", _("NYANDARUA")),
+    ("NYERI", _("NYERI")),
+    ("KIRINYAGA", _("KIRINYAGA")),
+    ("MURANGA", _("MURANGA")),
+    ("KIAMBU", _("KIAMBU")),
+    ("TURKANA", _("TURKANA")),
+    ("WEST POKOT", _("WEST POKOT")),
+    ("SAMBURU", _("SAMBURU")),
+    ("TRANS-NZOIA", _("TRANS-NZOIA")),
+    ("UASIN GISHU", _("UASIN GISHU")),
+    ("ELGEYO-MARAKWET", _("ELGEYO-MARAKWET")),
+    ("NANDI", _("NANDI")),
+    ("BARINGO", _("BARINGO")),
+    ("LAIKIPIA", _("LAIKIPIA")),
+    ("NAKURU", _("NAKURU")),
+    ("NAROK", _("NAROK")),
+    ("KAJIADO", _("KAJIADO")),
+    ("KERICHO", _("KERICHO")),
+    ("BOMET", _("BOMET")),
+    ("KAKAMEGA", _("KAKAMEGA")),
+    ("VIHIGA", _("VIHIGA")),
+    ("BUNGOMA", _("BUNGOMA")),
+    ("BUSIA", _("BUSIA")),
+    ("SIAYA", _("SIAYA")),
+    ("KISUMU", _("KISUMU")),
+    ("HOMA BAY", _("HOMA BAY")),
+    ("MIGORI", _("MIGORI")),
+    ("KISII", _("KISII")),
+    ("NYAMIRA", _("NYAMIRA")),
+    ("NAIROBI", _("NAIROBI")),
 )

From b1240506cb8be4efd4b113852426eded16a52657 Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Sun, 29 Nov 2020 22:09:42 +0300
Subject: [PATCH 07/21] chore: add KE Models

---
 localflavor/ke/models.py | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)
 create mode 100644 localflavor/ke/models.py

diff --git a/localflavor/ke/models.py b/localflavor/ke/models.py
new file mode 100644
index 000000000..2252822c0
--- /dev/null
+++ b/localflavor/ke/models.py
@@ -0,0 +1,33 @@
+from typing import Any
+from django.db.models import CharField
+from django.utils.translation import gettext_lazy as _
+
+from .forms import (
+    KECompanyRegNumber as KECompanyRegNumberFormField,
+    KECountySelect,
+    KEIDNumber,
+    KEKraPinNumber,
+    KENHIFNumber,
+    KENSSFNumber,
+    KEPassportNumber,
+    KEPostalCodeField as KEPostalCodeFormField,
+)
+
+
+class KEPostalCodeField(CharField):
+    """
+    A model field that stores the Kenyan Postal Codes
+    """
+    description = _("Kenya Postal Code")
+
+    def __init__(self, *args, **kwargs) -> None:
+        kwargs.update(max_length=8)
+        super().__init__(*args, **kwargs)
+
+    def formfield(self, **kwargs) -> Any:
+        defaults = {"form_class": KEPostalCodeFormField}
+        defaults.update(kwargs)
+        return super().formfield(**defaults)
+
+        
+        
\ No newline at end of file

From dfb0a47b609a548b6b28ec7087d45782ece75ebe Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Sun, 29 Nov 2020 22:15:07 +0300
Subject: [PATCH 08/21] chore: add Mpesa Paybill

---
 localflavor/ke/forms.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/localflavor/ke/forms.py b/localflavor/ke/forms.py
index 16cd5c47d..fd6bca1b6 100644
--- a/localflavor/ke/forms.py
+++ b/localflavor/ke/forms.py
@@ -85,7 +85,13 @@ class KECompanyRegNumber(RegexField):
     """
     Kenya Companies Reg. Number
     """
+    ...
 
+class KEPayBillNumber(RegexField):
+    """
+    MPESA PayBill 
+    """
+    ...
 
 class KECountySelect(Select):
     """

From 8c25e12ed97977df97e0504c42eb646cb2d9db47 Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Thu, 7 Jan 2021 09:20:57 +0300
Subject: [PATCH 09/21] feat: add regex to validate postal code and ID number

---
 localflavor/ke/forms.py | 53 +++++++++++++++++++++++++++++++++--------
 1 file changed, 43 insertions(+), 10 deletions(-)

diff --git a/localflavor/ke/forms.py b/localflavor/ke/forms.py
index fd6bca1b6..c1518d2d9 100644
--- a/localflavor/ke/forms.py
+++ b/localflavor/ke/forms.py
@@ -1,11 +1,15 @@
 """Kenya-specific Form Helpers"""
 
+import re
+
 from django.forms import ValidationError
 from django.forms.fields import RegexField, Select
 from django.utils.translation import gettext_lazy as _
 
 from .ke_counties import COUNTY_CHOICES
 
+ke_id_re = re.compile(r"^\d{7}(?:\d{1})?$")
+ke_po_box_re = re.compile(r"\A\d{5,5}\Z")
 
 class KEPostalCodeField(RegexField):
     """
@@ -14,21 +18,29 @@ class KEPostalCodeField(RegexField):
     """
 
     default_error_messages = {
-        "invalid": _("Enter a valid Postal code in the format XXXXX")
+        "invalid": _("Enter a valid Kenyan Postal code in the format 12345")
     }
 
-    def __init__(self, **kwargs) -> None:
-        """
-        TODO
-        """
-        super().__init__(r"()", **kwargs)
+    def clean(self, value):
+        value = super().clean(value)
+        if value in self.empty_values:
+            return self.empty_value
+
+        # Strip out spaces and dashes
+        value = value.replace(" ", "").replace("-", "")
+        match = re.match(ke_po_box_re, value)
+        if not match:
+            raise ValidationError(self.error_messages.get("invalid"))
+        return value
+
+
 
 
 class KEKraPinNumber(RegexField):
     """
     TODO
 
-    Kenya Revenue Authority PIN Number
+    A form field that validates input as a Kenya Revenue Authority PIN Number
 
     Validates 2 different formats:
 
@@ -46,9 +58,26 @@ class KEIDNumber(RegexField):
 
     Kenya National ID Number
 
+    8 Digits
+
     """
 
-    ...
+    default_error_messages = {
+        "invalid": _("Enter a valid Kenyan ID Number"),
+    }
+
+    def clean(self, value):
+        value = super().clean(value)
+        if value in self.empty_values:
+            return self.empty_value
+
+        # Strip out spaces and dashes
+        value = value.replace(" ", "").replace("-", "")
+        match = re.match(ke_id_re, value)
+
+        if not match:
+            raise ValidationError(self.error_messages.get("invalid"))
+        return value
 
 
 class KEPassportNumber(RegexField):
@@ -85,18 +114,22 @@ class KECompanyRegNumber(RegexField):
     """
     Kenya Companies Reg. Number
     """
+
     ...
 
+
 class KEPayBillNumber(RegexField):
     """
-    MPESA PayBill 
+    MPESA PayBill
     """
+
     ...
 
+
 class KECountySelect(Select):
     """
     A Select widget listing Kenyan Counties as the choices
     """
 
     def __init__(self, attrs=None) -> None:
-        super().__init__(attrs, choices=COUNTY_CHOICES)
\ No newline at end of file
+        super().__init__(attrs, choices=COUNTY_CHOICES)

From c83b235d6eccc984496da36417de79f8087fdfc0 Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Thu, 7 Jan 2021 09:21:21 +0300
Subject: [PATCH 10/21] chore: add deprecation notice

---
 localflavor/ke/deprecation.py | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 localflavor/ke/deprecation.py

diff --git a/localflavor/ke/deprecation.py b/localflavor/ke/deprecation.py
new file mode 100644
index 000000000..fb80ff5f3
--- /dev/null
+++ b/localflavor/ke/deprecation.py
@@ -0,0 +1,2 @@
+class RemovedInLocalflavor30Warning(PendingDeprecationWarning):
+    ...
\ No newline at end of file

From 9556f2ab198aaef158bb57df7d5b2b11a0fcaa2f Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Fri, 8 Jan 2021 13:59:42 +0300
Subject: [PATCH 11/21] chore: update KE forms

---
 localflavor/ke/forms.py | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/localflavor/ke/forms.py b/localflavor/ke/forms.py
index c1518d2d9..4cbdc2427 100644
--- a/localflavor/ke/forms.py
+++ b/localflavor/ke/forms.py
@@ -3,7 +3,7 @@
 import re
 
 from django.forms import ValidationError
-from django.forms.fields import RegexField, Select
+from django.forms.fields import CharField, RegexField, Select
 from django.utils.translation import gettext_lazy as _
 
 from .ke_counties import COUNTY_CHOICES
@@ -11,7 +11,7 @@
 ke_id_re = re.compile(r"^\d{7}(?:\d{1})?$")
 ke_po_box_re = re.compile(r"\A\d{5,5}\Z")
 
-class KEPostalCodeField(RegexField):
+class KEPostalCodeField(CharField):
     """
     A form field that validates its input as a Kenyan Postal Code.
 
@@ -36,7 +36,7 @@ def clean(self, value):
 
 
 
-class KEKraPinNumber(RegexField):
+class KEKraPinNumberField(CharField):
     """
     TODO
 
@@ -52,7 +52,7 @@ class KEKraPinNumber(RegexField):
     ...
 
 
-class KEIDNumber(RegexField):
+class KEIDNumberField(CharField):
     """
     TODO
 
@@ -80,7 +80,7 @@ def clean(self, value):
         return value
 
 
-class KEPassportNumber(RegexField):
+class KEPassportNumberField(RegexField):
     """
     TODO
 
@@ -90,7 +90,7 @@ class KEPassportNumber(RegexField):
     ...
 
 
-class KENSSFNumber(RegexField):
+class KENSSFNumberField(RegexField):
     """
     TODO
 
@@ -100,7 +100,7 @@ class KENSSFNumber(RegexField):
     ...
 
 
-class KENHIFNumber(RegexField):
+class KENHIFNumberField(RegexField):
     """
     TODO
 
@@ -110,7 +110,7 @@ class KENHIFNumber(RegexField):
     ...
 
 
-class KECompanyRegNumber(RegexField):
+class KECompanyRegNumberField(RegexField):
     """
     Kenya Companies Reg. Number
     """
@@ -126,7 +126,7 @@ class KEPayBillNumber(RegexField):
     ...
 
 
-class KECountySelect(Select):
+class KECountySelectField(Select):
     """
     A Select widget listing Kenyan Counties as the choices
     """

From cfa3713faf40e23c00f10834b9b2de8a8b72239a Mon Sep 17 00:00:00 2001
From: chmodx <chmod777.ke@gmail.com>
Date: Fri, 8 Jan 2021 13:59:59 +0300
Subject: [PATCH 12/21] chore: update ke model

---
 localflavor/ke/models.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/localflavor/ke/models.py b/localflavor/ke/models.py
index 2252822c0..516776269 100644
--- a/localflavor/ke/models.py
+++ b/localflavor/ke/models.py
@@ -5,11 +5,11 @@
 from .forms import (
     KECompanyRegNumber as KECompanyRegNumberFormField,
     KECountySelect,
-    KEIDNumber,
-    KEKraPinNumber,
-    KENHIFNumber,
-    KENSSFNumber,
-    KEPassportNumber,
+    KEIDNumberField,
+    KEKraPinNumberField,
+    KENHIFNumberField,
+    KENSSFNumberField,
+    KEPassportNumberField,
     KEPostalCodeField as KEPostalCodeFormField,
 )
 

From c15ebd9d45a00d79e7debde3177d69e07d8954a2 Mon Sep 17 00:00:00 2001
From: chmod77 <chmod777.ke@gmail.com>
Date: Sun, 26 Mar 2023 09:04:58 +0300
Subject: [PATCH 13/21] feat: update KE KRA PIN validators

---
 localflavor/ke/forms.py | 59 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 52 insertions(+), 7 deletions(-)

diff --git a/localflavor/ke/forms.py b/localflavor/ke/forms.py
index 4cbdc2427..f346a5b3c 100644
--- a/localflavor/ke/forms.py
+++ b/localflavor/ke/forms.py
@@ -11,6 +11,7 @@
 ke_id_re = re.compile(r"^\d{7}(?:\d{1})?$")
 ke_po_box_re = re.compile(r"\A\d{5,5}\Z")
 
+
 class KEPostalCodeField(CharField):
     """
     A form field that validates its input as a Kenyan Postal Code.
@@ -21,7 +22,18 @@ class KEPostalCodeField(CharField):
         "invalid": _("Enter a valid Kenyan Postal code in the format 12345")
     }
 
-    def clean(self, value):
+    def clean(self, value: str):
+        """Validates KE Postal Code
+
+        Args:
+            value (_type_): _description_
+
+        Raises:
+            ValidationError: _description_
+
+        Returns:
+            _type_: _description_
+        """
         value = super().clean(value)
         if value in self.empty_values:
             return self.empty_value
@@ -34,22 +46,55 @@ def clean(self, value):
         return value
 
 
-
-
-class KEKraPinNumberField(CharField):
+class KEKRAPINField(CharField):
     """
     TODO
 
-    A form field that validates input as a Kenya Revenue Authority PIN Number
+    A form field that validates input as a Kenya Revenue Authority PIN
+    (Personal Identification Number) Number.
+
+    A Kenyan KRA (Kenya Revenue Authority) PIN (Personal Identification Number)
+
+    is typically 11 characters long, consisting of the letter 'A' or 'P' followed
+
+    by 9 digits and ending with a letter (e.g., A123456789B or P987654321C).
 
     Validates 2 different formats:
 
-        POXXXXXXXX - Company/Institutions
+        POXXXXXXXX - Company/Institution
 
         AXXXXXXXXX - Individuals
     """
 
-    ...
+    default_error_messages = {
+        "invalid": _(
+            "Enter a valid Kenyan KRA PIN Number in the format A123456789B or P987654321C"
+        ),
+    }
+
+    def clean(self, value):
+        """Runs the validation checks
+
+        Args:
+            value (_type_): _description_
+
+        Raises:
+            ValidationError: _description_
+
+        Returns:
+            _type_: _description_
+        """
+        value = super().clean(value)
+        if value in self.empty_values:
+            return self.empty_value
+
+        # Strip out spaces and dashes
+        value = value.replace(" ", "").replace("-", "")
+        kra_pin_regex = r"^(A|P)\d{9}[A-Z]$"
+        match = re.match(kra_pin_regex, value)
+        if not match:
+            raise ValidationError(self.error_messages.get("invalid"))
+        return value.upper()
 
 
 class KEIDNumberField(CharField):

From 8ad74a74adf87e7c0ea8f4bd08a97b18a4fc5815 Mon Sep 17 00:00:00 2001
From: chmod77 <chmod777.ke@gmail.com>
Date: Sun, 26 Mar 2023 09:05:37 +0300
Subject: [PATCH 14/21] chore: document the counties model

---
 localflavor/ke/ke_counties.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/localflavor/ke/ke_counties.py b/localflavor/ke/ke_counties.py
index bfe326302..0536e632d 100644
--- a/localflavor/ke/ke_counties.py
+++ b/localflavor/ke/ke_counties.py
@@ -4,6 +4,7 @@
 
 from django.utils.translation import gettext_lazy as _
 
+# The 47 counties of Kenya
 COUNTY_CHOICES = (
     ("MOMBASA", _("MOMBASA")),
     ("KWALE", _("KWALE")),

From 783576f93b1035c97c51edfedcc2af03bf26773e Mon Sep 17 00:00:00 2001
From: chmod77 <chmod777.ke@gmail.com>
Date: Sun, 26 Mar 2023 09:39:49 +0300
Subject: [PATCH 15/21] feat: update validators for National ID

---
 localflavor/ke/forms.py  | 47 ++++++++++++++++++++++++++--------------
 localflavor/ke/models.py |  6 ++---
 2 files changed, 33 insertions(+), 20 deletions(-)

diff --git a/localflavor/ke/forms.py b/localflavor/ke/forms.py
index f346a5b3c..2d1549fc0 100644
--- a/localflavor/ke/forms.py
+++ b/localflavor/ke/forms.py
@@ -8,8 +8,10 @@
 
 from .ke_counties import COUNTY_CHOICES
 
-ke_id_re = re.compile(r"^\d{7}(?:\d{1})?$")
 ke_po_box_re = re.compile(r"\A\d{5,5}\Z")
+ke_kra_pin_regex = re.compile(r"^(A|P)\d{9}[A-Z]$")
+ke_passport_regex = re.compile(r"^[A-Z]\d{6,7}$")
+ke_national_id_regex = re.compile(r"^\d{7,8}$")
 
 
 class KEPostalCodeField(CharField):
@@ -90,49 +92,62 @@ def clean(self, value):
 
         # Strip out spaces and dashes
         value = value.replace(" ", "").replace("-", "")
-        kra_pin_regex = r"^(A|P)\d{9}[A-Z]$"
-        match = re.match(kra_pin_regex, value)
+        match = re.match(ke_kra_pin_regex, value)
         if not match:
             raise ValidationError(self.error_messages.get("invalid"))
         return value.upper()
 
 
-class KEIDNumberField(CharField):
+class KENationalIDNumberField(CharField):
     """
-    TODO
-
-    Kenya National ID Number
-
-    8 Digits
+    A form field that validates its input as a Kenyan National ID Number.
 
     """
 
     default_error_messages = {
-        "invalid": _("Enter a valid Kenyan ID Number"),
+        "invalid": _(
+            "Enter a valid Kenyan National ID Number in the format 1234567 or 12345678"
+        )
     }
 
     def clean(self, value):
+        """Runs the validation checks for KE National ID Number"""
         value = super().clean(value)
         if value in self.empty_values:
             return self.empty_value
 
         # Strip out spaces and dashes
         value = value.replace(" ", "").replace("-", "")
-        match = re.match(ke_id_re, value)
-
+        match = re.match(ke_national_id_regex, value)
         if not match:
             raise ValidationError(self.error_messages.get("invalid"))
         return value
 
 
-class KEPassportNumberField(RegexField):
+class KEPassportNumberField(CharField):
     """
-    TODO
+    A form field that validates its input as a Kenyan Passport Number.
 
-    Kenya Passport Number
     """
 
-    ...
+    default_error_messages = {
+        "invalid": _(
+            "Enter a valid Kenyan Passport Number in the format A123456 or B1234567"
+        )
+    }
+
+    def clean(self, value):
+        """Runs the validation checks for KE Passport Number"""
+        value = super().clean(value)
+        if value in self.empty_values:
+            return self.empty_value
+
+        # Strip out spaces and dashes
+        value = value.replace(" ", "").replace("-", "")
+        match = re.match(ke_passport_regex, value)
+        if not match:
+            raise ValidationError(self.error_messages.get("invalid"))
+        return value.upper()
 
 
 class KENSSFNumberField(RegexField):
diff --git a/localflavor/ke/models.py b/localflavor/ke/models.py
index 516776269..ce609a9d4 100644
--- a/localflavor/ke/models.py
+++ b/localflavor/ke/models.py
@@ -3,10 +3,8 @@
 from django.utils.translation import gettext_lazy as _
 
 from .forms import (
-    KECompanyRegNumber as KECompanyRegNumberFormField,
-    KECountySelect,
-    KEIDNumberField,
-    KEKraPinNumberField,
+    KENationalIDNumberField,
+    KEKRAPINField,
     KENHIFNumberField,
     KENSSFNumberField,
     KEPassportNumberField,

From 47c477aabe68fa1a4227f60e4fbdd1cf4beea987 Mon Sep 17 00:00:00 2001
From: chmod77 <chmod777.ke@gmail.com>
Date: Thu, 12 Oct 2023 04:55:51 +0300
Subject: [PATCH 16/21] chore: auto update authors

---
 docs/authors.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/docs/authors.rst b/docs/authors.rst
index 8825321a6..0480f2957 100644
--- a/docs/authors.rst
+++ b/docs/authors.rst
@@ -125,3 +125,4 @@ Authors
 * Vishal Pandey
 * Vladimir Nani
 * Abhineet Tamrakar
+* Shalom Nyende

From 9359cee7cc127c9f0ec555264fd8c57ea2350ded Mon Sep 17 00:00:00 2001
From: chmod77 <chmod777.ke@gmail.com>
Date: Thu, 12 Oct 2023 04:56:59 +0300
Subject: [PATCH 17/21] chore: auto update changelog

---
 docs/changelog.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index a0a4db765..e5ebfb31b 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -5,6 +5,7 @@ Changelog
 ------------------
 
 New flavors:
+- Kenya localflavor
 
 - Nepal LocalFlavor: Support for Nepal added
   (`gh-451 <https://github.com/django/django-localflavor/pull/451>`_).

From f59b428cd7e54390baab38d6b9d084c8a5f614b6 Mon Sep 17 00:00:00 2001
From: chmod77 <chmod777.ke@gmail.com>
Date: Thu, 12 Oct 2023 04:57:15 +0300
Subject: [PATCH 18/21] feat: update KE forms

---
 localflavor/ke/forms.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/localflavor/ke/forms.py b/localflavor/ke/forms.py
index 2d1549fc0..0a29f4d69 100644
--- a/localflavor/ke/forms.py
+++ b/localflavor/ke/forms.py
@@ -17,7 +17,6 @@
 class KEPostalCodeField(CharField):
     """
     A form field that validates its input as a Kenyan Postal Code.
-
     """
 
     default_error_messages = {
@@ -66,6 +65,7 @@ class KEKRAPINField(CharField):
         POXXXXXXXX - Company/Institution
 
         AXXXXXXXXX - Individuals
+        
     """
 
     default_error_messages = {

From 1afc94d0a1935513a29f592b9b75af376a03f261 Mon Sep 17 00:00:00 2001
From: chmod77 <chmod777.ke@gmail.com>
Date: Thu, 12 Oct 2023 04:57:34 +0300
Subject: [PATCH 19/21] feat: update KE models

---
 localflavor/ke/models.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/localflavor/ke/models.py b/localflavor/ke/models.py
index ce609a9d4..e888e19a9 100644
--- a/localflavor/ke/models.py
+++ b/localflavor/ke/models.py
@@ -26,6 +26,8 @@ def formfield(self, **kwargs) -> Any:
         defaults = {"form_class": KEPostalCodeFormField}
         defaults.update(kwargs)
         return super().formfield(**defaults)
+    
+
 
         
         
\ No newline at end of file

From d6762f7e85f2467b716e9436c6e63a03ea1108e8 Mon Sep 17 00:00:00 2001
From: chmod77 <chmod777.ke@gmail.com>
Date: Thu, 12 Oct 2023 04:57:45 +0300
Subject: [PATCH 20/21] feat: add KE tests

---
 tests/test_ke.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 59 insertions(+)
 create mode 100644 tests/test_ke.py

diff --git a/tests/test_ke.py b/tests/test_ke.py
new file mode 100644
index 000000000..4446e191c
--- /dev/null
+++ b/tests/test_ke.py
@@ -0,0 +1,59 @@
+from django.test import SimpleTestCase
+from django.forms import ValidationError
+from .forms import KEPostalCodeField, KEKRAPINField, KENationalIDNumberField, KEPassportNumberField
+
+class KEPostalCodeFieldTest(SimpleTestCase):
+    def test_valid_postal_code(self):
+        field = KEPostalCodeField()
+        valid_postal_codes = ["12345", "54321"]
+        for postal_code in valid_postal_codes:
+            self.assertEqual(field.clean(postal_code), "12345")
+
+    def test_invalid_postal_code(self):
+        field = KEPostalCodeField()
+        invalid_postal_codes = ["1234", "ABCDE", "12 345", "12-345"]
+        for postal_code in invalid_postal_codes:
+            with self.assertRaises(ValidationError):
+                field.clean(postal_code)
+
+class KEKRAPINFieldTest(SimpleTestCase):
+    def test_valid_kra_pin(self):
+        field = KEKRAPINField()
+        valid_pins = ["A123456789B", "P987654321C"]
+        for pin in valid_pins:
+            self.assertEqual(field.clean(pin), pin)
+
+    def test_invalid_kra_pin(self):
+        field = KEKRAPINField()
+        invalid_pins = ["1234567890", "A123456789", "P987654321", "A12-3456789B"]
+        for pin in invalid_pins:
+            with self.assertRaises(ValidationError):
+                field.clean(pin)
+
+class KENationalIDNumberFieldTest(SimpleTestCase):
+    def test_valid_national_id(self):
+        field = KENationalIDNumberField()
+        valid_ids = ["1234567", "12345678"]
+        for id_number in valid_ids:
+            self.assertEqual(field.clean(id_number), id_number)
+
+    def test_invalid_national_id(self):
+        field = KENationalIDNumberField()
+        invalid_ids = ["12345", "12345A", "12-34567", "123456789"]
+        for id_number in invalid_ids:
+            with self.assertRaises(ValidationError):
+                field.clean(id_number)
+
+class KEPassportNumberFieldTest(SimpleTestCase):
+    def test_valid_passport_number(self):
+        field = KEPassportNumberField()
+        valid_passports = ["A123456", "B1234567"]
+        for passport in valid_passports:
+            self.assertEqual(field.clean(passport), passport)
+
+    def test_invalid_passport_number(self):
+        field = KEPassportNumberField()
+        invalid_passports = ["12345", "A1234567B", "AB-123456"]
+        for passport in invalid_passports:
+            with self.assertRaises(ValidationError):
+                field.clean(passport)

From 84a1f2eca9df4f09cd40811d295d6c0dfbc4e395 Mon Sep 17 00:00:00 2001
From: chmod77 <chmod777.ke@gmail.com>
Date: Thu, 12 Oct 2023 05:09:34 +0300
Subject: [PATCH 21/21] feat: add versioning for new localflavor

---
 localflavor/ke/forms.py  | 8 ++++++--
 localflavor/ke/models.py | 1 +
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/localflavor/ke/forms.py b/localflavor/ke/forms.py
index 0a29f4d69..3c6f929be 100644
--- a/localflavor/ke/forms.py
+++ b/localflavor/ke/forms.py
@@ -17,6 +17,7 @@
 class KEPostalCodeField(CharField):
     """
     A form field that validates its input as a Kenyan Postal Code.
+    .. versionadded:: 4.0
     """
 
     default_error_messages = {
@@ -65,6 +66,8 @@ class KEKRAPINField(CharField):
         POXXXXXXXX - Company/Institution
 
         AXXXXXXXXX - Individuals
+
+    .. versionadded:: 4.0
         
     """
 
@@ -101,7 +104,7 @@ def clean(self, value):
 class KENationalIDNumberField(CharField):
     """
     A form field that validates its input as a Kenyan National ID Number.
-
+    .. versionadded:: 4.0
     """
 
     default_error_messages = {
@@ -127,7 +130,7 @@ def clean(self, value):
 class KEPassportNumberField(CharField):
     """
     A form field that validates its input as a Kenyan Passport Number.
-
+    .. versionadded:: 4.0
     """
 
     default_error_messages = {
@@ -189,6 +192,7 @@ class KEPayBillNumber(RegexField):
 class KECountySelectField(Select):
     """
     A Select widget listing Kenyan Counties as the choices
+    .. versionadded:: 4.0
     """
 
     def __init__(self, attrs=None) -> None:
diff --git a/localflavor/ke/models.py b/localflavor/ke/models.py
index e888e19a9..73667c6a5 100644
--- a/localflavor/ke/models.py
+++ b/localflavor/ke/models.py
@@ -15,6 +15,7 @@
 class KEPostalCodeField(CharField):
     """
     A model field that stores the Kenyan Postal Codes
+    .. versionadded:: 4.0
     """
     description = _("Kenya Postal Code")