from itertools import chain
import datetime
from django.utils import timezone
from django.conf import settings
from django.db import models
from django.db.models.query import QuerySet
from django.contrib.auth.models import User, Group
from django.contrib.comments.signals import comment_was_posted
from django.utils.translation import ugettext_lazy as _
from .lifecycle import State, Lifecycle, get_cancelled_state
from .plmobject import PLMObject
from .part import get_all_parts
from .document import get_all_documents
def _prefetch_related(qs, *extra):
return qs.prefetch_related("plmobject", "user", "user__profile", *extra)
# history stuff
[docs]class AbstractHistory(models.Model):
u"""
History model.
This model records all events related to :class:`.PLMObject`
:model attributes:
.. attribute:: plmobject
:class:`.PLMObject` of the event
.. attribute:: action
type of action (see :attr:`.ACTIONS`)
.. attribute:: details
type of action (see :attr:`.ACTIONS`)
.. attribute:: date
date of the event
.. attribute:: user
:class:`~django.contrib.auth.models.User` who maded the event
:class attribute:
"""
#: some actions available in the admin interface
ACTIONS = (
("Create", "Create"),
("Delete", "Delete"),
("Modify", "Modify"),
("Revise", "Revise"),
("Promote", "Promote"),
("Demote", "Demote"),
("Cancel", "Cancel"),
("Publish", "Publish"),
("Unpublish", "Unpublish"),
)
class Meta:
abstract = True
action = models.CharField(max_length=50, choices=ACTIONS)
details = models.TextField()
date = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User, related_name="%(class)s_user")
def __unicode__(self):
return "History<%s, %s, %s>" % (self.plmobject, self.date, self.action)
def get_day_as_int(self):
return self.date.year * 10000 + self.date.month * 100 + self.date.day
def get_day(self):
return datetime.date(self.date.year, self.date.month, self.date.day)
@classmethod
def timeline_items(cls, user):
return _prefetch_related(cls.objects.all().order_by("-date"))
@property
def title(self):
return self.plmobject.title
[docs]class History(AbstractHistory):
class Meta:
app_label = "plmapp"
plmobject = models.ForeignKey(PLMObject)
def get_redirect_url(self):
return "/history_item/object/%d/" % self.id
@classmethod
def timeline_items(cls, user):
q = models.Q(plmobject__owner__username=settings.COMPANY)
q |= models.Q(plmobject__group__in=user.groups.all())
histories = History.objects.filter(q).order_by('-date')
return _prefetch_related(histories)
[docs]class UserHistory(AbstractHistory):
class Meta:
app_label = "plmapp"
plmobject = models.ForeignKey(User)
def get_redirect_url(self):
return "/history_item/user/%d/" % self.id
@property
def title(self):
return self.plmobject.username
@classmethod
def timeline_items(cls, user):
return _prefetch_related(cls.objects.all().order_by("-date"))
[docs]class GroupHistory(AbstractHistory):
class Meta:
app_label = "plmapp"
plmobject = models.ForeignKey(Group)
def get_redirect_url(self):
return "/history_item/group/%d/" % self.id
@classmethod
def timeline_items(cls, user):
return _prefetch_related(cls.objects.all().order_by("-date"), "plmobject__groupinfo")
@property
def title(self):
return self.plmobject.groupinfo.title
[docs]class StateHistoryQuerySet(QuerySet):
""" QuerySet with utility methods to filter :class:`StateHistory` alive at a given time."""
[docs] def now(self):
"""
Filters state histories: keeps only alive state histories (end_time is null).
"""
return self.filter(end_time__isnull=True)
[docs] def at(self, time):
"""
Filters state histories: keeps alive state histories at time *time*.
:param time: a :class:`~datetime.datetime` or None
"""
if time is None:
return self.now()
return self.filter(start_time__lte=time).exclude(end_time__lt=time)
[docs] def officials(self):
"""
Filters only official state histories.
"""
return self.filter(state_category=StateHistory.OFFICIAL)
[docs]class StateHistoryManager(models.Manager):
"""state histories manager, returns a :class:`StateHistoryQuerySet`."""
use_for_related_fields = True
def get_query_set(self):
return StateHistoryQuerySet(self.model)
[docs] def now(self):
"""
Shorcut for ``self.get_query_set().now()``. See :meth:`StateHistoryQuerySet.now`.
"""
return self.get_query_set().now()
[docs] def at(self, time):
"""
Shorcut for ``self.get_query_set().at(time)``. See :meth:`StateHistoryQuerySet.at`.
"""
return self.get_query_set().at(time)
[docs] def officials(self):
"""
Shorcut for ``self.get_query_set().officials()``. See :meth:`StateHistoryQuerySet.officials()`.
"""
return self.get_query_set().officials()
[docs]class StateHistory(models.Model):
"""
Models that tracks the promotions and demotions of a :class:`.PLMObject`.
:model attributes:
.. attribute:: plmobject
:class:`.PLMObject` that has been promoted/demoted.
.. attribute:: start_time
date of the promotion/demotion
.. attribute:: end_time
date of the next promotion/demotion, None if the promotion
is the latest promotion
.. attribute:: state
:class:`.State` of *plmobject* at *t*, if *start_time* <= t < *end_time*
.. attribute:: lifecycle
:class:`.Lifecycle` of *plmobject* at *t*, if *start_time* <= t < *end_time*
.. attribute:: state_category
state's category of *plmobject* at *t*, if *start_time* <= t < *end_time*.
This field is redundant with the tuple (state, lifecycle) but it allows
fast queries to check if an object was official at a given time.
This attribute is automatically set when the object is saved.
Valid state's categories are:
.. attribute:: DRAFT
set if :meth:`.PLMObject.is_draft` returns True
.. attribute:: PROPOSED
set if :meth:`.PLMObject.is_proposed` returns True
.. attribute:: OFFICIAL
set if :meth:`.PLMObject.is_official` returns True
.. attribute:: DEPRECATED
set if :meth:`.PLMObject.is_deprecated` returns True
.. attribute:: CANCELLED
set if :meth:`.PLMObject.is_cancelled` returns True
"""
class Meta:
app_label = "plmapp"
DRAFT, PROPOSED, OFFICIAL, DEPRECATED, CANCELLED = range(5)
STATE_CATEGORIES = (
(DRAFT, "draft"),
(PROPOSED, "proposed"),
(OFFICIAL, "official"),
(DEPRECATED, "deprecated"),
(CANCELLED, "cancelled")
)
plmobject = models.ForeignKey(PLMObject)
state = models.ForeignKey(State)
lifecycle = models.ForeignKey(Lifecycle)
start_time = models.DateTimeField(_("date of promotion"),
default=timezone.now, auto_now_add=False)
end_time = models.DateTimeField(null=True)
state_category = models.PositiveSmallIntegerField(choices=STATE_CATEGORIES)
objects = StateHistoryManager()
def save(self, *args, **kwargs):
if self.state == get_cancelled_state():
category = StateHistory.CANCELLED
elif self.state == self.lifecycle.official_state:
category = StateHistory.OFFICIAL
elif self.state == self.lifecycle.first_state:
category = StateHistory.DRAFT
elif self.state == self.lifecycle.last_state:
category = StateHistory.DEPRECATED
else:
category = StateHistory.PROPOSED
self.state_category = category
super(StateHistory, self).save(*args, **kwargs)
def timeline_histories(user, date_begin=None, date_end=None, done_by=None, list_display=None):
if date_begin is None and date_end is None:
return History.timeline_items(user)
else:
history = None
history_plmobject = None
history_group = None
if list_display["display_document"] or list_display["display_part"]:
history_plmobject = History.timeline_items(user)
if list_display["display_document"] and not list_display["display_part"]:
documents = get_all_documents().keys()
history_plmobject = history_plmobject.filter(plmobject__type__in = documents)
elif not list_display["display_document"]:
parts = get_all_parts().keys()
history_plmobject = history_plmobject.filter(plmobject__type__in = parts)
history_plmobject = history_plmobject.filter(date__gte = date_end, date__lt = date_begin)
if done_by:
if User.objects.filter(username=done_by).exists():
history_plmobject = history_plmobject.filter(user__username = done_by)
else:
history_plmobject = history_plmobject.none()
if list_display["display_group"]:
history_group = GroupHistory.timeline_items(user)
history_group = history_group.filter(date__gte = date_end, date__lt = date_begin)
if done_by != "":
if User.objects.filter(username= done_by).exists():
history_group = history_group.filter(user__username = done_by)
else:
history_group = history_group.none()
for h in history_group:
h.plmobject.plmobject_url = h.plmobject.groupinfo.plmobject_url
if (list_display["display_document"] or list_display["display_part"]) and list_display["display_group"]:
history = sorted(chain(history_group, history_plmobject), key=lambda instance: instance.date, reverse=True)
elif list_display["display_group"]:
history = history_group
elif list_display["display_document"] or list_display["display_part"]:
history = history_plmobject
return history
comment_was_posted.connect(_save_comment_history)