Commit b8bedce5 authored by Giorgos Korfiatis's avatar Giorgos Korfiatis
Browse files

astakos: Improve logging for Projects and Applications

Introduce model ProjectLog as well as fields in ProjectApplication
in order to record all actions.
parent 6f974ec4
......@@ -26,7 +26,7 @@ Astakos
of its `reference' application (current definition). Lock Project rather
than Chain (the latter is semantically obsolete).
* Improve recording of membership actions.
* Improve recording of project, application, and membership actions.
Cyclades
--------
......
......@@ -769,7 +769,7 @@ def cancel_application(application_id, request_user=None, reason=""):
qh_release_pending_app(application.owner)
application.cancel()
application.cancel(actor=request_user, reason=reason)
logger.info("%s has been cancelled." % (application.log_display))
......@@ -783,7 +783,7 @@ def dismiss_application(application_id, request_user=None, reason=""):
(application.id, application.state_display()))
raise ProjectConflict(m)
application.dismiss()
application.dismiss(actor=request_user, reason=reason)
logger.info("%s has been dismissed." % (application.log_display))
......@@ -800,7 +800,7 @@ def deny_application(application_id, request_user=None, reason=""):
qh_release_pending_app(application.owner)
application.deny(reason)
application.deny(actor=request_user, reason=reason)
logger.info("%s has been denied with reason \"%s\"." %
(application.log_display, reason))
application_deny_notify(application)
......@@ -843,7 +843,7 @@ def approve_application(app_id, request_user=None, reason=""):
get_users_for_update(uids_to_sync)
qh_release_pending_app(owner, locked=True)
application.approve(reason)
application.approve(actor=request_user, reason=reason)
project.application = application
project.name = application.name
project.save()
......@@ -864,31 +864,31 @@ def check_expiration(execute=False):
return [project.expiration_info() for project in expired]
def terminate(project_id, request_user=None):
def terminate(project_id, request_user=None, reason=None):
project = get_project_for_update(project_id)
project_check_allowed(project, request_user, level=ADMIN_LEVEL)
checkAlive(project)
project.terminate()
project.terminate(actor=request_user, reason=reason)
qh_sync_project(project)
logger.info("%s has been terminated." % (project))
project_termination_notify(project)
def suspend(project_id, request_user=None):
def suspend(project_id, request_user=None, reason=None):
project = get_project_for_update(project_id)
project_check_allowed(project, request_user, level=ADMIN_LEVEL)
checkAlive(project)
project.suspend()
project.suspend(actor=request_user, reason=reason)
qh_sync_project(project)
logger.info("%s has been suspended." % (project))
project_suspension_notify(project)
def resume(project_id, request_user=None):
def resume(project_id, request_user=None, reason=None):
project = get_project_for_update(project_id)
project_check_allowed(project, request_user, level=ADMIN_LEVEL)
......@@ -896,7 +896,7 @@ def resume(project_id, request_user=None):
m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.id
raise ProjectConflict(m)
project.resume()
project.resume(actor=request_user, reason=reason)
qh_sync_project(project)
logger.info("%s has been unsuspended." % (project))
......
......@@ -206,9 +206,9 @@ def project_fields(project, pending_app):
('request end date', app.end_date),
])
deact_date = project.deactivation_date
if deact_date is not None:
d['deactivation date'] = deact_date
deact = project.last_deactivation()
if deact is not None:
d['deactivation date'] = deact.date
mem_limit = app.limit_on_members_number
mem_limit_show = mem_limit if mem_limit is not None else "unlimited"
......
This diff is collapsed.
......@@ -1359,6 +1359,12 @@ class ProjectApplication(models.Model):
issue_date = models.DateTimeField(auto_now_add=True)
response_date = models.DateTimeField(null=True, blank=True)
response = models.TextField(null=True, blank=True)
response_actor = models.ForeignKey(AstakosUser, null=True,
related_name='responded_apps')
waive_date = models.DateTimeField(null=True, blank=True)
waive_reason = models.TextField(null=True, blank=True)
waive_actor = models.ForeignKey(AstakosUser, null=True,
related_name='waived_apps')
objects = ProjectApplicationManager()
......@@ -1428,31 +1434,37 @@ class ProjectApplication(models.Model):
def can_cancel(self):
return self.state == self.PENDING
def cancel(self):
def cancel(self, actor=None, reason=None):
if not self.can_cancel():
m = _("cannot cancel: application '%s' in state '%s'") % (
self.id, self.state)
raise AssertionError(m)
self.state = self.CANCELLED
self.waive_date = datetime.now()
self.waive_reason = reason
self.waive_actor = actor
self.save()
def can_dismiss(self):
return self.state == self.DENIED
def dismiss(self):
def dismiss(self, actor=None, reason=None):
if not self.can_dismiss():
m = _("cannot dismiss: application '%s' in state '%s'") % (
self.id, self.state)
raise AssertionError(m)
self.state = self.DISMISSED
self.waive_date = datetime.now()
self.waive_reason = reason
self.waive_actor = actor
self.save()
def can_deny(self):
return self.state == self.PENDING
def deny(self, reason):
def deny(self, actor=None, reason=None):
if not self.can_deny():
m = _("cannot deny: application '%s' in state '%s'") % (
self.id, self.state)
......@@ -1461,12 +1473,13 @@ class ProjectApplication(models.Model):
self.state = self.DENIED
self.response_date = datetime.now()
self.response = reason
self.response_actor = actor
self.save()
def can_approve(self):
return self.state == self.PENDING
def approve(self, reason):
def approve(self, actor=None, reason=None):
if not self.can_approve():
m = _("cannot approve: project '%s' in state '%s'") % (
self.name, self.state)
......@@ -1476,6 +1489,7 @@ class ProjectApplication(models.Model):
self.state = self.APPROVED
self.response_date = now
self.response = reason
self.response_actor = actor
self.save()
@property
......@@ -1613,15 +1627,11 @@ class Project(models.Model):
application = models.OneToOneField(
ProjectApplication,
related_name='project')
last_approval_date = models.DateTimeField(null=True)
members = models.ManyToManyField(
AstakosUser,
through='ProjectMembership')
deactivation_reason = models.CharField(max_length=255, null=True)
deactivation_date = models.DateTimeField(null=True)
creation_date = models.DateTimeField(auto_now_add=True)
name = models.CharField(
max_length=80,
......@@ -1633,6 +1643,8 @@ class Project(models.Model):
SUSPENDED = 10
TERMINATED = 100
DEACTIVATED_STATES = [SUSPENDED, TERMINATED]
state = models.IntegerField(default=NORMAL,
db_index=True)
......@@ -1734,6 +1746,13 @@ class Project(models.Model):
return (str(self.id), self.name, self.state_display(),
str(self.application.end_date))
def last_deactivation(self):
objs = self.log.filter(to_state__in=self.DEACTIVATED_STATES)
ls = objs.order_by("-date")
if not ls:
return None
return ls[0]
def is_deactivated(self, reason=None):
if reason is not None:
return self.state == reason
......@@ -1748,33 +1767,30 @@ class Project(models.Model):
### Deactivation calls
def terminate(self):
self.deactivation_reason = 'TERMINATED'
self.deactivation_date = datetime.now()
self.state = self.TERMINATED
self.name = None
self.save()
def _log_create(self, from_state, to_state, actor=None, reason=None,
comments=None):
now = datetime.now()
self.log.create(from_state=from_state, to_state=to_state, date=now,
actor=actor, reason=reason, comments=comments)
def suspend(self):
self.deactivation_reason = 'SUSPENDED'
self.deactivation_date = datetime.now()
self.state = self.SUSPENDED
def set_state(self, to_state, actor=None, reason=None, comments=None):
self._log_create(self.state, to_state, actor=actor, reason=reason,
comments=comments)
self.state = to_state
self.save()
def resume(self):
self.deactivation_reason = None
self.deactivation_date = None
self.state = self.NORMAL
def terminate(self, actor=None, reason=None):
self.set_state(self.TERMINATED, actor=actor, reason=reason)
self.name = None
self.save()
### Logical checks
def suspend(self, actor=None, reason=None):
self.set_state(self.SUSPENDED, actor=actor, reason=reason)
def is_inconsistent(self):
now = datetime.now()
dates = [self.creation_date,
self.last_approval_date,
self.deactivation_date]
return any([date > now for date in dates])
def resume(self, actor=None, reason=None):
self.set_state(self.NORMAL, actor=actor, reason=reason)
### Logical checks
@property
def is_alive(self):
......@@ -1813,6 +1829,26 @@ class Project(models.Model):
return [m.person for m in self.approved_memberships]
class ProjectLogManager(models.Manager):
def last_deactivations(self, projects):
logs = self.filter(
project__in=projects,
to_state__in=Project.DEACTIVATED_STATES).order_by("-date")
return first_of_group(lambda l: l.project_id, logs)
class ProjectLog(models.Model):
project = models.ForeignKey(Project, related_name="log")
from_state = models.IntegerField(null=True)
to_state = models.IntegerField()
date = models.DateTimeField()
actor = models.ForeignKey(AstakosUser, null=True)
reason = models.TextField(null=True)
comments = models.TextField(null=True)
objects = ProjectLogManager()
class ProjectMembershipManager(ForUpdateManager):
def any_accepted(self):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment