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): def get_permission_titles(self):
"""
Returns the descriptibe labels for the permissions.
"""
result = ', '.join( result = ', '.join(
[force_text(permission) for permission in self.permissions.all()] [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 @python_2_unicode_compatible
class Cabinet(MPTTModel): 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( parent = TreeForeignKey(
blank=True, db_index=True, null=True, on_delete=models.CASCADE, blank=True, db_index=True, null=True, on_delete=models.CASCADE,
related_name='children', to='self' related_name='children', to='self'
@@ -41,6 +47,10 @@ class Cabinet(MPTTModel):
return self.get_full_path() return self.get_full_path()
def add_document(self, document, user=None): 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) self.documents.add(document)
event_cabinets_add_document.commit( event_cabinets_add_document.commit(
action_object=self, actor=user, target=document action_object=self, actor=user, target=document
@@ -50,14 +60,26 @@ class Cabinet(MPTTModel):
return reverse('cabinets:cabinet_view', args=(self.pk,)) return reverse('cabinets:cabinet_view', args=(self.pk,))
def get_document_count(self, user): 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() return self.get_documents_queryset(user=user).count()
def get_documents_queryset(self, user): 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( return AccessControlList.objects.filter_by_access(
permission_document_view, user, queryset=self.documents permission_document_view, user, queryset=self.documents
) )
def get_full_path(self): def get_full_path(self):
"""
Returns a string that represents the path to the cabinet. The
path string starts from the root cabinet.
"""
result = [] result = []
for node in self.get_ancestors(include_self=True): for node in self.get_ancestors(include_self=True):
result.append(node.label) result.append(node.label)
@@ -65,17 +87,22 @@ class Cabinet(MPTTModel):
return ' / '.join(result) return ' / '.join(result)
def remove_document(self, document, user=None): def remove_document(self, document, user=None):
"""
Remove a document from a cabinet. This method provides the
corresponding event commit.
"""
self.documents.remove(document) self.documents.remove(document)
event_cabinets_remove_document.commit( event_cabinets_remove_document.commit(
action_object=self, actor=user, target=document action_object=self, actor=user, target=document
) )
def validate_unique(self, exclude=None): 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 Explicit validation of uniqueness of parent+label as the provided
# when there is a FK in the unique_together tuple unique_together check in Meta is not working for all 100% cases
# https://code.djangoproject.com/ticket/1751 when there is a FK in the unique_together tuple
https://code.djangoproject.com/ticket/1751
"""
with transaction.atomic(): with transaction.atomic():
if connection.vendor == 'oracle': if connection.vendor == 'oracle':
queryset = Cabinet.objects.filter(parent=self.parent, label=self.label) queryset = Cabinet.objects.filter(parent=self.parent, label=self.label)
@@ -102,6 +129,11 @@ class Cabinet(MPTTModel):
class DocumentCabinet(Cabinet): 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: class Meta:
proxy = True proxy = True
verbose_name = _('Document cabinet') verbose_name = _('Document cabinet')

View File

@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
@python_2_unicode_compatible @python_2_unicode_compatible
class DocumentCheckout(models.Model): 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( document = models.OneToOneField(
on_delete=models.CASCADE, to=Document, verbose_name=_('Document') on_delete=models.CASCADE, to=Document, verbose_name=_('Document')
@@ -99,6 +99,9 @@ class DocumentCheckout(models.Model):
class NewVersionBlock(models.Model): class NewVersionBlock(models.Model):
"""
Model to keep track of which documents have new version upload restricted.
"""
document = models.ForeignKey( document = models.ForeignKey(
on_delete=models.CASCADE, to=Document, verbose_name=_('Document') 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 ErrorLogEntry(models.Model):
"""
Class to store an error log for any object. Uses generic foreign keys to
reference the parent object.
"""
namespace = models.CharField( namespace = models.CharField(
max_length=128, verbose_name=_('Namespace') max_length=128, verbose_name=_('Namespace')
) )
@@ -46,6 +50,10 @@ class ErrorLogEntry(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class SharedUploadedFile(models.Model): 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( file = models.FileField(
storage=storage_sharedupload, upload_to=upload_to, storage=storage_sharedupload, upload_to=upload_to,
verbose_name=_('File') verbose_name=_('File')
@@ -76,6 +84,10 @@ class SharedUploadedFile(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class UserLocaleProfile(models.Model): class UserLocaleProfile(models.Model):
"""
Stores the locale preferences of an user. Stores timezone and language
at the moment.
"""
user = models.OneToOneField( user = models.OneToOneField(
on_delete=models.CASCADE, related_name='locale_profile', on_delete=models.CASCADE, related_name='locale_profile',
to=settings.AUTH_USER_MODEL, verbose_name=_('User') 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) return '{} - {}'.format(self.key_id, self.user_id)
def clean(self): def clean(self):
"""
Validate the key before saving.
"""
import_results = gpg_backend.import_key(key_data=self.key_data) import_results = gpg_backend.import_key(key_data=self.key_data)
if not import_results.count: if not import_results.count:
@@ -78,6 +81,9 @@ class Key(models.Model):
@property @property
def key_id(self): def key_id(self):
"""
Short form key ID (using the first 8 characters).
"""
return self.fingerprint[-8:] return self.fingerprint[-8:]
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@@ -101,13 +107,15 @@ class Key(models.Model):
super(Key, self).save(*args, **kwargs) super(Key, self).save(*args, **kwargs)
def sign_file(self, file_object, passphrase=None, clearsign=False, detached=False, binary=False, output=None): 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. Digitally sign a file
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=55647 WARNING: using clearsign=True and subsequent decryption corrupts the
# "The problems differ from run to run and file to file. Appears to be a problem in python-gnupg or gpg itself.
# file, and appear to be due to random data being inserted in the https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=55647
# output data stream." "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_sign_results = gpg_backend.sign_file(
file_object=file_object, key_data=self.key_data, file_object=file_object, key_data=self.key_data,
passphrase=passphrase, clearsign=clearsign, detached=detached, passphrase=passphrase, clearsign=clearsign, detached=detached,

View File

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

View File

@@ -77,6 +77,14 @@ class Index(models.Model):
) )
def index_document(self, document): 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) logger.debug('Index; Indexing document: %s', document)
with transaction.atomic(): with transaction.atomic():
@@ -131,10 +139,18 @@ class Index(models.Model):
@property @property
def template_root(self): def template_root(self):
"""
Return the root node for this index.
"""
return self.node_templates.get(parent=None) return self.node_templates.get(parent=None)
class IndexInstance(Index): 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: class Meta:
proxy = True proxy = True
verbose_name = _('Index instance') verbose_name = _('Index instance')
@@ -286,6 +302,12 @@ class IndexTemplateNode(MPTTModel):
@python_2_unicode_compatible @python_2_unicode_compatible
class IndexInstanceNode(MPTTModel): 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( parent = TreeForeignKey(
blank=True, null=True, on_delete=models.CASCADE, to='self', blank=True, null=True, on_delete=models.CASCADE, to='self',
) )
@@ -311,6 +333,9 @@ class IndexInstanceNode(MPTTModel):
return self.value return self.value
def delete_empty(self): def delete_empty(self):
"""
Method to delete all empty node instances in a recursive manner.
"""
# Prevent another process to delete this node. # Prevent another process to delete this node.
try: try:
lock = locking_backend.acquire_lock( lock = locking_backend.acquire_lock(
@@ -374,6 +399,9 @@ class IndexInstanceNode(MPTTModel):
return 'indexing:index_instance_node_{}'.format(self.pk) return 'indexing:index_instance_node_{}'.format(self.pk)
def index(self): def index(self):
"""
Return's the index instance of this node instance.
"""
return IndexInstance.objects.get(pk=self.index_template_node.index.pk) return IndexInstance.objects.get(pk=self.index_template_node.index.pk)
def remove_document(self, document): def remove_document(self, document):
@@ -399,6 +427,11 @@ class IndexInstanceNode(MPTTModel):
class DocumentIndexInstanceNode(IndexInstanceNode): 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() objects = DocumentIndexInstanceNodeManager()
class Meta: class Meta:

View File

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

View File

@@ -17,6 +17,9 @@ from .managers import (
@python_2_unicode_compatible @python_2_unicode_compatible
class StoredEventType(models.Model): class StoredEventType(models.Model):
"""
Model to mirror the real event classes as database objects.
"""
name = models.CharField( name = models.CharField(
max_length=64, unique=True, verbose_name=_('Name') max_length=64, unique=True, verbose_name=_('Name')
) )
@@ -42,6 +45,10 @@ class StoredEventType(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class EventSubscription(models.Model): class EventSubscription(models.Model):
"""
This model stores the event subscriptions of an user for the entire
system.
"""
user = models.ForeignKey( user = models.ForeignKey(
db_index=True, on_delete=models.CASCADE, db_index=True, on_delete=models.CASCADE,
related_name='event_subscriptions', to=settings.AUTH_USER_MODEL, related_name='event_subscriptions', to=settings.AUTH_USER_MODEL,
@@ -64,6 +71,11 @@ class EventSubscription(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class Notification(models.Model): 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( user = models.ForeignKey(
db_index=True, on_delete=models.CASCADE, db_index=True, on_delete=models.CASCADE,
related_name='notifications', to=settings.AUTH_USER_MODEL, related_name='notifications', to=settings.AUTH_USER_MODEL,

View File

@@ -16,6 +16,11 @@ from .managers import SmartLinkManager
@python_2_unicode_compatible @python_2_unicode_compatible
class SmartLink(models.Model): 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( label = models.CharField(
db_index=True, max_length=96, verbose_name=_('Label') db_index=True, max_length=96, verbose_name=_('Label')
) )
@@ -43,6 +48,10 @@ class SmartLink(models.Model):
return self.label return self.label
def get_dynamic_label(self, document): 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: if self.dynamic_label:
context = Context({'document': document}) context = Context({'document': document})
try: try:
@@ -58,6 +67,10 @@ class SmartLink(models.Model):
return None return None
def get_linked_document_for(self, document): 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): if document.document_type.pk not in self.document_types.values_list('pk', flat=True):
raise Exception( raise Exception(
_( _(
@@ -100,6 +113,10 @@ class SmartLink(models.Model):
class ResolvedSmartLink(SmartLink): class ResolvedSmartLink(SmartLink):
"""
Proxy model to represent an already resolved smart link. Used for easier
colums registration.
"""
class Meta: class Meta:
proxy = True proxy = True
@@ -109,6 +126,10 @@ class ResolvedSmartLink(SmartLink):
@python_2_unicode_compatible @python_2_unicode_compatible
class SmartLinkCondition(models.Model): 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( smart_link = models.ForeignKey(
on_delete=models.CASCADE, related_name='conditions', to=SmartLink, on_delete=models.CASCADE, related_name='conditions', to=SmartLink,
verbose_name=_('Smart link') verbose_name=_('Smart link')

View File

@@ -14,6 +14,11 @@ logger = logging.getLogger(__name__)
class LockManager(models.Manager): class LockManager(models.Manager):
def acquire_lock(self, name, timeout=None): 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) logger.debug('trying to acquire lock: %s', name)
lock = self.model(name=name, timeout=timeout) lock = self.model(name=name, timeout=timeout)

View File

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

View File

@@ -33,6 +33,12 @@ class LogEntry(models.Model):
class UserMailer(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( label = models.CharField(
max_length=128, unique=True, verbose_name=_('Label') max_length=128, unique=True, verbose_name=_('Label')
) )
@@ -122,6 +128,9 @@ class UserMailer(models.Model):
self.error_log.all().delete() self.error_log.all().delete()
def send_document(self, document, to, subject='', body='', as_attachment=False): def send_document(self, document, to, subject='', body='', as_attachment=False):
"""
Send a document using this user mailing profile.
"""
context_dictionary = { context_dictionary = {
'link': 'http://%s%s' % ( 'link': 'http://%s%s' % (
Site.objects.get_current().domain, Site.objects.get_current().domain,
@@ -153,6 +162,10 @@ class UserMailer(models.Model):
) )
def test(self, to): 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) self.send(subject=_('Test email from Mayan EDMS'), to=to)

View File

@@ -40,7 +40,9 @@ def parser_choices():
@python_2_unicode_compatible @python_2_unicode_compatible
class MetadataType(models.Model): 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( name = models.CharField(
max_length=48, max_length=48,
@@ -127,6 +129,10 @@ class MetadataType(models.Model):
return MetadataType.comma_splitter(template.render(context=context)) return MetadataType.comma_splitter(template.render(context=context))
def get_required_for(self, document_type): 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( return document_type.metadata.filter(
required=True, metadata_type=self required=True, metadata_type=self
).exists() ).exists()
@@ -183,8 +189,8 @@ class MetadataType(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class DocumentMetadata(models.Model): class DocumentMetadata(models.Model):
""" """
Link a document to a specific instance of a metadata type with it's Model used to link an instance of a metadata type with a value to a
current value document.
""" """
document = models.ForeignKey( document = models.ForeignKey(
on_delete=models.CASCADE, related_name='metadata', to=Document, 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): def delete(self, enforce_required=True, *args, **kwargs):
""" """
enforce_required prevents deletion of required metadata at the Delete a metadata from a document. enforce_required which defaults
model level. It used set to False when deleting document metadata to True, prevents deletion of required metadata at the model level.
on document type change. 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): 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( raise ValidationError(
@@ -241,6 +248,10 @@ class DocumentMetadata(models.Model):
@property @property
def is_required(self): 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( return self.metadata_type.get_required_for(
document_type=self.document.document_type document_type=self.document.document_type
) )
@@ -272,6 +283,10 @@ class DocumentMetadata(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class DocumentTypeMetadataType(models.Model): class DocumentTypeMetadataType(models.Model):
"""
Model used to store the relationship between a metadata type and a
document type.
"""
document_type = models.ForeignKey( document_type = models.ForeignKey(
on_delete=models.CASCADE, related_name='metadata', to=DocumentType, on_delete=models.CASCADE, related_name='metadata', to=DocumentType,
verbose_name=_('Document type') verbose_name=_('Document type')

View File

@@ -9,6 +9,10 @@ from .managers import MessageManager
@python_2_unicode_compatible @python_2_unicode_compatible
class Message(models.Model): 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( label = models.CharField(
max_length=32, help_text=_('Short description of this message.'), max_length=32, help_text=_('Short description of this message.'),
verbose_name=_('Label') verbose_name=_('Label')

View File

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

View File

@@ -16,6 +16,11 @@ logger = logging.getLogger(__name__)
@python_2_unicode_compatible @python_2_unicode_compatible
class StoredPermission(models.Model): 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')) namespace = models.CharField(max_length=64, verbose_name=_('Namespace'))
name = models.CharField(max_length=64, verbose_name=_('Name')) name = models.CharField(max_length=64, verbose_name=_('Name'))
@@ -34,9 +39,17 @@ class StoredPermission(models.Model):
return self.name return self.name
def get_volatile_permission_id(self): 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) return '{}.{}'.format(self.namespace, self.name)
def get_volatile_permission(self): def get_volatile_permission(self):
"""
Returns the real class of the permission represented by this model
instance.
"""
return Permission.get( return Permission.get(
pk=self.get_volatile_permission_id(), proxy_only=True pk=self.get_volatile_permission_id(), proxy_only=True
) )
@@ -45,6 +58,12 @@ class StoredPermission(models.Model):
return (self.namespace, self.name) return (self.namespace, self.name)
def requester_has_this(self, user): 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: if user.is_superuser or user.is_staff:
logger.debug( logger.debug(
'Permission "%s" granted to user "%s" as superuser or staff', 'Permission "%s" granted to user "%s" as superuser or staff',
@@ -70,6 +89,13 @@ class StoredPermission(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class Role(models.Model): 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( label = models.CharField(
max_length=64, unique=True, verbose_name=_('Label') max_length=64, unique=True, verbose_name=_('Label')
) )

View File

@@ -19,6 +19,10 @@ from .widgets import widget_single_tag
@python_2_unicode_compatible @python_2_unicode_compatible
class Tag(models.Model): 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( label = models.CharField(
db_index=True, help_text=_( db_index=True, help_text=_(
'A short text used as the tag name.' 'A short text used as the tag name.'
@@ -41,6 +45,9 @@ class Tag(models.Model):
return self.label return self.label
def attach_to(self, document, user=None): def attach_to(self, document, user=None):
"""
Attach a tag to a document and commit the corresponding event.
"""
self.documents.add(document) self.documents.add(document)
event_tag_attach.commit( event_tag_attach.commit(
action_object=self, actor=user, target=document 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),)) return reverse('tags:tag_tagged_item_list', args=(str(self.pk),))
def get_document_count(self, user): 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( queryset = AccessControlList.objects.filter_by_access(
permission_document_view, user, queryset=self.documents permission_document_view, user, queryset=self.documents
) )
@@ -61,6 +72,9 @@ class Tag(models.Model):
get_preview_widget.short_description = _('Preview') get_preview_widget.short_description = _('Preview')
def remove_from(self, document, user=None): def remove_from(self, document, user=None):
"""
Remove a tag from a document and commit the corresponding event.
"""
self.documents.remove(document) self.documents.remove(document)
event_tag_remove.commit( event_tag_remove.commit(
action_object=self, actor=user, target=document action_object=self, actor=user, target=document

View File

@@ -8,6 +8,11 @@ from .managers import UserOptionsManager
class UserOptions(models.Model): 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( user = models.OneToOneField(
on_delete=models.CASCADE, related_name='user_options', on_delete=models.CASCADE, related_name='user_options',
to=settings.AUTH_USER_MODEL, unique=True, verbose_name=_('User') to=settings.AUTH_USER_MODEL, unique=True, verbose_name=_('User')