Add docstrings for almost all models

Also adds docstring to some managers and model methods.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2018-11-24 22:56:35 -04:00
parent 8c98679687
commit b04b205fb6
19 changed files with 252 additions and 20 deletions

View File

@@ -70,6 +70,9 @@ class AccessControlList(models.Model):
)
def get_permission_titles(self):
"""
Returns the descriptibe labels for the permissions.
"""
result = ', '.join(
[force_text(permission) for permission in self.permissions.all()]
)

View File

@@ -19,6 +19,12 @@ from .search import cabinet_search # NOQA
@python_2_unicode_compatible
class Cabinet(MPTTModel):
"""
Model to store a hierarchical tree of document containers. Each container
can store an unlimited number of documents using an M2M field. Only
the top level container is can have an ACL. All child container's
access is delegated to their corresponding root container.
"""
parent = TreeForeignKey(
blank=True, db_index=True, null=True, on_delete=models.CASCADE,
related_name='children', to='self'
@@ -41,6 +47,10 @@ class Cabinet(MPTTModel):
return self.get_full_path()
def add_document(self, document, user=None):
"""
Add a document to a container. This can be done without using this
method but this method provides the event commit already coded.
"""
self.documents.add(document)
event_cabinets_add_document.commit(
action_object=self, actor=user, target=document
@@ -50,14 +60,26 @@ class Cabinet(MPTTModel):
return reverse('cabinets:cabinet_view', args=(self.pk,))
def get_document_count(self, user):
"""
Return numeric count of the total documents in a cabinet. The count
is filtered by access.
"""
return self.get_documents_queryset(user=user).count()
def get_documents_queryset(self, user):
"""
Provide a queryset of the documents in a cabinet. The queryset is
filtered by access.
"""
return AccessControlList.objects.filter_by_access(
permission_document_view, user, queryset=self.documents
)
def get_full_path(self):
"""
Returns a string that represents the path to the cabinet. The
path string starts from the root cabinet.
"""
result = []
for node in self.get_ancestors(include_self=True):
result.append(node.label)
@@ -65,17 +87,22 @@ class Cabinet(MPTTModel):
return ' / '.join(result)
def remove_document(self, document, user=None):
"""
Remove a document from a cabinet. This method provides the
corresponding event commit.
"""
self.documents.remove(document)
event_cabinets_remove_document.commit(
action_object=self, actor=user, target=document
)
def validate_unique(self, exclude=None):
# Explicit validation of uniqueness of parent+label as the provided
# unique_together check in Meta is not working for all 100% cases
# when there is a FK in the unique_together tuple
# https://code.djangoproject.com/ticket/1751
"""
Explicit validation of uniqueness of parent+label as the provided
unique_together check in Meta is not working for all 100% cases
when there is a FK in the unique_together tuple
https://code.djangoproject.com/ticket/1751
"""
with transaction.atomic():
if connection.vendor == 'oracle':
queryset = Cabinet.objects.filter(parent=self.parent, label=self.label)
@@ -102,6 +129,11 @@ class Cabinet(MPTTModel):
class DocumentCabinet(Cabinet):
"""
Represent a document's cabinet. This Model is a proxy model from Cabinet
and is used as an alias to map columns to it without having to map them
to the base Cabinet model.
"""
class Meta:
proxy = True
verbose_name = _('Document cabinet')

View File

@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class DocumentCheckout(models.Model):
"""
Model to store the state and information of a document checkout
Model to store the state and information of a document checkout.
"""
document = models.OneToOneField(
on_delete=models.CASCADE, to=Document, verbose_name=_('Document')
@@ -99,6 +99,9 @@ class DocumentCheckout(models.Model):
class NewVersionBlock(models.Model):
"""
Model to keep track of which documents have new version upload restricted.
"""
document = models.ForeignKey(
on_delete=models.CASCADE, to=Document, verbose_name=_('Document')
)

View File

@@ -20,6 +20,10 @@ def upload_to(instance, filename):
class ErrorLogEntry(models.Model):
"""
Class to store an error log for any object. Uses generic foreign keys to
reference the parent object.
"""
namespace = models.CharField(
max_length=128, verbose_name=_('Namespace')
)
@@ -46,6 +50,10 @@ class ErrorLogEntry(models.Model):
@python_2_unicode_compatible
class SharedUploadedFile(models.Model):
"""
Keep a database link to a stored file. Used to share files between code
that runs out of process.
"""
file = models.FileField(
storage=storage_sharedupload, upload_to=upload_to,
verbose_name=_('File')
@@ -76,6 +84,10 @@ class SharedUploadedFile(models.Model):
@python_2_unicode_compatible
class UserLocaleProfile(models.Model):
"""
Stores the locale preferences of an user. Stores timezone and language
at the moment.
"""
user = models.OneToOneField(
on_delete=models.CASCADE, related_name='locale_profile',
to=settings.AUTH_USER_MODEL, verbose_name=_('User')

View File

@@ -65,6 +65,9 @@ class Key(models.Model):
return '{} - {}'.format(self.key_id, self.user_id)
def clean(self):
"""
Validate the key before saving.
"""
import_results = gpg_backend.import_key(key_data=self.key_data)
if not import_results.count:
@@ -78,6 +81,9 @@ class Key(models.Model):
@property
def key_id(self):
"""
Short form key ID (using the first 8 characters).
"""
return self.fingerprint[-8:]
def save(self, *args, **kwargs):
@@ -101,13 +107,15 @@ class Key(models.Model):
super(Key, self).save(*args, **kwargs)
def sign_file(self, file_object, passphrase=None, clearsign=False, detached=False, binary=False, output=None):
# WARNING: using clearsign=True and subsequent decryption corrupts the
# file. Appears to be a problem in python-gnupg or gpg itself.
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=55647
# "The problems differ from run to run and file to
# file, and appear to be due to random data being inserted in the
# output data stream."
"""
Digitally sign a file
WARNING: using clearsign=True and subsequent decryption corrupts the
file. Appears to be a problem in python-gnupg or gpg itself.
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=55647
"The problems differ from run to run and file to
file, and appear to be due to random data being inserted in the
output data stream."
"""
file_sign_results = gpg_backend.sign_file(
file_object=file_object, key_data=self.key_data,
passphrase=passphrase, clearsign=clearsign, detached=detached,

View File

@@ -18,6 +18,9 @@ logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class Comment(models.Model):
"""
Model to store one comment per document per user per date & time.
"""
document = models.ForeignKey(
db_index=True, on_delete=models.CASCADE, related_name='comments',
to=Document, verbose_name=_('Document')

View File

@@ -77,6 +77,14 @@ class Index(models.Model):
)
def index_document(self, document):
"""
Method to start the indexing process for a document. The entire process
happens inside one transaction. The document is first removed from all
the index nodes to which it already belongs. The different index
templates that match this document's type are evaluated and for each
result a node is fetched or created and the document is added to that
node.
"""
logger.debug('Index; Indexing document: %s', document)
with transaction.atomic():
@@ -131,10 +139,18 @@ class Index(models.Model):
@property
def template_root(self):
"""
Return the root node for this index.
"""
return self.node_templates.get(parent=None)
class IndexInstance(Index):
"""
Model that represents an evaluated index. This is an index whose nodes
have been evaluated against a series of documents. If is a proxy model
at the moment.
"""
class Meta:
proxy = True
verbose_name = _('Index instance')
@@ -286,6 +302,12 @@ class IndexTemplateNode(MPTTModel):
@python_2_unicode_compatible
class IndexInstanceNode(MPTTModel):
"""
This model represent one instance node from a index template node. That is
a node template that has been evaluated against a document and the result
from that evaluation is this node's stored values. Instances of this
model also point to the original node template.
"""
parent = TreeForeignKey(
blank=True, null=True, on_delete=models.CASCADE, to='self',
)
@@ -311,6 +333,9 @@ class IndexInstanceNode(MPTTModel):
return self.value
def delete_empty(self):
"""
Method to delete all empty node instances in a recursive manner.
"""
# Prevent another process to delete this node.
try:
lock = locking_backend.acquire_lock(
@@ -374,6 +399,9 @@ class IndexInstanceNode(MPTTModel):
return 'indexing:index_instance_node_{}'.format(self.pk)
def index(self):
"""
Return's the index instance of this node instance.
"""
return IndexInstance.objects.get(pk=self.index_template_node.index.pk)
def remove_document(self, document):
@@ -399,6 +427,11 @@ class IndexInstanceNode(MPTTModel):
class DocumentIndexInstanceNode(IndexInstanceNode):
"""
Proxy model of node instance. It is used to represent the node instance
in which a document is currently located. It is used to aid in column
registration. The inherited methods of this model should not be used.
"""
objects = DocumentIndexInstanceNodeManager()
class Meta:

View File

@@ -11,6 +11,9 @@ from .managers import DocumentPageContentManager, DocumentTypeSettingsManager
@python_2_unicode_compatible
class DocumentPageContent(models.Model):
"""
This model store's the parsed content of a document page.
"""
document_page = models.OneToOneField(
on_delete=models.CASCADE, related_name='content', to=DocumentPage,
verbose_name=_('Document page')
@@ -33,6 +36,9 @@ class DocumentPageContent(models.Model):
class DocumentTypeSettings(models.Model):
"""
This model stores the parsing settings for a document type.
"""
document_type = models.OneToOneField(
on_delete=models.CASCADE, related_name='parsing_settings',
to=DocumentType, unique=True, verbose_name=_('Document type')
@@ -56,6 +62,10 @@ class DocumentTypeSettings(models.Model):
@python_2_unicode_compatible
class DocumentVersionParseError(models.Model):
"""
This module stores the errors captures when attempting to parse a
document version.
"""
document_version = models.ForeignKey(
on_delete=models.CASCADE, related_name='parsing_errors',
to=DocumentVersion, verbose_name=_('Document version')

View File

@@ -17,6 +17,9 @@ from .managers import (
@python_2_unicode_compatible
class StoredEventType(models.Model):
"""
Model to mirror the real event classes as database objects.
"""
name = models.CharField(
max_length=64, unique=True, verbose_name=_('Name')
)
@@ -42,6 +45,10 @@ class StoredEventType(models.Model):
@python_2_unicode_compatible
class EventSubscription(models.Model):
"""
This model stores the event subscriptions of an user for the entire
system.
"""
user = models.ForeignKey(
db_index=True, on_delete=models.CASCADE,
related_name='event_subscriptions', to=settings.AUTH_USER_MODEL,
@@ -64,6 +71,11 @@ class EventSubscription(models.Model):
@python_2_unicode_compatible
class Notification(models.Model):
"""
This model keeps track of the notifications for an user. Notifications are
created when an event to which this user has been subscribed, are
commited elsewhere in the system.
"""
user = models.ForeignKey(
db_index=True, on_delete=models.CASCADE,
related_name='notifications', to=settings.AUTH_USER_MODEL,

View File

@@ -16,6 +16,11 @@ from .managers import SmartLinkManager
@python_2_unicode_compatible
class SmartLink(models.Model):
"""
This model stores the basic fields for a smart link. Smart links allow
linking documents using a programmatic method of conditions that mirror
Django's database filter operations.
"""
label = models.CharField(
db_index=True, max_length=96, verbose_name=_('Label')
)
@@ -43,6 +48,10 @@ class SmartLink(models.Model):
return self.label
def get_dynamic_label(self, document):
"""
If the smart links was created using a template label instead of a
static label, resolve the template and return the result.
"""
if self.dynamic_label:
context = Context({'document': document})
try:
@@ -58,6 +67,10 @@ class SmartLink(models.Model):
return None
def get_linked_document_for(self, document):
"""
Execute the corresponding smart links conditions for the document
provided and return the resulting document queryset.
"""
if document.document_type.pk not in self.document_types.values_list('pk', flat=True):
raise Exception(
_(
@@ -100,6 +113,10 @@ class SmartLink(models.Model):
class ResolvedSmartLink(SmartLink):
"""
Proxy model to represent an already resolved smart link. Used for easier
colums registration.
"""
class Meta:
proxy = True
@@ -109,6 +126,10 @@ class ResolvedSmartLink(SmartLink):
@python_2_unicode_compatible
class SmartLinkCondition(models.Model):
"""
This model stores a single smart link condition. A smart link is a
collection of one of more smart link conditions.
"""
smart_link = models.ForeignKey(
on_delete=models.CASCADE, related_name='conditions', to=SmartLink,
verbose_name=_('Smart link')

View File

@@ -14,6 +14,11 @@ logger = logging.getLogger(__name__)
class LockManager(models.Manager):
def acquire_lock(self, name, timeout=None):
"""
Attempts to acquire a lock. Return a LockError is the lock is already
held by someone else or if is not possible to acquire the lock due to
database or operational errors.
"""
logger.debug('trying to acquire lock: %s', name)
lock = self.model(name=name, timeout=timeout)

View File

@@ -10,6 +10,9 @@ from .settings import setting_default_lock_timeout
@python_2_unicode_compatible
class Lock(models.Model):
"""
Model to provide distributed resource locking using the database.
"""
creation_datetime = models.DateTimeField(
auto_now_add=True, verbose_name=_('Creation datetime')
)
@@ -30,6 +33,9 @@ class Lock(models.Model):
return self.name
def release(self):
"""
Release a previously held lock.
"""
try:
lock = Lock.objects.get(
name=self.name, creation_datetime=self.creation_datetime

View File

@@ -33,6 +33,12 @@ class LogEntry(models.Model):
class UserMailer(models.Model):
"""
This model is used to create mailing profiles that can be used from inside
the system. These profiles differ from the system mailing profile in that
they can be created at runtime and can be assigned ACLs to restrict
their use.
"""
label = models.CharField(
max_length=128, unique=True, verbose_name=_('Label')
)
@@ -122,6 +128,9 @@ class UserMailer(models.Model):
self.error_log.all().delete()
def send_document(self, document, to, subject='', body='', as_attachment=False):
"""
Send a document using this user mailing profile.
"""
context_dictionary = {
'link': 'http://%s%s' % (
Site.objects.get_current().domain,
@@ -153,6 +162,10 @@ class UserMailer(models.Model):
)
def test(self, to):
"""
Send a test message to make sure the mailing profile settings are
correct.
"""
self.send(subject=_('Test email from Mayan EDMS'), to=to)

View File

@@ -40,7 +40,9 @@ def parser_choices():
@python_2_unicode_compatible
class MetadataType(models.Model):
"""
Define a type of metadata
Model to store a type of metadata. Metadata are user defined properties
that can be assigned a value for each document. Metadata types need
to be assigned to a document type before they can be used.
"""
name = models.CharField(
max_length=48,
@@ -127,6 +129,10 @@ class MetadataType(models.Model):
return MetadataType.comma_splitter(template.render(context=context))
def get_required_for(self, document_type):
"""
Return a queryset of metadata types that are required for the
specified document type.
"""
return document_type.metadata.filter(
required=True, metadata_type=self
).exists()
@@ -183,8 +189,8 @@ class MetadataType(models.Model):
@python_2_unicode_compatible
class DocumentMetadata(models.Model):
"""
Link a document to a specific instance of a metadata type with it's
current value
Model used to link an instance of a metadata type with a value to a
document.
"""
document = models.ForeignKey(
on_delete=models.CASCADE, related_name='metadata', to=Document,
@@ -218,9 +224,10 @@ class DocumentMetadata(models.Model):
def delete(self, enforce_required=True, *args, **kwargs):
"""
enforce_required prevents deletion of required metadata at the
model level. It used set to False when deleting document metadata
on document type change.
Delete a metadata from a document. enforce_required which defaults
to True, prevents deletion of required metadata at the model level.
It used set to False when deleting document metadata on document
type change.
"""
if enforce_required and self.metadata_type.pk in self.document.document_type.metadata.filter(required=True).values_list('metadata_type', flat=True):
raise ValidationError(
@@ -241,6 +248,10 @@ class DocumentMetadata(models.Model):
@property
def is_required(self):
"""
Return a boolean value of True of this metadata instance's parent type
is required for the stored document type.
"""
return self.metadata_type.get_required_for(
document_type=self.document.document_type
)
@@ -272,6 +283,10 @@ class DocumentMetadata(models.Model):
@python_2_unicode_compatible
class DocumentTypeMetadataType(models.Model):
"""
Model used to store the relationship between a metadata type and a
document type.
"""
document_type = models.ForeignKey(
on_delete=models.CASCADE, related_name='metadata', to=DocumentType,
verbose_name=_('Document type')

View File

@@ -9,6 +9,10 @@ from .managers import MessageManager
@python_2_unicode_compatible
class Message(models.Model):
"""
Model to store an information message that will be displayed at the login
screen. Messages can have an activation and deactivation date.
"""
label = models.CharField(
max_length=32, help_text=_('Short description of this message.'),
verbose_name=_('Label')

View File

@@ -13,7 +13,7 @@ from .managers import (
class DocumentTypeSettings(models.Model):
"""
Define for OCR for a specific document should behave
Model to store the OCR settings for a document type.
"""
document_type = models.OneToOneField(
on_delete=models.CASCADE, related_name='ocr_settings',
@@ -37,6 +37,9 @@ class DocumentTypeSettings(models.Model):
@python_2_unicode_compatible
class DocumentPageOCRContent(models.Model):
"""
This model stores the OCR results for a document page.
"""
document_page = models.OneToOneField(
on_delete=models.CASCADE, related_name='ocr_content',
to=DocumentPage, verbose_name=_('Document page')
@@ -59,6 +62,10 @@ class DocumentPageOCRContent(models.Model):
@python_2_unicode_compatible
class DocumentVersionOCRError(models.Model):
"""
This models keeps track of the errors captured during the OCR of a
document version.
"""
document_version = models.ForeignKey(
on_delete=models.CASCADE, related_name='ocr_errors',
to=DocumentVersion, verbose_name=_('Document version')

View File

@@ -16,6 +16,11 @@ logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class StoredPermission(models.Model):
"""
This model is the counterpart of the permissions.classes.Permission
class. Allows storing a database counterpart of a permission class.
It is used to store the permissions help by a role or in an ACL.
"""
namespace = models.CharField(max_length=64, verbose_name=_('Namespace'))
name = models.CharField(max_length=64, verbose_name=_('Name'))
@@ -34,9 +39,17 @@ class StoredPermission(models.Model):
return self.name
def get_volatile_permission_id(self):
"""
Return the identifier of the real permission class represented by
this model instance.
"""
return '{}.{}'.format(self.namespace, self.name)
def get_volatile_permission(self):
"""
Returns the real class of the permission represented by this model
instance.
"""
return Permission.get(
pk=self.get_volatile_permission_id(), proxy_only=True
)
@@ -45,6 +58,12 @@ class StoredPermission(models.Model):
return (self.namespace, self.name)
def requester_has_this(self, user):
"""
Helper method to check if an user has been granted this permission.
The check is done sequentially over all of the user's groups and
roles. The check is interrupted at the first positive result.
The check always returns True for superusers or staff users.
"""
if user.is_superuser or user.is_staff:
logger.debug(
'Permission "%s" granted to user "%s" as superuser or staff',
@@ -70,6 +89,13 @@ class StoredPermission(models.Model):
@python_2_unicode_compatible
class Role(models.Model):
"""
This model represents a Role. Roles are permission units. They are the
only object to which permissions can be granted. They are themselves
containers too, containing Groups, which are organization units. Roles
are the basic method to grant a permission to a group. Permissions granted
to a group using a role, are granted for the entire system.
"""
label = models.CharField(
max_length=64, unique=True, verbose_name=_('Label')
)

View File

@@ -19,6 +19,10 @@ from .widgets import widget_single_tag
@python_2_unicode_compatible
class Tag(models.Model):
"""
This model represents a binary property that can be applied to a document.
The tag can have a label and a color.
"""
label = models.CharField(
db_index=True, help_text=_(
'A short text used as the tag name.'
@@ -41,6 +45,9 @@ class Tag(models.Model):
return self.label
def attach_to(self, document, user=None):
"""
Attach a tag to a document and commit the corresponding event.
"""
self.documents.add(document)
event_tag_attach.commit(
action_object=self, actor=user, target=document
@@ -50,6 +57,10 @@ class Tag(models.Model):
return reverse('tags:tag_tagged_item_list', args=(str(self.pk),))
def get_document_count(self, user):
"""
Return the numeric count of documents that have this tag attached.
The count if filtered by access.
"""
queryset = AccessControlList.objects.filter_by_access(
permission_document_view, user, queryset=self.documents
)
@@ -61,6 +72,9 @@ class Tag(models.Model):
get_preview_widget.short_description = _('Preview')
def remove_from(self, document, user=None):
"""
Remove a tag from a document and commit the corresponding event.
"""
self.documents.remove(document)
event_tag_remove.commit(
action_object=self, actor=user, target=document

View File

@@ -8,6 +8,11 @@ from .managers import UserOptionsManager
class UserOptions(models.Model):
"""
This model stores administrative configurations for an user accounts.
At the moment it stores a boolean flag to restrict an user's
ability to change their password.
"""
user = models.OneToOneField(
on_delete=models.CASCADE, related_name='user_options',
to=settings.AUTH_USER_MODEL, unique=True, verbose_name=_('User')