from io import BytesIO from django.conf import settings from django.contrib.sites.models import Site from django.core.files import File from django.core.mail import send_mail from django.db import models from django.db.models.signals import post_save, pre_save from django.dispatch import receiver from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ from PIL import Image class SiteConfig(models.Model): site = models.OneToOneField(Site, on_delete=models.CASCADE, related_name="config", primary_key=True) color = models.CharField(max_length=7, default="#ffe600") scrolling_text = models.CharField(max_length=2048, blank=True, null=True) default = models.BooleanField(default=False) @staticmethod def pre_save(sender, instance, update_fields, **kwargs): if instance.default: for obj in sender.objects.filter(default=True): obj.default = False obj.save() pre_save.connect(SiteConfig.pre_save, SiteConfig) def make_thumbnail(image, size=(512, 512)): """Makes thumbnails of given size from given image""" im = Image.open(image.path) im = im.convert('RGB') # convert mode im.thumbnail(size) # resize imag thumb_io = BytesIO() # create a BytesIO object im.save(thumb_io, 'JPEG', quality=85, # save image to BytesIO object icc_profile = im.info.get('icc_profile','')) thumbnail = File(thumb_io, name=image.name) # create a django friendly File object return thumbnail SHORT_VALUE_LENGTH = 50 TAG_CATEGORIES_ORDER = ['misc', 'objektuntergruppe', 'where', 'when', 'object_type', 'who'] THUMB_SIZE = (512, 512) class ItemGroup(models.Model): title = models.CharField(_("title"), max_length=50, unique=True) slug = models.SlugField(_("slug")) site = models.ForeignKey(Site, on_delete=models.CASCADE) def __str__(self): return self.title class Meta: verbose_name = _("item group") verbose_name_plural = _("item groups") class Thesaurus(models.Model): name = models.CharField(_("name"), max_length=50, db_index=True) slug = models.SlugField(_("slug")) parent = models.ForeignKey("Thesaurus", on_delete=models.SET_NULL, null=True, verbose_name=_("parent")) full_path = models.CharField(_("full path"), max_length=300, db_index=True, unique=True) site = models.ForeignKey(Site, on_delete=models.CASCADE) def __str__(self): return self.full_path class Meta: verbose_name = _("thesaurus") verbose_name_plural = _("thesaurus") class Tag(models.Model): CATEGORIES = ( ("when", _("Dekade")), ("object_type", _("Objektart")), ("who", _("Besitzer")), ("where", _("Region")), ("misc", _("Thema")), ) name = models.CharField(_("name"), max_length=100, db_index=True) slug = models.SlugField(_("slug"), db_index=True) is_active = models.BooleanField(_("is active"), default=True) display_count = models.IntegerField(_("display count"), default=0) category = models.CharField(_("category"), max_length=20, db_index=True, choices=CATEGORIES, default="when" ) def __str__(self): return self.name class Meta: ordering = ('category', 'name',) verbose_name = _("tag") verbose_name_plural = _("tags") class Item(models.Model): STATUS_QUO = '1_status_quo' STATUS = ( (STATUS_QUO, _('Status Quo')), ('2_redigiert', _('Objekte redigiert')), ('3_aufgehangt', _('Objekte aufgehängt')), ) # To Later enable status for Items # DRAFT = 'draft' # PUBLISH_STATUS = ( # ('under_review', _("Under Review")), # ('published', _("Published")), # (DRAFT, _("Draft")), # ) # publish_status = models.CharField( # _("publish status"), max_length=64, blank=False, null=False, choices=PUBLISH_STATUS, # default=DRAFT # ) is_active = models.BooleanField(_("is active"), default=True) status = models.CharField(max_length=15, choices=STATUS, default=STATUS_QUO) inventory_number = models.CharField( _("Inventory number"), max_length=30, db_index=True, blank=True, null=True ) inventory_number_eb = models.CharField( _("Inventory number (eb)"), max_length=4, db_index=True, blank=True, null=True ) research = models.BooleanField(_("recherche"), default=False) prio = models.BooleanField(_("prio"), default=False) title = models.CharField(_("title"), max_length=400) title_short = models.CharField( _("title (short)"), max_length=50, blank=True, null=True, help_text=_("use on the label") ) participant = models.CharField( _("participant"), max_length=256, blank=True, null=True ) participant_short = models.CharField( _("participant short"), max_length=27, blank=True, null=True, help_text=_("use on the label") ) date = models.CharField(_("date"), max_length=50, blank=True, null=True) date_short = models.CharField( _("date (short)"), max_length=50, blank=True, null=True, help_text=_("use on the label") ) group = models.ForeignKey(ItemGroup, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("group"), editable=False) thesaurus_obj = models.ManyToManyField(Thesaurus, blank=True, editable=False) thesaurus = models.TextField(blank=True, null=True, editable=False) tags = models.ManyToManyField(Tag, limit_choices_to={"is_active": True}, blank=True) site = models.ForeignKey(Site, on_delete=models.CASCADE) size = models.CharField(_("size"), max_length=100, blank=True, null=True, editable=False) size_short = models.CharField( _("size (short)"), max_length=100, blank=True, null=True, editable=False ) description = models.TextField(_("description"), blank=True, null=True) description_short = models.CharField( _("description (short)"), max_length=213, blank=True, null=True, help_text=_("use on the label") ) history = models.TextField(_("history"), null=True, blank=True) history_short = models.CharField( # back of label _("history (short)"), max_length=463, blank=True, null=True, help_text=_("use on the label") ) youtube_url = models.URLField( _("youtube url"), max_length=200, blank=True, null=True ) image_path = models.CharField( _("image path"), max_length=300, blank=True, null=True, editable=False ) image_name = models.CharField( _("image name"), max_length=200, blank=True, null=True, editable=False ) image_suffix = models.CharField( _("image suffix"), max_length=10, blank=True, null=True, editable=False ) image = models.ImageField(_("image"), upload_to="items/", null=True, blank=True, height_field="image_height", width_field="image_width") image_height = models.IntegerField(null=True, blank=True, editable=False) image_width = models.IntegerField(null=True, blank=True, editable=False) thumbnail = models.ImageField(_("thumbnail"), upload_to="items_thumbnails/", null=True, blank=True, editable=False, height_field="thumbnail_height", width_field="thumbnail_width") thumbnail_height = models.IntegerField(null=True, blank=True, editable=False) thumbnail_width = models.IntegerField(null=True, blank=True, editable=False) video = models.FileField( upload_to="videos/", verbose_name=_("Video"), null=True, blank=True, editable=False, help_text=_("Upload only gif files"), ) notes = models.TextField(_("notes"), null=True, blank=True, help_text=_("für den Administrator")) def __str__(self): return self.title class Meta: verbose_name = _("item") verbose_name_plural = _("items") def export_as_json(self): palette = ["083d77", "ebebd3", "f4d35e", "ee964b", "f95738"] img = None thumbnail = None if bool(self.image): img = self.image.url if bool(self.thumbnail): thumbnail = self.thumbnail.url video_url = None if bool(self.video): video_url = self.video.url group_title = "" if self.group is not None: group_title = self.group.title data = { "url": reverse("item", args=[self.id]), "inventory_number": self.inventory_number_eb or f"i-{self.id}", "title": self.title_short or '', "participant": self.participant_short or "", "date": self.date_short or "", "group": group_title, "size": self.size or '', "description": self.description_short or "", "history": self.history_short or "", "image": img, "image_height": self.image_height, "image_width": self.image_width, "thumbnail": thumbnail, "thumbnail_height": self.thumbnail_height, "thumbnail_width": self.thumbnail_width, "video": video_url, "prio": self.prio, "thesaurus": '', # [item.full_path for item in self.thesaurus_obj.all()], "tags": [ {"name": tag.name, "slug": tag.slug} for tag in get_tags(self.tags.all()) ], } if self.youtube_url is not None: data["youtube"] = self.youtube_url if hasattr(self, "num_comments"): data["validated_comments_count"] = self.num_comments return data def save(self, *args, **kwargs): super().save(*args, **kwargs) if self.pk is not None and self.image: # check if Item has image (avoid item with video) self.thumbnail = make_thumbnail(self.image, size=THUMB_SIZE) super().save(*args, **kwargs) def get_tags(qs, hide_categories=None): final_tag_list = [] tags_per_group = {} for tag_obj in qs: if len(tag_obj.name) == 0 or tag_obj.is_active is False: continue if tag_obj.category not in tags_per_group: tags_per_group[tag_obj.category] = [] tags_per_group[tag_obj.category].append(tag_obj) for category in TAG_CATEGORIES_ORDER: if category in tags_per_group: if hide_categories is not None: if category in hide_categories: continue for tag in tags_per_group[category]: final_tag_list.append(tag) return final_tag_list def item_tags(qs, hide_categories=None): final_tag_list = get_tags(qs, hide_categories=hide_categories) return " ".join(["#{}".format(tag.name) for tag in final_tag_list if len(tag.name) > 0]) class Comment(models.Model): ADDED = "1_added" VALIDATED = "2_validated" REFUSED = "3_refused" LABELLED = "4_labelled" STATUS = ( (ADDED, _("Added")), (VALIDATED, _("Validated")), (LABELLED, _("Labelled")), (REFUSED, _("Refused")) ) KEEP_CHOICES = ( ("forever", _("forever")), ("until_2021", _("until 2021")), ) item = models.ForeignKey( Item, on_delete=models.CASCADE, related_name="comments", null=True, blank=True, verbose_name=_("item") ) added_on = models.DateTimeField(_("added on"), default=timezone.now, editable=False) status = models.CharField(_("status"), max_length=15, db_index=True, choices=STATUS, default=ADDED ) site = models.ForeignKey(Site, on_delete=models.CASCADE) object_id = models.CharField(_("Object ID"), max_length=300, null=True, blank=True) object_type = models.CharField( _("object type"), max_length=300, null=True, blank=True ) description = models.TextField(_("Description")) attachment = models.FileField(_("Attachment"), upload_to="uploads/", null=True, blank=True) first_name = models.CharField(_("first name"), max_length=300) last_name = models.CharField(_("last name"), max_length=300) address = models.CharField(_("address"), max_length=300, null=True, blank=True) post_code = models.CharField(_("post code"), max_length=300, null=True, blank=True) birth_year = models.CharField(_("birth year"), max_length=300, null=True, blank=True) city = models.CharField(_("city"), max_length=300, null=True, blank=True) phone = models.CharField(_("phone"), max_length=300, null=True, blank=True) email = models.EmailField(_("email")) keep = models.CharField( _("keep"), max_length=300, choices=KEEP_CHOICES, null=True, blank=True ) admin_comment = models.TextField(_("Admin comment"), null=True, blank=True) misc_data = models.JSONField(default=dict) @staticmethod def post_comment_save(sender, instance, created, **kwargs): if created: # Send email comment_admin_url = reverse('admin:catalog_comment_change', args=[instance.id]) url = f"https://www.e-fundbuero.ch{comment_admin_url}" send_mail( gettext('New message'), gettext('You have a new message. You can retrieve it from %(url)s.') % {'url': url}, 'noreply@e-fundbuero.ch', [settings.ADMIN_EMAIL], fail_silently=True, ) def __str__(self): if self.object_id is not None and len(self.object_id) > 0: return self.object_id elif self.object_type is not None and len(self.object_type) > 0: return self.object_type else: return "comment {}".format(self.pk) class Meta: verbose_name = _("comment") verbose_name_plural = _("comments") post_save.connect(Comment.post_comment_save, sender=Comment) class Ad(models.Model): title = models.CharField(_("ad title"), max_length=400) video_de = models.FileField( upload_to="ads/videos/de/", verbose_name=_("Video (German)"), help_text="Upload only mp4 video", ) title_de = models.CharField(_("title (German)"), max_length=300, default="", null=False, blank=True) text_de = models.TextField(_("Text (German)"), default="", null=False, blank=True) video_fr = models.FileField( upload_to="ads/videos/fr/", verbose_name=_("Video (French)"), help_text="Upload only mp4 video", null=True, blank=True, ) title_fr = models.CharField(_("title (French)"), max_length=300,default="", null=False, blank=True) text_fr = models.TextField(_("Text (French)"), default="", null=False, blank=True) video_it = models.FileField( upload_to="ads/videos/it/", verbose_name=_("Video (Italian)"), help_text="Upload only mp4 video", null=True, blank=True, ) title_it = models.CharField(_("title (Italian)"), max_length=300, default="", null=False, blank=True) text_it = models.TextField(_("Text (Italian)"), default="", null=False, blank=True) video_en = models.FileField( upload_to="ads/videos/en/", verbose_name=_("Video (English)"), help_text="Upload only mp4 video", null=True, blank=True, ) title_en = models.CharField(_("title (English)"), max_length=300, default="", null=False, blank=True) text_en = models.TextField(_("Text (English)"), default="", null=False, blank=True) prio = models.BooleanField(_("prio"), default=False) show_per_page = models.BooleanField(_("show per page"), default=False) site = models.ForeignKey(Site, on_delete=models.CASCADE) def __str__(self): return self.title class Meta: verbose_name = _("ad") verbose_name_plural = _("ads")