Source code for erp_framework.base.models

import datetime
import logging

from django.contrib.auth import get_user_model
from django.db import models
from django.urls import reverse, NoReverseMatch
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _

from . import app_settings

logger = logging.getLogger(__name__)
User = get_user_model()


class DiffingMixin(object):
    def __init__(self, *args, **kwargs):
        super(DiffingMixin, self).__init__(*args, **kwargs)
        self._original_state = dict(self.__dict__)
        self.pre_current_state = None

    def save(self, *args, **kwargs):
        state = dict(self.__dict__)
        del state["_original_state"]
        super(DiffingMixin, self).save(*args, **kwargs)
        self.pre_current_state = self._original_state
        self._original_state = state

    def is_dirty(self):
        missing = object()
        for key, value in self._original_state.items():
            try:
                if value != self.__dict__.get(key, missing):
                    return True
            except TypeError as e:
                o = self.__dict__.get(key, missing)
                if type(value) == datetime.datetime and type(o) == datetime.datetime:
                    # Make both unaware
                    o = o.replace(tzinfo=None)
                    value = value.replace(tzinfo=None)
                    if value != o:
                        return True

                else:
                    raise e
        return False

    def changed_columns(self):
        missing = object()
        result = {}
        for key, value in self._original_state.items():
            try:
                if value != self.__dict__.get(key, missing):
                    result[key] = {"old": value, "new": self.__dict__.get(key, missing)}
            except TypeError:
                result[key] = {"old": value, "new": self.__dict__.get(key, missing)}
        return result


class RAModel(DiffingMixin, models.Model):
    owner = models.ForeignKey(
        User,
        related_name="%(app_label)s_%(class)s_related",
        verbose_name=_("owner"),
        on_delete=models.CASCADE,
    )
    creation_date = models.DateTimeField(_("creation date and time"), default=now)
    lastmod = models.DateTimeField(_("last modification"), db_index=True)
    lastmod_user = models.ForeignKey(
        User,
        related_name="%(app_label)s_%(class)s_lastmod_related",
        verbose_name=_("last modification by"),
        on_delete=models.CASCADE,
    )

    class Meta:
        abstract = True


class ERPMixin:
    @classmethod
    def get_class_name(cls):
        """
        return the class name, usable when a erp_framework model is mimicking (ie:proxying)
        another model.
        This method is used is get_doc_type_* functions,
        This method is made to avoid to repeat registered type to make adjustments
        """
        return cls.__name__


[docs]class EntityModel(ERPMixin, RAModel): """ The Main base for ERP framework `static` models Example: Client , Expense etc.. """ slug = models.SlugField( _("Identifier slug"), help_text=_("For fast recall"), max_length=50, unique=True, db_index=True, blank=True, ) name = models.CharField(_("Name"), max_length=255, unique=True, db_index=True) notes = models.TextField(_("Notes"), null=True, blank=True) class Meta: abstract = True def __init__(self, *args, **kwargs): super(EntityModel, self).__init__(*args, **kwargs) # self.reporting_model = None if not getattr(self, "pk_name", False): self.pk_name = None def __str__(self): return self.name def get_absolute_url(self): model_name = self._meta.model_name.lower() try: url = reverse( "%s:%s_%s_view" % ( app_settings.ERP_FRAMEWORK_SITE_NAME, self._meta.app_label, model_name, ), args=(self.pk,), ) except NoReverseMatch: url = reverse( "%s:%s_%s_change" % ( app_settings.ERP_FRAMEWORK_SITE_NAME, self._meta.app_label, self.get_class_name().lower(), ), args=(self.pk,), ) return url
[docs] def get_next_slug(self, suggestion=None): """ Get the next slug If it's a new instance and the slug is not provided, we try and attempt a serial over the already added slugs in relation to the model :return: """ from .helpers import get_next_serial return get_next_serial(self.__class__) # repr(time.time()).replace('.', '')
[docs] def save(self, *args, **kwargs): if self.pk is None: if not self.slug: self.slug = self.get_next_slug(self.name) # print(self.slug) if not self.owner_id and self.lastmod_user_id: self.owner_id = self.lastmod_user_id if not self.lastmod_user_id and self.owner_id: self.lastmod_user_id = self.owner_id self.lastmod = now() super(EntityModel, self).save(*args, **kwargs)
[docs]class TransactionModel(EntityModel): name = None slug = models.SlugField( _("Slug"), max_length=50, db_index=True, validators=[], blank=True ) date = models.DateTimeField(_("Date"), db_index=True) type = models.CharField(max_length=30, db_index=True) notes = models.TextField(_("Notes"), null=True, blank=True) value = models.DecimalField(_("Value"), max_digits=19, decimal_places=2, default=0) owner = models.ForeignKey( User, related_name="%(app_label)s_%(class)s_related", verbose_name=_("Created By"), on_delete=models.CASCADE, ) creation_date = models.DateTimeField(_("Created"), default=now) lastmod = models.DateTimeField(_("Last modified"), db_index=True) lastmod_user = models.ForeignKey( User, related_name="%(app_label)s_%(class)s_lastmod_related", verbose_name=_("Last modification by"), on_delete=models.CASCADE, )
[docs] @classmethod def get_doc_type(cls): """ Return the type :return: """ return cls.__name__.lower()
def __str__(self): return "%s-%s" % (self._meta.verbose_name, self.slug) def __repr__(self): return "<%s pk:%s slug:%s type:%s>" % ( self.__class__.__name__, self.pk, self.slug, self.type, ) class Meta: abstract = True
[docs] def save(self, *args, **kwargs): """ Custom save, it assign the user As owner and the last modifed it sets the type make sure that dlc_date has correct timezone ?! :param force_insert: :param force_update: :param using: :param update_fields: :return: """ from erp_framework.base.helpers import get_next_serial self.type = self.get_doc_type() if not self.slug: self.slug = get_next_serial(self.__class__) super().save(*args, **kwargs)
@property def name(self): return self.date.strftime("%Y/%m/%d %H:%M")
class TransactionItemModel(TransactionModel): """ Abstract model to identify a movement with a value """ class Meta: abstract = True class QuantitativeTransactionItemModel(TransactionItemModel): quantity = models.DecimalField( _("Quantity"), max_digits=19, decimal_places=2, default=0 ) price = models.DecimalField(_("Price"), max_digits=19, decimal_places=2, default=0) discount = models.DecimalField( _("Discount"), max_digits=19, decimal_places=2, default=0 ) def save(self, *args, **kwargs): self.value = self.quantity * self.price if self.discount: self.value -= self.value * self.discount / 100 super(QuantitativeTransactionItemModel, self).save(*args, **kwargs) class Meta: abstract = True class BaseReportModel(DiffingMixin, models.Model): slug = models.SlugField( _("Slug"), max_length=50, db_index=True, validators=[], blank=True ) date = models.DateTimeField(_("Date"), db_index=True) type = models.CharField(max_length=30, db_index=True, verbose_name=_("Type")) notes = models.TextField(_("Notes"), null=True, blank=True) value = models.DecimalField(_("Value"), max_digits=19, decimal_places=2, default=0) owner = models.ForeignKey( User, related_name="%(app_label)s_%(class)s_related", verbose_name=_("Created By"), on_delete=models.DO_NOTHING, ) creation_date = models.DateTimeField(_("Creation Timestamp"), default=now) lastmod = models.DateTimeField(_("Last modified"), db_index=True) lastmod_user = models.ForeignKey( User, related_name="%(app_label)s_%(class)s_lastmod_related", verbose_name=_("Last modification by"), on_delete=models.DO_NOTHING, ) class Meta: abstract = True default_permissions = () class QuanValueReport(BaseReportModel): quantity = models.DecimalField( _("quantity"), max_digits=19, decimal_places=2, default=0 ) price = models.DecimalField(_("price"), max_digits=19, decimal_places=2, default=0) discount = models.DecimalField( _("discount"), max_digits=19, decimal_places=2, default=0 ) class Meta: abstract = True