Source code for django_core.db.models.mixins.base

from __future__ import unicode_literals

from copy import deepcopy
from datetime import datetime

from django.conf import settings
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django_core.db.models.managers import CommonManager
from django_core.utils.list_utils import make_obj_list


@python_2_unicode_compatible
[docs]class AbstractBaseModel(models.Model): """Base model for other db model to extend. This class contains common model attributes needed by almost all models. Fields: * created: created user. The user who created this instance. * created_dttm: created datetime. * last_modified: last user to modify this instance * last_modified_dttm: updated datetime. Datetime this document was last updated. """ created_user = models.ForeignKey( settings.AUTH_USER_MODEL, related_name='%(app_label)s_%(class)s_created_user+') created_dttm = models.DateTimeField(default=datetime.utcnow) last_modified_user = models.ForeignKey( settings.AUTH_USER_MODEL, related_name='%(app_label)s_%(class)s_last_modified_user+') last_modified_dttm = models.DateTimeField(default=datetime.utcnow) objects = CommonManager() class Meta: abstract = True # Default ordering is by id instead of created_dttm for 2 reasons: # 1) id is indexed (primary key) # 2) it's safer than created_dttm since created_dttm could be the same # value which would lead to inconsistent ording in responses from # queries. This works because id is an integer field that's auto # incremented. ordering = ('-id',) def __str__(self): return str(self.id)
[docs] def save(self, *args, **kwargs): """Optional kwargs: * id_length: the length of characters to use for the id. Default is 10. """ self.__class__.save_prep(self) return super(AbstractBaseModel, self).save(*args, **kwargs)
[docs] def get_verbose_name(self): """Gets the verbose name for an object.""" return self._meta.verbose_name
@classmethod
[docs] def save_prep(cls, instance_or_instances): """Common save functionality for all models. This can be called with a saved or unsaved instance or one or many objects. This is beneficial when additional process needs to happen before a bulk_create which doesn't explicitly call the .save on each instance being saved. This method will go through each object and do necessary presave processing. This method can be extended by classes and implement this abstact class by simply creating the def save_prep method and making sure to call super class method making sure the save_prep method is properly called from each inheriting class: All models.CharField will be stripped prior to saving. Example: @classmethod def save_prep(cls, instance_or_instances): # Do additional processing for inheriting class super(MyInheritingClass, cls).save_prep(instance_or_instances) Note: Make sure not to call the save_prep method in the save method of inheriting classes or it will get called twice which likely isn't wanted since this Abstract class explicitly calls the save_prep on save(). All objects are assumed to have the following fields: * id * created * created_dttm * last_modified * last_modified_dttm """ instances = make_obj_list(instance_or_instances) utc_now = datetime.utcnow() for instance in instances: instance.last_modified_dttm = utc_now if not instance.created_dttm: instance.created_dttm = utc_now if instance.created_user: if not hasattr(instance, 'last_modified') or \ not instance.last_modified: instance.last_modified_user = instance.created_user instance.strip_fields()
[docs] def strip_fields(self): """Strips whitespace from all text related model fields. This includes CharField and TextFields and all subclasses of those two fields. """ for field in self._meta.fields: if isinstance(field, (models.CharField, models.TextField)): value = getattr(self, field.name) if value and hasattr(value, 'strip'): setattr(self, field.name, value.strip())
[docs] def copy(self, exclude_fields=None, **override_fields): """Returns an unsaved copy of this object with all fields except for any fields that are unique in the DB. Those fields must be explicitly set before saving the instance: * id * created_dttm * last_modified_dttm NOTE: If a field doesn't except null values, you must explicitly set the value in the override fields or this method will error out since you can't set that fields to be null. :param exclude_fields: fields to exclude from the copy. They will fallback to the field default if one is given or None. :param override_fields: kwargs with fields to override. The key is the field name, the value is the value to set the copied object to. Example: >> new_obj = some_obj.copy(my_field='hello world') >> new_obj.my_field 'hello world' """ if not exclude_fields: exclude_fields = [] exclude_fields += ['created_dttm', 'last_modified_dttm'] # Don't copy data for any unique fields. These must be explicitly set. exclude_fields += [field.name for field in self._meta.fields if field.unique] instance = deepcopy(self) instance.last_modified_user = instance.created_user instance.last_modified_user_id = instance.created_user_id # unset all the attributes that you don't want copied over for field in set(exclude_fields): meta = self.__class__._meta default = meta.get_field_by_name(field)[0].get_default() setattr(instance, field, default or None) if override_fields: for field, val in override_fields.items(): setattr(instance, field, val) return instance
@classmethod def _get_many_to_many_model(cls, field_name): """Get the model for the many to many field. :param field_name: field name to get the model for. """ for field in cls._meta.many_to_many: if field.attname == field_name: if hasattr(field.related, 'parent_model'): # django < 1.8 return field.related.parent_model return field.related.model @classmethod
[docs] def post_save(cls, *args, **kwargs): """Adding a hook here so it's safe to call the super's post_save.""" pass
@classmethod
[docs] def post_delete(cls, *args, **kwargs): """Adding a hook here so it's safe to call the super's post_delete.""" pass
@classmethod
[docs] def m2m_changed(cls, *args, **kwargs): """Adding a hook here so it's safe to call the super's m2m_changed.""" pass