models.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. from __future__ import unicode_literals
  2. from future.builtins import str
  3. from future.utils import native
  4. from io import BytesIO
  5. import os
  6. from string import punctuation
  7. from zipfile import ZipFile
  8. from chardet import detect as charsetdetect
  9. from django.core.files.base import ContentFile
  10. from django.core.files.storage import default_storage
  11. from django.db import models
  12. from django.utils.encoding import python_2_unicode_compatible
  13. from django.utils.encoding import force_text
  14. from django.utils.translation import ugettext_lazy as _
  15. from mezzanine.conf import settings
  16. from mezzanine.core.fields import FileField
  17. from mezzanine.core.models import Orderable, RichText
  18. from mezzanine.pages.models import Page
  19. from mezzanine.utils.importing import import_dotted_path
  20. from mezzanine.utils.models import upload_to
  21. # Set the directory where gallery images are uploaded to,
  22. # either MEDIA_ROOT + 'galleries', or filebrowser's upload
  23. # directory if being used.
  24. GALLERIES_UPLOAD_DIR = "galleries"
  25. if settings.PACKAGE_NAME_FILEBROWSER in settings.INSTALLED_APPS:
  26. fb_settings = "%s.settings" % settings.PACKAGE_NAME_FILEBROWSER
  27. try:
  28. GALLERIES_UPLOAD_DIR = import_dotted_path(fb_settings).DIRECTORY
  29. except ImportError:
  30. pass
  31. class BaseGallery(models.Model):
  32. """
  33. Base gallery functionality.
  34. """
  35. class Meta:
  36. abstract = True
  37. zip_import = models.FileField(verbose_name=_("Zip import"), blank=True,
  38. upload_to=upload_to("galleries.Gallery.zip_import", "galleries"),
  39. help_text=_("Upload a zip file containing images, and "
  40. "they'll be imported into this gallery."))
  41. def save(self, delete_zip_import=True, *args, **kwargs):
  42. """
  43. If a zip file is uploaded, extract any images from it and add
  44. them to the gallery, before removing the zip file.
  45. """
  46. super(BaseGallery, self).save(*args, **kwargs)
  47. if self.zip_import:
  48. zip_file = ZipFile(self.zip_import)
  49. for name in zip_file.namelist():
  50. data = zip_file.read(name)
  51. try:
  52. from PIL import Image
  53. image = Image.open(BytesIO(data))
  54. image.load()
  55. image = Image.open(BytesIO(data))
  56. image.verify()
  57. except ImportError:
  58. pass
  59. except:
  60. continue
  61. name = os.path.split(name)[1]
  62. # In python3, name is a string. Convert it to bytes.
  63. if not isinstance(name, bytes):
  64. try:
  65. name = name.encode('cp437')
  66. except UnicodeEncodeError:
  67. # File name includes characters that aren't in cp437,
  68. # which isn't supported by most zip tooling. They will
  69. # not appear correctly.
  70. tempname = name
  71. # Decode byte-name.
  72. if isinstance(name, bytes):
  73. encoding = charsetdetect(name)['encoding']
  74. tempname = name.decode(encoding)
  75. # A gallery with a slug of "/" tries to extract files
  76. # to / on disk; see os.path.join docs.
  77. slug = self.slug if self.slug != "/" else ""
  78. path = os.path.join(GALLERIES_UPLOAD_DIR, slug, tempname)
  79. try:
  80. saved_path = default_storage.save(path, ContentFile(data))
  81. except UnicodeEncodeError:
  82. from warnings import warn
  83. warn("A file was saved that contains unicode "
  84. "characters in its path, but somehow the current "
  85. "locale does not support utf-8. You may need to set "
  86. "'LC_ALL' to a correct value, eg: 'en_US.UTF-8'.")
  87. # The native() call is needed here around str because
  88. # os.path.join() in Python 2.x (in posixpath.py)
  89. # mixes byte-strings with unicode strings without
  90. # explicit conversion, which raises a TypeError as it
  91. # would on Python 3.
  92. path = os.path.join(GALLERIES_UPLOAD_DIR, slug,
  93. native(str(name, errors="ignore")))
  94. saved_path = default_storage.save(path, ContentFile(data))
  95. self.images.create(file=saved_path)
  96. if delete_zip_import:
  97. zip_file.close()
  98. self.zip_import.delete(save=True)
  99. class Gallery(Page, RichText, BaseGallery):
  100. """
  101. Page bucket for gallery photos.
  102. """
  103. class Meta:
  104. verbose_name = _("Gallery")
  105. verbose_name_plural = _("Galleries")
  106. @python_2_unicode_compatible
  107. class GalleryImage(Orderable):
  108. gallery = models.ForeignKey("Gallery", related_name="images")
  109. file = FileField(_("File"), max_length=200, format="Image",
  110. upload_to=upload_to("galleries.GalleryImage.file", "galleries"))
  111. description = models.CharField(_("Description"), max_length=1000,
  112. blank=True)
  113. class Meta:
  114. verbose_name = _("Image")
  115. verbose_name_plural = _("Images")
  116. def __str__(self):
  117. return self.description
  118. def save(self, *args, **kwargs):
  119. """
  120. If no description is given when created, create one from the
  121. file name.
  122. """
  123. if not self.id and not self.description:
  124. name = force_text(self.file)
  125. name = name.rsplit("/", 1)[-1].rsplit(".", 1)[0]
  126. name = name.replace("'", "")
  127. name = "".join([c if c not in punctuation else " " for c in name])
  128. # str.title() doesn't deal with unicode very well.
  129. # http://bugs.python.org/issue6412
  130. name = "".join([s.upper() if i == 0 or name[i - 1] == " " else s
  131. for i, s in enumerate(name)])
  132. self.description = name
  133. super(GalleryImage, self).save(*args, **kwargs)