import os.path import io import datetime from pathlib import Path from PIL import Image from django.core.exceptions import ValidationError from django.contrib.gis.db import models from django.contrib.sitemaps import Sitemap from django.db.models.signals import post_save from django.dispatch import receiver from django.db.models.signals import m2m_changed from django.utils.encoding import force_text from django.utils.functional import cached_property from django.urls import reverse from django.apps import apps from django.utils.html import format_html from django.conf import settings from django import forms from resizeimage.imageexceptions import ImageSizeError from taggit.managers import TaggableManager from .readexif import readexif from .utils import resize_image from locations.models import Location def get_upload_path(self, filename): return "images/original/%s/%s" % (datetime.datetime.today().strftime("%Y"), filename) def get_vid_upload_path(self, filename): return "images/videos/%s/%s" % (datetime.datetime.today().strftime("%Y"), filename) def get_audio_upload_path(self, filename): return "audio/%s/%s" % (datetime.datetime.today().strftime("%Y"), filename) class LuxImageSize(models.Model): name = models.CharField(null=True, blank=True, max_length=30) slug = models.SlugField(null=True, blank=True) width = models.IntegerField(null=True, blank=True) height = models.IntegerField(null=True, blank=True) quality = models.IntegerField() class Meta: ordering = ('-name', 'id') verbose_name_plural = 'Image Sizes' def __str__(self): if self.width: size = self.width if self.height: size = self.height return "%s - %s" %(self.name, str(size)) class LuxImage(models.Model): image = models.FileField(blank=True, null=True, upload_to=get_upload_path) title = models.CharField(null=True, blank=True, max_length=300) alt = models.CharField(null=True, blank=True, max_length=300) photo_credit_source = models.CharField(null=True, blank=True, max_length=300) photo_credit_url = models.CharField(null=True, blank=True, max_length=300) caption = models.TextField(blank=True, null=True) pub_date = models.DateTimeField(default=datetime.datetime.now) exif_raw = models.TextField(blank=True, null=True) exif_aperture = models.CharField(max_length=50, blank=True, null=True) exif_make = models.CharField(max_length=50, blank=True, null=True) exif_model = models.CharField(max_length=50, blank=True, null=True) exif_exposure = models.CharField(max_length=50, blank=True, null=True) exif_iso = models.CharField(max_length=50, blank=True, null=True) exif_focal_length = models.CharField(max_length=50, blank=True, null=True) exif_lens = models.CharField(max_length=50, blank=True, null=True) exif_date = models.DateTimeField(blank=True, null=True) height = models.CharField(max_length=6, blank=True, null=True) width = models.CharField(max_length=6, blank=True, null=True) point = models.PointField(null=True, blank=True) location = models.ForeignKey("locations.Location", on_delete=models.CASCADE, null=True, blank=True) is_public = models.BooleanField(default=True) sizes = models.ManyToManyField(LuxImageSize, blank=True, related_name='sizes') sizes_cache = models.CharField(null=True, blank=True, max_length=300) class Meta: ordering = ('-pub_date', 'id') verbose_name_plural = 'Images' get_latest_by = 'pub_date' def __str__(self): if self.title: return "%s" % self.title else: return "%s" % self.pk def get_type(self): return str(self.__class__.__name__) def get_admin_image(self): for size in self.sizes.all(): if size.width and size.width <= 820 or size.height and size.height <= 800: return self.get_image_url_by_size(size.name) def get_largest_image(self): t = [] for size in self.sizes.all(): t.append(size.width) t.sort(key=float) t.reverse() return self.get_image_path_by_size(t[0]) @cached_property def image_name(self): return os.path.basename(self.image.path)[:-4] @cached_property def image_ext(self): return self.image.url[-3:] @cached_property def get_image_filename(self): return os.path.basename(self.image.path) @cached_property def get_srcset(self): srcset = "" length = len(self.sizes.all()) print(length) loopnum = 1 for size in self.sizes.all(): srcset += "%s%s/%s_%s.%s %sw" % (settings.IMAGES_URL, self.pub_date.strftime("%Y"), self.image_name, size.name, self.image_ext, size.width) if loopnum < length: srcset += ", " loopnum = loopnum+1 return srcset @cached_property def get_src(self): src = "" if self.sizes.all().count() > 1: src += "%s%s/%s_%s.%s" % (settings.IMAGES_URL, self.pub_date.strftime("%Y"), self.image_name, 'picwide-med', self.image_ext) else: src += "%s%s/%s_%s.%s" % (settings.IMAGES_URL, self.pub_date.strftime("%Y"), self.image_name, [size.name for size in self.sizes.all()], self.image_ext) return src def get_image_url_by_size(self, size="original"): if size == "original": return self.image.url if size == "admin_insert": return "images/%s/%s.%s" % (self.pub_date.strftime("%Y"), self.image_name, self.image_ext) else: luximagesize = LuxImageSize.objects.get(slug=size) #if luximagesize not in self.get_sizes: #self.sizes.add(luximagesize) return "%s%s/%s_%s.%s" % (settings.IMAGES_URL, self.pub_date.strftime("%Y"), self.image_name, luximagesize.slug, self.image_ext) def get_image_path_by_size(self, size="original"): if size == "original": return self.image.path else: luximagesize = LuxImageSize.objects.get(slug=size) return "%s/%s/%s_%s.%s" % (settings.IMAGES_ROOT, self.pub_date.strftime("%Y"), self.image_name, luximagesize.slug, self.image_ext) @cached_property def get_thumbnail_url(self): return self.get_image_url_by_size("tn") def admin_thumbnail(self): return format_html('' % (self.get_image_url_by_size(), self.get_image_url_by_size("tn"))) admin_thumbnail.short_description = 'Thumbnail' @property def get_sizes(self): return self.sizes_cache.split(",") @property def get_previous_published(self): return self.get_previous_by_pub_date() @property def get_next_published(self): return self.get_next_by_pub_date() @property def get_previous_admin_url(self): n = self.get_previous_by_pub_date() return reverse('admin:%s_%s_change' %(self._meta.app_label, self._meta.model_name), args=[n.id] ) @property def get_next_admin_url(self): model = apps.get_model(app_label=self._meta.app_label, model_name=self._meta.model_name) try: return reverse('admin:%s_%s_change' %(self._meta.app_label, self._meta.model_name), args=[self.get_next_by_pub_date().pk] ) except model.DoesNotExist: return '' @property def is_portait(self): if int(self.height) > int(self.width): return True else: return False def save(self, *args, **kwargs): created = self.pk is None if not created: self.sizes_cache = ",".join(s.slug for s in self.sizes.all()) super(LuxImage, self).save() class LuxGallery(models.Model): title = models.CharField(blank=True, max_length=300) description = models.TextField(blank=True, null=True) slug = models.CharField(blank=True, max_length=300) thumb = models.ForeignKey(LuxImage, on_delete=models.CASCADE, related_name="gallery_thumb", null=True, blank=True) images = models.ManyToManyField(LuxImage) pub_date = models.DateTimeField(null=True) is_public = models.BooleanField(default=True) caption_style = models.CharField(blank=True, null=True, max_length=400) class Meta: ordering = ('-pub_date', 'id') verbose_name_plural = 'Galleries' get_latest_by = 'pub_date' def __str__(self): return self.title def get_main_image(self): return "%sgallery_thumbs/%s.jpg" % (settings.IMAGES_URL, self.id) def get_absolute_url(self): if self.is_public: return "/photos/galleries/%s" % (self.slug) else: return "/photos/galleries/private/%s" % (self.slug) def thumbs(self): lst = [x.image.name for x in self.images.all()] lst = ["%s" % (x, x.split('/')[-1]) for x in lst] return ', '.join(item for item in lst if item) thumbs.allow_tags = True class LuxVideo(models.Model): video_mp4 = models.FileField(blank=True, null=True, upload_to=get_vid_upload_path) video_webm = models.FileField(blank=True, null=True, upload_to=get_vid_upload_path) video_poster = models.FileField(blank=True, null=True, upload_to=get_vid_upload_path) title = models.CharField(null=True, blank=True, max_length=300) pub_date = models.DateTimeField(default=datetime.datetime.now) youtube_url = models.CharField(null=True, blank=True, max_length=80) vimeo_url = models.CharField(null=True, blank=True, max_length=300) point = models.PointField(blank=True, null=True) location = models.ForeignKey(Location, on_delete=models.CASCADE, blank=True, null=True) def __str__(self): if self.title: return self.title else: return str(self.pk) def get_type(self): return str(self.__class__.__name__) class Meta: ordering = ('-pub_date', 'id') verbose_name_plural = 'Videos' verbose_name = 'Video' get_latest_by = 'pub_date' def save(self, *args, **kwargs): if not self.point: self.point = CheckIn.objects.latest().point try: self.location = Location.objects.filter(geometry__contains=self.point).get() except Location.DoesNotExist: raise forms.ValidationError("There is no location associated with that point, add it: %sadmin/locations/location/add/" % (settings.BASE_URL)) super(LuxVideo, self).save(*args, **kwargs) class LuxAudio(models.Model): title = models.CharField(max_length=200) subtitle = models.CharField(max_length=200, blank=True) slug = models.SlugField(unique_for_date='pub_date', blank=True) body_html = models.TextField(blank=True) body_markdown = models.TextField(blank=True) pub_date = models.DateTimeField(default=datetime.datetime.now) mp3 = models.FileField(blank=True, null=True, upload_to=get_audio_upload_path) ogg = models.FileField(blank=True, null=True, upload_to=get_audio_upload_path) point = models.PointField(blank=True, null=True) location = models.ForeignKey(Location, on_delete=models.CASCADE, blank=True, null=True) class Meta: ordering = ('-pub_date',) verbose_name_plural = 'Audio' verbose_name = 'Audio' get_latest_by = 'pub_date' def __str__(self): return self.title def get_absolute_url(self): return reverse("prompt:detail", kwargs={"slug": self.slug}) def get_image_url_by_size(self, size="original"): pass @property def get_previous_published(self): return self.get_previous_by_pub_date(status__exact=1) @property def get_previous_admin_url(self): n = self.get_previous_by_pub_date() return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=[n.id]) @property def get_next_published(self): return self.get_next_by_pub_date(status__exact=1) @property def get_next_admin_url(self): model = apps.get_model(app_label=self._meta.app_label, model_name=self._meta.model_name) try: return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=[self.get_next_by_pub_date().pk]) except model.DoesNotExist: return '' def get_type(self): return str(self.__class__.__name__) def save(self, *args, **kwargs): md = render_images(self.body_markdown) self.body_html = markdown_to_html(md) if not self.point: self.point = CheckIn.objects.latest().point try: self.location = Location.objects.filter(geometry__contains=self.point).get() except Location.DoesNotExist: raise forms.ValidationError("There is no location associated with that point, add it: %sadmin/locations/location/add/" % (settings.BASE_URL)) super(LuxAudio, self).save(*args, **kwargs) @receiver(post_save, sender=LuxImage) def post_save_events(sender, update_fields, created, instance, **kwargs): if created: if instance.exif_raw == '': instance = readexif(instance) instance.sizes.add(LuxImageSize.objects.get(slug="tn")) img = Image.open(instance.image.path) instance.height = img.height instance.width = img.width post_save.disconnect(post_save_events, sender=LuxImage) instance.save() post_save.connect(post_save_events, sender=LuxImage) @receiver(m2m_changed, sender=LuxImage.sizes.through) def update_photo_sizes(sender, instance, **kwargs): # update the local cache of sizes sizes = instance.sizes.all() if sizes: instance.sizes_cache = ",".join(s.slug for s in sizes) instance.save() for size in instance.get_sizes: print("SIZE is: %s" % size) # check each size and see if there's an image there already my_file = Path(instance.get_image_path_by_size(size)) if not my_file.is_file(): #file doesn't exist, so create it new_size = LuxImageSize.objects.get(slug=size) if new_size.width: img = Image.open(instance.image.path) try: if new_size.width <= img.width: resize_image(img, new_size.width, None, new_size.quality, instance.get_image_path_by_size(size)) else: raise ValidationError({'items': ["Size is larger than source image"]}) except ImageSizeError: m2m_changed.disconnect(update_photo_sizes, sender=LuxImage.sizes.through) instance.sizes.remove(new_size) m2m_changed.connect(update_photo_sizes, sender=LuxImage.sizes.through) if new_size.height: img = Image.open(instance.image.path) try: if new_size.height <= img.height: resize_image(img, None, new_size.height, new_size.quality, instance.get_image_path_by_size(size)) else: pass except ImageSizeError: m2m_changed.disconnect(update_photo_sizes, sender=LuxImage.sizes.through) instance.sizes.remove(new_size) m2m_changed.connect(update_photo_sizes, sender=LuxImage.sizes.through) else: # file exists, might add something here to force it to do the above when I want print("file %s exists" % size) pass