<commit_before>from django.core.files.images import get_image_dimensions
import Image
from datetime import datetime
from os import path
import os

from django.db import models, IntegrityError
from django.conf import settings
from django.utils.translation import ugettext, ugettext_lazy as _
from django.contrib.contenttypes import generic
from django.contrib.sites.models import Site
from django.utils.safestring import mark_safe
from django.core.files.uploadedfile import UploadedFile

from ella.core.models.main import Author, Source
from ella.core.box import Box
from ella.core.cache.utils import get_cached_object
from ella.utils.filemanipulation import file_rename

from formatter import Formatter, detect_img_type

# settings default
PHOTOS_FORMAT_QUALITY_DEFAULT = (
    (45, _('Low')),
    (65, _('Medium')),
    (75, _('Good')),
    (85, _('Better')),
    (95, _('High')),
)

PHOTOS_THUMB_DIMENSION_DEFAULT = (80,80)

PHOTOS_FORMAT_QUALITY = getattr(settings, 'PHOTOS_FORMAT_QUALITY', PHOTOS_FORMAT_QUALITY_DEFAULT)
PHOTOS_THUMB_DIMENSION = getattr(settings, 'PHOTOS_THUMB_DIMENSION', PHOTOS_THUMB_DIMENSION_DEFAULT)
PHOTOS_DO_URL_CHECK = getattr(settings, 'PHOTOS_DO_URL_CHECK', False)
CUSTOM_SUBDIR = getattr(settings, 'PHOTOS_CUSTOM_SUBDIR', '')
UPLOAD_TO = CUSTOM_SUBDIR and 'photos/%s/%%Y/%%m/%%d' % CUSTOM_SUBDIR or 'photos/%Y/%m/%d'

PHOTOS_TYPE_EXTENSION = {
    'JPEG': '.jpg',
    'PNG': '.png',
    'GIF': '.gif'
}


class PhotoBox(Box):
    def get_context(self):
        "Updates box context with photo-specific variables."
        cont = super(PhotoBox, self).get_context()
        cont.update({
                'title' : self.params.get('title', self.obj.title),
                'alt' : self.params.get('alt', ''),
                'description' : self.params.get('description', self.obj.description),
                'show_title' : self.params.get('show_title', ''),
                'show_description' : self.params.get('show_description', ''),
                'show_authors' : self.params.get('show_authors', ''),
                'show_detail' : self.params.get('show_detail', ''),
                'link_url': self.params.get('link_url', ''),
            })
        return cont

class Photo(models.Model):
    "Represents original (unformated) photo."
    box_class = PhotoBox
    title = models.CharField(_('Title'), max_length=200)
    description = models.TextField(_('Description'), blank=True)
    slug = models.SlugField(_('Slug'), max_length=255)
    image = models.ImageField(upload_to=UPLOAD_TO, height_field='height', width_field='width') # save it to YYYY/MM/DD structure
    width = models.PositiveIntegerField(editable=False)
    height = models.PositiveIntegerField(editable=False)

    # important area
    important_top = models.PositiveIntegerField(null=True, blank=True)
    important_left = models.PositiveIntegerField(null=True, blank=True)
    important_bottom = models.PositiveIntegerField(null=True, blank=True)
    important_right = models.PositiveIntegerField(null=True, blank=True)

    # Authors and Sources
    authors = models.ManyToManyField(Author, verbose_name=_('Authors') , related_name='photo_set')
    source = models.ForeignKey(Source, blank=True, null=True, verbose_name=_('Source'))

    created = models.DateTimeField(default=datetime.now, editable=False)

    def thumb(self):
        """
        Generates html and thumbnails for admin site.
        """
        thumbUrl = self.thumb_url()
        if not thumbUrl:
            return mark_safe("""<strong>%s</strong>""" % ugettext('Thumbnail not available'))
        return mark_safe("""<a href="%s"><img src="%s" alt="Thumbnail %s" /></a>""" % (self.image.url, thumbUrl, self.title))
    thumb.allow_tags = True

    def thumb_url(self):
        """
        Generates thumbnail for admin site and returns its url
        """
        type = detect_img_type(self.image.path)
        if not type:
            return None

        # photos/2008/12/31/foo.jpg => photos/2008/12/31/thumb-foo.jpg
        thumb_name = path.dirname(self.image.name) + "/" + 'thumb-%s' % path.basename(self.image.name)

        storage = self.image.storage
        if not storage.exists(thumb_name):
            try:
                im = Image.open(self.image.path)
                im.thumbnail(PHOTOS_THUMB_DIMENSION, Image.ANTIALIAS)
                im.save(storage.path(thumb_name), type)
            except IOError:
                # TODO Logging something wrong
                return None
        return storage.url(thumb_name)

    def save(self, force_insert=False, force_update=False):
        """Overrides models.Model.save.

        - Generates slug.
        - Saves image file.
        """

        # prefill the slug with the ID, it requires double save
        if not self.id:
            if isinstance(self.image, UploadedFile):
                # due to PIL has read several bytes from image, position in file has to be reset
                self.image.seek(0)
            super(Photo, self).save(force_insert, force_update)
            self.width, self.height = get_image_dimensions(self.image.path)
            self.slug = str(self.id) + '-' + self.slug
            force_insert, force_update = False, True
            image_changed = False
        else:
            old = Photo.objects.get(pk = self.pk)
            image_changed = old.image != self.image
        # rename image by slug
        imageType = detect_img_type(path.join(settings.MEDIA_ROOT, self.image.name))
        if imageType is not None:
            self.image = file_rename(self.image.name, self.slug, PHOTOS_TYPE_EXTENSION[ imageType ])
        # delete formatedphotos if new image was uploaded
        if image_changed:
            self.width, self.height = get_image_dimensions(self.image)
            for f_photo in self.formatedphoto_set.all():
                f_photo.delete()
        super(Photo, self).save(force_insert, force_update)

    def ratio(self):
        "Return photo's width to height ratio"
        if self.height:
            return float(self.width) / self.height
        else:
            return None

    def get_formated_photo(self, format):
        "Return formated photo"
        format_object = Format.objects.get(name=format, site=settings.SITE_ID)
        try:
            formated_photo = get_cached_object(FormatedPhoto, photo=self, format=format_object)
        except FormatedPhoto.DoesNotExist:
            try:
                formated_photo = FormatedPhoto.objects.create(photo=self, format=format_object)
            except (IOError, SystemError, IntegrityError):
                return None

        return formated_photo

    def __unicode__(self):
        return self.title

    def get_absolute_url(self):
        return self.image.url
<commit_msg>Photo.save() fixed.<commit_after>from django.core.files.images import get_image_dimensions
import Image
from datetime import datetime
from os import path
import os

from django.db import models, IntegrityError
from django.conf import settings
from django.utils.translation import ugettext, ugettext_lazy as _
from django.contrib.sites.models import Site
from django.utils.safestring import mark_safe
from django.core.files.uploadedfile import UploadedFile

from ella.core.models.main import Author, Source
from ella.core.box import Box
from ella.core.cache.utils import get_cached_object
from ella.utils.filemanipulation import file_rename

from formatter import Formatter, detect_img_type

# settings default
PHOTOS_FORMAT_QUALITY_DEFAULT = (
    (45, _('Low')),
    (65, _('Medium')),
    (75, _('Good')),
    (85, _('Better')),
    (95, _('High')),
)

PHOTOS_THUMB_DIMENSION_DEFAULT = (80,80)

PHOTOS_FORMAT_QUALITY = getattr(settings, 'PHOTOS_FORMAT_QUALITY', PHOTOS_FORMAT_QUALITY_DEFAULT)
PHOTOS_THUMB_DIMENSION = getattr(settings, 'PHOTOS_THUMB_DIMENSION', PHOTOS_THUMB_DIMENSION_DEFAULT)
PHOTOS_DO_URL_CHECK = getattr(settings, 'PHOTOS_DO_URL_CHECK', False)
CUSTOM_SUBDIR = getattr(settings, 'PHOTOS_CUSTOM_SUBDIR', '')
UPLOAD_TO = CUSTOM_SUBDIR and 'photos/%s/%%Y/%%m/%%d' % CUSTOM_SUBDIR or 'photos/%Y/%m/%d'

PHOTOS_TYPE_EXTENSION = {
    'JPEG': '.jpg',
    'PNG': '.png',
    'GIF': '.gif'
}


class PhotoBox(Box):
    def get_context(self):
        "Updates box context with photo-specific variables."
        cont = super(PhotoBox, self).get_context()
        cont.update({
                'title' : self.params.get('title', self.obj.title),
                'alt' : self.params.get('alt', ''),
                'description' : self.params.get('description', self.obj.description),
                'show_title' : self.params.get('show_title', ''),
                'show_description' : self.params.get('show_description', ''),
                'show_authors' : self.params.get('show_authors', ''),
                'show_detail' : self.params.get('show_detail', ''),
                'link_url': self.params.get('link_url', ''),
            })
        return cont

class Photo(models.Model):
    "Represents original (unformated) photo."
    box_class = PhotoBox
    title = models.CharField(_('Title'), max_length=200)
    description = models.TextField(_('Description'), blank=True)
    slug = models.SlugField(_('Slug'), max_length=255)
    image = models.ImageField(upload_to=UPLOAD_TO, height_field='height', width_field='width') # save it to YYYY/MM/DD structure
    width = models.PositiveIntegerField(editable=False)
    height = models.PositiveIntegerField(editable=False)

    # important area
    important_top = models.PositiveIntegerField(null=True, blank=True)
    important_left = models.PositiveIntegerField(null=True, blank=True)
    important_bottom = models.PositiveIntegerField(null=True, blank=True)
    important_right = models.PositiveIntegerField(null=True, blank=True)

    # Authors and Sources
    authors = models.ManyToManyField(Author, verbose_name=_('Authors') , related_name='photo_set')
    source = models.ForeignKey(Source, blank=True, null=True, verbose_name=_('Source'))

    created = models.DateTimeField(default=datetime.now, editable=False)

    def thumb(self):
        """
        Generates html and thumbnails for admin site.
        """
        thumbUrl = self.thumb_url()
        if not thumbUrl:
            return mark_safe("""<strong>%s</strong>""" % ugettext('Thumbnail not available'))
        return mark_safe("""<a href="%s"><img src="%s" alt="Thumbnail %s" /></a>""" % (self.image.url, thumbUrl, self.title))
    thumb.allow_tags = True

    def thumb_url(self):
        """
        Generates thumbnail for admin site and returns its url
        """
        type = detect_img_type(self.image.path)
        if not type:
            return None

        # photos/2008/12/31/foo.jpg => photos/2008/12/31/thumb-foo.jpg
        thumb_name = path.dirname(self.image.name) + "/" + 'thumb-%s' % path.basename(self.image.name)

        storage = self.image.storage
        if not storage.exists(thumb_name):
            try:
                im = Image.open(self.image.path)
                im.thumbnail(PHOTOS_THUMB_DIMENSION, Image.ANTIALIAS)
                im.save(storage.path(thumb_name), type)
            except IOError:
                # TODO Logging something wrong
                return None
        return storage.url(thumb_name)

    def save(self, force_insert=False, force_update=False):
        """Overrides models.Model.save.

        - Generates slug.
        - Saves image file.
        """

        # prefill the slug with the ID, it requires double save
        if not self.id:
            if isinstance(self.image, UploadedFile):
                # due to PIL has read several bytes from image, position in file has to be reset
                self.image.seek(0)
            super(Photo, self).save(force_insert, force_update)
            self.width, self.height = get_image_dimensions(self.image.path)
            self.slug = str(self.id) + '-' + self.slug
            force_insert, force_update = False, True
            image_changed = False
        else:
            old = Photo.objects.get(pk = self.pk)
            image_changed = old.image != self.image
        # rename image by slug
        imageType = detect_img_type(self.image.path)
        if imageType is not None:
            self.image = file_rename(self.image.name, self.slug, PHOTOS_TYPE_EXTENSION[ imageType ])
        # delete formatedphotos if new image was uploaded
        if image_changed:
            super(Photo, self).save(force_insert, force_update)
            self.width, self.height = get_image_dimensions(self.image.path)
            force_insert, force_update = False, True
            for f_photo in self.formatedphoto_set.all():
                f_photo.delete()
        super(Photo, self).save(force_insert, force_update)

    def ratio(self):
        "Return photo's width to height ratio"
        if self.height:
            return float(self.width) / self.height
        else:
            return None

    def get_formated_photo(self, format):
        "Return formated photo"
        format_object = Format.objects.get(name=format, site=settings.SITE_ID)
        try:
            formated_photo = get_cached_object(FormatedPhoto, photo=self, format=format_object)
        except FormatedPhoto.DoesNotExist:
            try:
                formated_photo = FormatedPhoto.objects.create(photo=self, format=format_object)
            except (IOError, SystemError, IntegrityError):
                return None

        return formated_photo

    def __unicode__(self):
        return self.title

    def get_absolute_url(self):
        return self.image.url

{'fraction_non_alphanumeric': 0.11186340889019723, 'fraction_numerical': 0.0054459817486017074, 'mean_word_length': 2.850665911022953, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3585695', 'n_tokens_mistral': 4229, 'n_tokens_neox': 4092, 'n_words': 879}