Skip to content

Instantly share code, notes, and snippets.

@gavinwahl
Created July 29, 2014 23:18
Show Gist options
  • Save gavinwahl/17c07335c8dd1b832911 to your computer and use it in GitHub Desktop.
Save gavinwahl/17c07335c8dd1b832911 to your computer and use it in GitHub Desktop.
CheckedAtBooleanField
class CheckedAtBooleanField(models.DateTimeField):
"""
A db field that acts like a checkbox in forms, but is backed by a
DateTimeField in the database. `None` represents that the box was
not checked, a timestamp value indicates that the box was checked
at that time. If the form is saved again, the time is not updated.
Specifically, the timestamp indicates when a user chose to opt in.
When setting the attribute on the model, it's possible to use the
values True and False. False sets the field to None. True behaves
like checkboxes do in a form -- The field is stores the timestamp
of the _first_ time it was True, not overriding the current value.
There are some decisions this field doesn't make -- How should it
behave in the admin center? Should it support filters like a bool?
Since querying like a boolean isn't supported, use isnull instead.
"""
def __init__(self, *args, **kwargs):
kwargs.setdefault('null', True)
kwargs.setdefault('blank', True)
super(CheckedAtBooleanField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
# Should this be setitem or setdefault? It depends what admin
# should do. The admin will try to use a datetime widget here,
# so utilizing setitem instead of setdefault we override that.
kwargs['form_class'] = forms.BooleanField
kwargs['widget'] = forms.BooleanField.widget
return super(CheckedAtBooleanField, self).formfield(**kwargs)
def save_form_data(self, instance, data):
current_value = getattr(instance, self.attname)
if data:
data = current_value or timezone.now()
else:
data = None
return super(CheckedAtBooleanField, self).save_form_data(instance, data)
def contribute_to_class(self, cls, name):
super(CheckedAtBooleanField, self).contribute_to_class(cls, name)
setattr(cls, name, CheckedAtBooleanFieldDescriptor(self))
class CheckedAtBooleanFieldDescriptor(object):
def __init__(self, field):
self.field = field
self.cache_name = self.field.get_cache_name()
def __get__(self, instance, instance_type=None):
if instance is None:
return self
else:
return getattr(instance, self.cache_name)
def __set__(self, instance, value):
if value is True:
current_value = getattr(instance, self.cache_name, None)
value = current_value or timezone.now()
elif value is False:
value = None
setattr(instance, self.cache_name, value)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment