from django import forms


class RequiredAnyLengthCharField(forms.CharField):
    """CharField variant that is required but allows empty strings.

    Specifically, it:

    - Clamps required=True, but
    - doesn't reject empty strings.

    This is good for situations where you want a field to be included in the POST
    dict but don't care whether it's zero-length or not."""

    def __init__(self, *args, **kwargs):
        # Rough sketch of what happens during CharField cleaning/validation:
        #
        # 1. It starts out with a dict of values passed in by POST. In our case
        #    this is just a QueryDict (basically a dict) with string keys and
        #    values. If a field was passed to the server, then it will be
        #    present here, even if the value is an empty string.
        # 2. Regardless of whether a key with the same name as this field was
        #    passed, this field's values are "cleaned" using self.to_python().
        #    to_python() gets the value in the POST dict if it exists, or else
        #    it gets None. If the value passed to to_python() is in
        #    self.empty_values, then it gets replaced with self.empty_value
        #    (which includes both None and '' by default) (which defaults to
        #    '').
        # 3. The cleaned value is then passed to self.validate(). When
        #    required=True, this checks whether the cleaned value is in
        #    self.empty_values, and throws an error if it is.
        #
        # So, if we want to allow empty strings but still require the name of
        # the field to be present in the POST dict, then we can set do three
        # things. First, we set require=True to error out when the input is in
        # self.empty_values. Second, we set self.empty_values to (None,) so the
        # empty string is not recognized as an "empty value", but None (which
        # signals that the key was not present) is still recognized as empty.
        # Third, we set self.empty_value=None to ensure that the special None
        # value gets preserved by cleaning.
        super().__init__(*args, required=True, empty_value=None, strip=False, **kwargs)
        self.empty_values = (None,)
