dms/dms/models.py

241 lines
7.0 KiB
Python

"""
This module defines the structure and properties of documents and versions.
"""
from django.db import models
from django.contrib.auth.models import Group, User
from closuretree.models import ClosureModel
import reversion
from process.models import Approval
from process.exceptions import ActorException
class Audience(ClosureModel):
name = models.CharField(max_length=50)
parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
group = models.ForeignKey(Group)
def __str__(self):
return self.name
class Meta:
ordering = ('name',)
class Site(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Meta:
ordering = ['name']
class Node(ClosureModel):
name = models.CharField(max_length=50)
acronym = models.CharField(max_length=20, null=True, blank=True)
parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
def __str__(self):
return self.name
class Keyword(ClosureModel):
name = models.CharField(max_length=255)
parent = models.ForeignKey('self', null=True, blank=True)
selectable = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class DocumentType(models.Model):
name = models.CharField(max_length=50)
level = models.IntegerField()
template = models.FileField(upload_to='documents_templates/', null=True, blank=True)
description = models.TextField(max_length=255, blank=True)
def __str__(self):
return self.name
class Meta:
ordering = ['level']
@reversion.register()
class Document(models.Model):
title = models.CharField(max_length=255)
overview = models.TextField(blank=True, null=True)
type = models.ForeignKey(DocumentType)
created_at = models.DateTimeField(auto_now_add=True)
manager = models.ForeignKey(User)
@property
def last_published_version(self):
try:
return self.versions.filter(is_published__isnull=True).first()
except Version.DoesNotExist:
return 'None'
except Exception:
return 'None'
@property
def last_working_version(self):
try:
return self.versions.filter(is_published__isnull=False).first()
except Version.DoesNotExist:
return 'None'
def create_new_version(self):
"""Creates a new version for this document.
By default, version 0.0 is created with the chosen template for this document.
:raises
IndexError if there is a current version still in draft
:returns
A new `Version` object if it needs to be created.
Raises an `IndexError` exception instead.
"""
latest_version = self.versions.last()
if not latest_version or latest_version.is_published:
latest_version = Version(
document= self,
file=self.type.template,
revision=1,
major=self.major,
)
latest_version.save()
return latest_version
if not latest_version.is_published:
raise IndexError('The latest version is still in draft.\n '
'Publish this first before creating a new one.')
@property
def major(self):
latest_version = self.versions.filter(is_published=True).last()
if latest_version:
return latest_version.major
return 0
@property
def revision(self):
latest_version = self.versions.last()
if latest_version:
return latest_version.revision
return 0
@reversion.create_revision()
def save(self, *args, **kwargs):
# todo : should create a default version based on the document type template.
super().save()
def __str__(self):
return self.title
@reversion.register()
class Version(models.Model):
"""A version represents the writing steps of a document.
System fields::
major (+Int)
minor (+Int)
is_published (bool)
created_at (DateTime)
Properties:
audiences
keywords
sites
nodes
restricted
Actors:
Validators
Writers
Reviewers
"""
# instance structure
document = models.ForeignKey(Document, related_name='versions')
file = models.FileField(upload_to='revisions/')
# system fields
major = models.PositiveIntegerField()
revision = models.PositiveIntegerField()
is_published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
# properties
keywords = models.ManyToManyField(Keyword, blank=True, related_name='versions')
audiences = models.ManyToManyField(Audience, blank=True, related_name='versions')
restricted = models.BooleanField(default=False)
sites = models.ManyToManyField(Site, blank=True)
nodes = models.ManyToManyField(Node, blank=True)
# actors
authors = models.ManyToManyField(User, related_name='versions_as_author')
reviewers = models.ManyToManyField(User, related_name='versions_as_reviewer')
validators = models.ManyToManyField(User, related_name='versions_as_validator')
@reversion.create_revision()
def publish(self):
"""Starts a new publication process, based on the people set as `validators`.
If the current version has not been published yet, this method checks that
there are people in the `validators` fields and assign a task to each one of them.
:exception
ActorException: when no validator has been set.
:returns
A new instance of type `process.Approval`.
"""
if not self.is_published:
if self.validators.count() == 0:
raise ActorException('There are no validators for this version. \n'
'Please, add at least one of them.')
return Approval.objects.create(
document_version=self
)
@reversion.create_revision()
def update(self, *args, **kwargs):
"""Update the current version by bumping its minor version by one.
This update can only occur when the current version has never been published.
:exception
ValueError when the current version has already been published.
:returns
super().save()"""
if not self.is_published:
self.revision = self.revision + 1
return super().save(*args, **kwargs)
raise ValueError('A published version cannot be updated.')
@reversion.create_revision()
def save(self, *args, **kwargs):
super().save()
class Meta:
unique_together = ('document', 'major')
@property
def status(self):
return 'Published' if self.is_published else 'Draft'
def __str__(self):
return '{} v{}.{} ({})'.format(self.document, self.major, self.revision, self.status)