241 lines
7.0 KiB
Python
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)
|