1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
|
from __future__ import unicode_literals
import django
from django.db import models
from django.conf import settings
from django.utils.encoding import python_2_unicode_compatible
from django.utils.timezone import now
DEFAULT_CHOICES_NAME = 'STATUS'
class AutoCreatedField(models.DateTimeField):
"""
A DateTimeField that automatically populates itself at
object creation.
By default, sets editable=False, default=datetime.now.
"""
def __init__(self, *args, **kwargs):
kwargs.setdefault('editable', False)
kwargs.setdefault('default', now)
super(AutoCreatedField, self).__init__(*args, **kwargs)
class AutoLastModifiedField(AutoCreatedField):
"""
A DateTimeField that updates itself on each save() of the model.
By default, sets editable=False and default=datetime.now.
"""
def pre_save(self, model_instance, add):
value = now()
setattr(model_instance, self.attname, value)
return value
class StatusField(models.CharField):
"""
A CharField that looks for a ``STATUS`` class-attribute and
automatically uses that as ``choices``. The first option in
``STATUS`` is set as the default.
Also has a default max_length so you don't have to worry about
setting that.
Also features a ``no_check_for_status`` argument to make sure
South can handle this field when it freezes a model.
"""
def __init__(self, *args, **kwargs):
kwargs.setdefault('max_length', 100)
self.check_for_status = not kwargs.pop('no_check_for_status', False)
self.choices_name = kwargs.pop('choices_name', DEFAULT_CHOICES_NAME)
super(StatusField, self).__init__(*args, **kwargs)
def prepare_class(self, sender, **kwargs):
if not sender._meta.abstract and self.check_for_status:
assert hasattr(sender, self.choices_name), \
"To use StatusField, the model '%s' must have a %s choices class attribute." \
% (sender.__name__, self.choices_name)
self._choices = getattr(sender, self.choices_name)
if django.VERSION >= (1, 9, 0):
self.choices = self._choices
if not self.has_default():
self.default = tuple(getattr(sender, self.choices_name))[0][0] # set first as default
def contribute_to_class(self, cls, name):
models.signals.class_prepared.connect(self.prepare_class, sender=cls)
# we don't set the real choices until class_prepared (so we can rely on
# the STATUS class attr being available), but we need to set some dummy
# choices now so the super method will add the get_FOO_display method
self._choices = [(0, 'dummy')]
if django.VERSION >= (1, 9, 0):
self.choices = self._choices
super(StatusField, self).contribute_to_class(cls, name)
def deconstruct(self):
name, path, args, kwargs = super(StatusField, self).deconstruct()
kwargs['no_check_for_status'] = True
return name, path, args, kwargs
class MonitorField(models.DateTimeField):
"""
A DateTimeField that monitors another field on the same model and
sets itself to the current date/time whenever the monitored field
changes.
"""
def __init__(self, *args, **kwargs):
kwargs.setdefault('default', now)
monitor = kwargs.pop('monitor', None)
if not monitor:
raise TypeError(
'%s requires a "monitor" argument' % self.__class__.__name__)
self.monitor = monitor
when = kwargs.pop('when', None)
if when is not None:
when = set(when)
self.when = when
super(MonitorField, self).__init__(*args, **kwargs)
def contribute_to_class(self, cls, name):
self.monitor_attname = '_monitor_%s' % name
models.signals.post_init.connect(self._save_initial, sender=cls)
super(MonitorField, self).contribute_to_class(cls, name)
def get_monitored_value(self, instance):
return getattr(instance, self.monitor)
def _save_initial(self, sender, instance, **kwargs):
setattr(instance, self.monitor_attname,
self.get_monitored_value(instance))
def pre_save(self, model_instance, add):
value = now()
previous = getattr(model_instance, self.monitor_attname, None)
current = self.get_monitored_value(model_instance)
if previous != current:
if self.when is None or current in self.when:
setattr(model_instance, self.attname, value)
self._save_initial(model_instance.__class__, model_instance)
return super(MonitorField, self).pre_save(model_instance, add)
def deconstruct(self):
name, path, args, kwargs = super(MonitorField, self).deconstruct()
kwargs['monitor'] = self.monitor
if self.when is not None:
kwargs['when'] = self.when
return name, path, args, kwargs
SPLIT_MARKER = getattr(settings, 'SPLIT_MARKER', '<!-- split -->')
# the number of paragraphs after which to split if no marker
SPLIT_DEFAULT_PARAGRAPHS = getattr(settings, 'SPLIT_DEFAULT_PARAGRAPHS', 2)
_excerpt_field_name = lambda name: '_%s_excerpt' % name
def get_excerpt(content):
excerpt = []
default_excerpt = []
paras_seen = 0
for line in content.splitlines():
if not line.strip():
paras_seen += 1
if paras_seen < SPLIT_DEFAULT_PARAGRAPHS:
default_excerpt.append(line)
if line.strip() == SPLIT_MARKER:
return '\n'.join(excerpt)
excerpt.append(line)
return '\n'.join(default_excerpt)
@python_2_unicode_compatible
class SplitText(object):
def __init__(self, instance, field_name, excerpt_field_name):
# instead of storing actual values store a reference to the instance
# along with field names, this makes assignment possible
self.instance = instance
self.field_name = field_name
self.excerpt_field_name = excerpt_field_name
# content is read/write
def _get_content(self):
return self.instance.__dict__[self.field_name]
def _set_content(self, val):
setattr(self.instance, self.field_name, val)
content = property(_get_content, _set_content)
# excerpt is a read only property
def _get_excerpt(self):
return getattr(self.instance, self.excerpt_field_name)
excerpt = property(_get_excerpt)
# has_more is a boolean property
def _get_has_more(self):
return self.excerpt.strip() != self.content.strip()
has_more = property(_get_has_more)
def __str__(self):
return self.content
class SplitDescriptor(object):
def __init__(self, field):
self.field = field
self.excerpt_field_name = _excerpt_field_name(self.field.name)
def __get__(self, instance, owner):
if instance is None:
raise AttributeError('Can only be accessed via an instance.')
content = instance.__dict__[self.field.name]
if content is None:
return None
return SplitText(instance, self.field.name, self.excerpt_field_name)
def __set__(self, obj, value):
if isinstance(value, SplitText):
obj.__dict__[self.field.name] = value.content
setattr(obj, self.excerpt_field_name, value.excerpt)
else:
obj.__dict__[self.field.name] = value
class SplitField(models.TextField):
def __init__(self, *args, **kwargs):
# for South FakeORM compatibility: the frozen version of a
# SplitField can't try to add an _excerpt field, because the
# _excerpt field itself is frozen as well. See introspection
# rules below.
self.add_excerpt_field = not kwargs.pop('no_excerpt_field', False)
super(SplitField, self).__init__(*args, **kwargs)
def contribute_to_class(self, cls, name):
if self.add_excerpt_field and not cls._meta.abstract:
excerpt_field = models.TextField(editable=False)
cls.add_to_class(_excerpt_field_name(name), excerpt_field)
super(SplitField, self).contribute_to_class(cls, name)
setattr(cls, self.name, SplitDescriptor(self))
def pre_save(self, model_instance, add):
value = super(SplitField, self).pre_save(model_instance, add)
excerpt = get_excerpt(value.content)
setattr(model_instance, _excerpt_field_name(self.attname), excerpt)
return value.content
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return value.content
def get_prep_value(self, value):
try:
return value.content
except AttributeError:
return value
def deconstruct(self):
name, path, args, kwargs = super(SplitField, self).deconstruct()
kwargs['no_excerpt_field'] = True
return name, path, args, kwargs
# allow South to handle these fields smoothly
try:
from south.modelsinspector import add_introspection_rules
# For a normal MarkupField, the add_excerpt_field attribute is
# always True, which means no_excerpt_field arg will always be
# True in a frozen MarkupField, which is what we want.
add_introspection_rules(rules=[
(
(SplitField,),
[],
{'no_excerpt_field': ('add_excerpt_field', {})}
),
(
(MonitorField,),
[],
{'monitor': ('monitor', {})}
),
(
(StatusField,),
[],
{'no_check_for_status': ('check_for_status', {})}
),
], patterns=['model_utils\.fields\.'])
except ImportError:
pass
|