summaryrefslogtreecommitdiff
path: root/app/media/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/media/models.py')
-rw-r--r--app/media/models.py382
1 files changed, 382 insertions, 0 deletions
diff --git a/app/media/models.py b/app/media/models.py
new file mode 100644
index 0000000..fb44c9e
--- /dev/null
+++ b/app/media/models.py
@@ -0,0 +1,382 @@
+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.utils.text import slugify
+from django.conf import settings
+from django import forms
+
+from resizeimage.imageexceptions import ImageSizeError
+
+from taggit.managers import TaggableManager
+
+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
+ else:
+ luximagesize = LuxImageSize.objects.get(name=size)
+ if luximagesize not in self.get_sizes:
+ print("new size is "+luximagesize.name)
+ 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(name=size)
+ return "%s%s/%s_%s.%s" % (settings.IMAGES_ROOT, self.pub_date.strftime("%Y"), self.image_name, luximagesize.slug, self.image_ext)
+
+ def get_thumbnail_url(self):
+ return self.get_image_url_by_size("tn")
+
+ def admin_thumbnail(self):
+ return format_html('<a href="%s"><img src="%s"></a>' % (self.get_image_url_by_size(), self.get_image_url_by_size("admin-thumbnail")))
+ admin_thumbnail.short_description = 'Thumbnail'
+
+ @cached_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 created:
+ self.sizes.add(LuxImageSize.objects.get(name="admin-thumbnail"))
+ img = Image.open(self.image.path)
+ self.height = img.height
+ self.width = img.width
+ 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 = ["<a href='/media/%s'>%s</a>" % (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})
+
+ @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(m2m_changed, sender=LuxImage.sizes.through)
+def update_photo_sizes(sender, instance, **kwargs):
+ # update the local cache of sizes
+ instance.sizes_cache = ",".join(s.slug for s in instance.sizes.all())
+ instance.save()
+ for size in instance.sizes.all():
+ # check each size and see if there's an image there already
+ my_file = Path(instance.get_image_path_by_size(size.name))
+ if not my_file.is_file():
+ print("creating new image file")
+ #file doesn't exist, so create it
+ if size.width:
+ img = Image.open(instance.image.path)
+ try:
+ if size.width <= img.width:
+ resize_image(img, size.width, None, size.quality, instance.get_image_path_by_size(size.name))
+ 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(size)
+ m2m_changed.connect(update_photo_sizes, sender=LuxImage.sizes.through)
+ if size.height:
+ try:
+ if size.height <= img.height:
+ resize_image(img, None, size.height, size.quality, instance.get_image_path_by_size(size.name))
+ else:
+ pass
+ except ImageSizeError:
+ m2m_changed.disconnect(update_photo_sizes, sender=LuxImage.sizes.through)
+ instance.sizes.remove(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
+
+