Languages

Previous versions

1.2
1.1

Source code for openPLM.plmapp.models.document

import os
import string
import random
import hashlib
from django.utils import timezone

from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.core.files.storage import FileSystemStorage
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext_noop
from django.utils.functional import memoize

from openPLM.plmapp.files.formats import native_to_standards
from openPLM.plmapp.utils import memoize_noarg

from .lifecycle import Lifecycle
from .plmobject import (PLMObject, get_all_subclasses,
        get_all_subclasses_with_level)

# document stuff
class DocumentStorage(FileSystemStorage):
[docs] """ File system storage which stores files with a specific name """ def get_available_name(self, name):
[docs] """ Returns a path for a file *name*, the path always refers to a file which does not already exist. The path is computed as follow: #. a directory which name is the last extension of *name*. For example, it is :file:`.gz` if *name* is :file:`.a.tar.gz`. If *name* does not have an extension, the directory is :file:`.no_ext/`. #. a file name with 4 parts: #. the md5 sum of *name* #. a dash separator: ``-`` #. a random part with 7 characters in ``[a-z0-9]`` #. the extension, like :file:`.gz` For example, if *name* is :file:`.my_file.tar.gz`, a possible output is: :file:`.gz/c7bfe8d00ea6e7138215ebfafff187af-jj6789g.gz` If *name* is :file:`.my_file`, a possible output is: :file:`.no_ext/59c211e8fc0f14b21c78c87eafe1ab72-dhh5555` """ def rand(): r = "" for i in xrange(7): r += random.choice(string.ascii_lowercase + string.digits) return r basename = os.path.basename(name) base, ext = os.path.splitext(basename) ext2 = ext.lstrip(".").lower() or "no_ext" md5 = hashlib.md5() md5.update(basename) md5_value = md5.hexdigest() + "-%s" + ext path = os.path.join(ext2, md5_value % rand()) while os.path.exists(os.path.join(self.location, path)): path = os.path.join(ext2, md5_value % rand()) return path #: :class:`.DocumentStorage` instance which stores files in :const:`.settings.DOCUMENTS_DIR` docfs = DocumentStorage(location=settings.DOCUMENTS_DIR)
#: :class:`.FileSystemStorage` instance which stores thumbnails in :const:`.settings.THUMBNAILS_DIR` thumbnailfs = FileSystemStorage(location=settings.THUMBNAILS_DIR, base_url=settings.THUMBNAILS_URL) class DocumentFile(models.Model):
[docs] """ .. versionchanged:: 1.2 New attributes: :attr:`.ctime`, :attr:`.end_time`, :attr:`.deleted`, :attr:`.revision`, :attr:`.previous_revision`, :attr:`.last_revision` Model which stores informations of a file bounded to a :class:`.Document` :model attributes: .. attribute:: filename original filename .. attribute:: file file stored in :obj:`.docfs` .. attribute:: size size of the file in Byte .. attribute:: locked True if the file is locked .. attribute:: locker :class:`~django.contrib.auth.models.User` who locked the file, None, if the file is not locked .. attribute:: document :class:`.Document` bounded to the file (required) .. attribute:: ctime date of creation of the document file .. attribute:: end_time date of creation of the next revision or None if it is the last revision .. attribute:: revision revision number (starts at 1) .. attribute:: previous_revision :class:`.DocumentFile` or None if it's the first revision .. attribute:: last_revision foreign key to the last revision (:class:`.DocumentFile`) or None it it'is the last_revision .. attribute:: deleted True if the file has been physically removed """ class Meta: app_label = "plmapp" filename = models.CharField(max_length=200) file = models.FileField(upload_to=".", storage=docfs) size = models.PositiveIntegerField() thumbnail = models.ImageField(upload_to=".", storage=thumbnailfs, blank=True, null=True) locked = models.BooleanField(default=lambda: False) locker = models.ForeignKey(User, null=True, blank=True, default=lambda: None) document = models.ForeignKey('Document') deprecated = models.BooleanField(default=lambda: False) ctime = models.DateTimeField(auto_now_add=False, default=timezone.now) end_time = models.DateTimeField(blank=True, null=True, default=lambda: None) deleted = models.BooleanField(default=False) revision = models.IntegerField(default=1) previous_revision = models.OneToOneField('self', related_name="next_revision", default=None, null=True) last_revision = models.ForeignKey('self', related_name="older_files", default=None, null=True) @property def native_related(self): def checkout_valid(self):
[docs] """ Returns False if DocumentFile has a native related *locked* file and :const:`.settings.ENABLE_NATIVE_FILE_MANAGEMENT` is True. """ if getattr(settings, 'ENABLE_NATIVE_FILE_MANAGEMENT', False): name, ext = os.path.splitext(self.filename) ext = ext.lower() doc_files = DocumentFile.objects.filter(document__id=self.document_id, locked=True)\ .exclude(deprecated=True, id=self.id).values_list("filename", flat=True) for filename in doc_files: native, native_ext = os.path.splitext(filename) if native == name and ext in native_to_standards[native_ext.lower()]: return False return True def __unicode__(self):
return u"DocumentFile<%s, %s>" % (self.filename, self.document) class PrivateFile(models.Model):
[docs] """ .. versionadded:: 1.2 Model which stores informations of a private file only readable by its creator. There are no revision, locker, deleted or similar attributes. A private file is not shared, and it is temporary. It is created to store a file before a document creation. Private files are created when a user uploads files and *then* creates a document containing these files. :model attributes: .. attribute:: filename original filename .. attribute:: file file stored in :obj:`.docfs` .. attribute:: size size of the file in Byte .. attribute:: creator :class:`~django.contrib.auth.models.User` who created the file, .. attribute:: ctime date of creation of the file """ class Meta: app_label = "plmapp" filename = models.CharField(max_length=200) file = models.FileField(upload_to=".", storage=docfs) size = models.PositiveIntegerField() creator = models.ForeignKey(User, related_name="files") ctime = models.DateTimeField(auto_now_add=False, default=timezone.now) def __unicode__(self): return u"PrivateFile<%s, %s>" % (self.filename, self.creator) class Document(PLMObject):
[docs] """ Model for documents """ class Meta: app_label = "plmapp" ACCEPT_FILES = True template = models.ForeignKey('self', related_name='tpl_instances', null=True, default=None, editable=False) @property def files(self):
[docs] "Queryset of all non deprecated :class:`.DocumentFile` linked to self" return self.documentfile_set.exclude(deprecated=True) @property
def deprecated_files(self):
[docs] "Queryset of all deprecated :class:`.DocumentFile` linked to self" return self.documentfile_set.filter(deprecated=True) def get_content_and_size(self, doc_file):
return open(doc_file.file.path, "rb"), doc_file.file.size def is_promotable(self):
[docs] """ Returns True if the object is promotable. A document is promotable if there is a next state in its lifecycle and if it has at least one file and if none of its files are locked. """ if not self._is_promotable(): return False if not self.files.exists(): self._promotion_errors.append(_("This document has no files.")) return False if self.files.filter(locked=True).exists(): self._promotion_errors.append(_("Some files are locked.")) return False return True @property
def menu_items(self): items = list(super(Document, self).menu_items) items.insert(0, ugettext_noop("files")) items.append(ugettext_noop("parts")) return items @property def is_part(self): return False @property def is_document(self): return True @classmethod def get_creation_score(cls, files):
[docs] """ .. versionadded:: 2.0 Returns a score (an integer) computed from *files*. *files* is a list of :class:`PrivateFile` uploaded by a user who wants to create a document with all files. The class which returns the highest score is chosen has the default type after an upload. The default implementation returns 10 if the current class is *Document* and 0 otherwise. For example, :class:`~Document3D` returns 50 if a CAD file has been uploaded. Document3D is so preferred when a STEP file is uploaded. .. seealso:: :func:`get_best_document_type` """ if cls == Document: return 10 return 0 @property
def is_template(self): return self.lifecycle.type == Lifecycle.TEMPLATE @memoize_noarg
def get_all_documents():
[docs] u""" Returns a dict<doc_name, doc_class> of all available :class:`.Document` classes """ res = {} get_all_subclasses(Document, res) return res def get_all_subtype_documents(subtype):
[docs] u""" Returns a dict<doc_name, doc_class> of all available **subtype** classes """ res = {} get_all_subclasses(subtype, res) return res def get_all_documents_with_level(only_accept_files=False):
lst = [] level="=>" get_all_subclasses_with_level(Document, lst, level) classes = get_all_documents() if only_accept_files: return [(n, l) for n, l in lst if classes[n].ACCEPT_FILES] return lst get_all_documents_with_level = memoize(get_all_documents_with_level, {}, 1) def get_best_document_type(files):
[docs] """ .. versionadded:: 2.0 Returns the document type (str) which returns the highest creation score for the uploaded files (list of :class:`PrivateFile`). .. seealso:: :meth:`Document.get_creation_score` """ dtype = "Document" max_score = 0 for name, cls in get_all_documents().iteritems(): score = cls.get_creation_score(files) if score > max_score: max_score = score dtype = name return dtype