text: import hashlib
  import os.path
  import warnings
  from contextlib import contextmanager
  
  from django.conf import settings
  from django.db import models
  from django.dispatch import Signal
  from django.urls import reverse
  from django.utils.translation import ugettext_lazy as _
  from taggit.managers import TaggableManager
  
  from wagtail.admin.models import get_object_usage
  from wagtail.core.models import CollectionMember
  from wagtail.search import index
  from wagtail.search.queryset import SearchableQuerySetMixin
  from wagtail.utils.deprecation import RemovedInWagtail210Warning
  
  
  class DocumentQuerySet(SearchableQuerySetMixin, models.QuerySet):
      pass
  
  
  def get_document_model():
      warnings.warn("wagtail.documents.models.get_document_model "
                    "has been moved to wagtail.documents.get_document_model",
                    RemovedInWagtail210Warning)
      from wagtail.documents import get_document_model
      return get_document_model()
  
  
  class AbstractDocument(CollectionMember, index.Indexed, models.Model):
      title = models.CharField(max_length=255, verbose_name=_('title'))
      file = models.FileField(upload_to='documents', verbose_name=_('file'))
      created_at = models.DateTimeField(verbose_name=_('created at'), auto_now_add=True)
      uploaded_by_user = models.ForeignKey(
          settings.AUTH_USER_MODEL,
          verbose_name=_('uploaded by user'),
          null=True,
          blank=True,
          editable=False,
          on_delete=models.SET_NULL
      )
  
      tags = TaggableManager(help_text=None, blank=True, verbose_name=_('tags'))
  
      file_size = models.PositiveIntegerField(null=True, editable=False)
      # A SHA-1 hash of the file contents
      file_hash = models.CharField(max_length=40, blank=True, editable=False)
  
      objects = DocumentQuerySet.as_manager()
  
      search_fields = CollectionMember.search_fields + [
          index.SearchField('title', partial_match=True, boost=10),
          index.AutocompleteField('title'),
          index.FilterField('title'),
          index.RelatedFields('tags', [
              index.SearchField('name', partial_match=True, boost=10),
              index.AutocompleteField('name'),
          ]),
          index.FilterField('uploaded_by_user'),
      ]
  
      def is_stored_locally(self):
          """
          Returns True if the image is hosted on the local filesystem
          """
          try:
              self.file.path
  
              return True
          except NotImplementedError:
              return False
  
      @contextmanager
      def open_file(self):
          # Open file if it is closed
          close_file = False
          f = self.file
  
          if f.closed:
              # Reopen the file
              if self.is_stored_locally():
                  f.open('rb')
              else:
                  # Some external storage backends don't allow reopening
                  # the file. Get a fresh file instance. #1397
                  storage = self._meta.get_field('file').storage
                  f = storage.open(f.name, 'rb')
  
              close_file = True
  
          # Seek to beginning
          f.seek(0)
  
          try:
              yield f
          finally:
              if close_file:
                  f.close()
  
      def get_file_size(self):
          if self.file_size is None:
              try:
                  self.file_size = self.file.size
              except Exception:
                  # File doesn't exist
                  return
  
              self.save(update_fields=['file_size'])
  
          return self.file_size
  
      def _set_file_hash(self, file_contents):
          self.file_hash = hashlib.sha1(file_contents).hexdigest()
  
      def get_file_hash(self):
          if self.file_hash == '':
              with self.open_file() as f:
                  self._set_file_hash(f.read())
  
              self.save(update_fields=['file_hash'])
  
          return self.file_hash
  
      def __str__(self):
          return self.title
  
      @property
      def filename(self):
          return os.path.basename(self.file.name)
  
      @property
      def file_extension(self):
          return os.path.splitext(self.filename)[1][1:]
  
      @property
      def url(self):
          if getattr(settings, 'WAGTAILDOCS_SERVE_METHOD', None) == 'direct':
              try:
                  return self.file.url
              except NotImplementedError:
                  # backend does not provide a url, so fall back on the serve view
                  pass
  
          return reverse('wagtaildocs_serve', args=[self.id, self.filename])
  
      def get_usage(self):
          return get_object_usage(self)
  
      @property
      def usage_url(self):
          return reverse('wagtaildocs:document_usage',
                         args=(self.id,))
  
      def is_editable_by_user(self, user):
          from wagtail.documents.permissions import permission_policy
          return permission_policy.user_has_permission_for_instance(user, 'change', self)
  
      class Meta:
          abstract = True
          verbose_name = _('document')
          verbose_name_plural = _('documents')
  
  
  class Document(AbstractDocument):
      admin_form_fields = (
          'title',
          'file',
          'collection',
          'tags'
      )
  
  
  document_served = Signal(providing_args=['request'])
  