import random import datetime import math from django.contrib.postgres import search from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models from django.utils import timezone from authentication.models import User VERIFICATION_STATUS = [ ("PENDING", "Diproses"), ("APPROVE", "Diterima"), ("DISAPPROVE", "Ditolak"), ("REVISION", "Perbaikan"), ("BLOCKED", "Diblokir"), ] # Create your models here. def get_random_color(): color = "%06x" % random.randint(0, 0xFFFFFF) return color def current_year(): return datetime.date.today().year class Category(models.Model): name = models.CharField(max_length=20) description = models.TextField(blank=False, default="") archived = models.BooleanField(default=False, blank=False) archived_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) def __str__(self): return self.name class MateriManager(models.Manager): def __init__(self, *args, **kwargs): self.alive_only = kwargs.pop('alive_only', True) super(MateriManager, self).__init__(*args, **kwargs) def get_queryset(self): if self.alive_only: return SoftDeletionQuerySet(self.model).filter(deleted_at=None) return SoftDeletionQuerySet(self.model) def search(self, search_text): search_vector = None for field, weight in Materi.SEARCH_INDEX: if search_vector is None: search_vector = search.SearchVector(field, weight=weight) else: search_vector += search.SearchVector(field, weight=weight) search_query = search.SearchQuery(search_text) search_rank = search.SearchRank(search_vector, search_query) search_result = ( self.get_queryset() .filter(_search_vector=search_query) .annotate(rank=search_rank) .order_by("-rank") ) return search_result class SoftDeletionQuerySet(models.query.QuerySet): def delete(self): return super(SoftDeletionQuerySet, self).update(deleted_at=timezone.now()) class SoftDeleteModel(models.Model): deleted_at = models.DateTimeField(blank=True, null=True) all_objects = MateriManager(alive_only=False) class Meta: abstract = True def soft_delete(self): self.deleted_at = timezone.now() self.save() class Materi(SoftDeleteModel): cover = models.ImageField() content = models.FileField() title = models.CharField(max_length=50, default="Judul") author = models.CharField(max_length=30, default="Penyusun") uploader = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) publisher = models.CharField(max_length=30, default="Penerbit") release_year = models.IntegerField(default=current_year) pages = models.IntegerField(default=0) descriptions = models.TextField(default="Deskripsi") status = models.CharField(max_length=30, choices=VERIFICATION_STATUS, default=VERIFICATION_STATUS[0][0]) categories = models.ManyToManyField(Category) date_created = models.DateTimeField(default=timezone.now) date_modified = models.DateTimeField(auto_now=True) yt_video_id = models.CharField(max_length=100, blank=True, null=True) _search_vector = search.SearchVectorField(null=True, editable=False) SEARCH_INDEX = ( ("title", "A"), ("author", "A"), ("publisher", "C"), ("descriptions", "C"), ("uploader", "D"), ) objects = MateriManager() def save(self, *args, **kwargs): super().save(*args, **kwargs) search_index = {field: weight for (field, weight) in Materi.SEARCH_INDEX} if "update_fields" in kwargs: is_search_index_updated = bool( set(search_index.keys()) & set(kwargs["update_fields"]) ) if ("update_fields" not in kwargs) or (is_search_index_updated): self._search_vector = None for field, weight in search_index.items(): if self._search_vector is None: self._search_vector = search.SearchVector(field, weight=weight) else: self._search_vector += search.SearchVector(field, weight=weight) self.save(update_fields=["_search_vector"]) @property def is_published(self): published = False if self.verificationreport_set.exists(): report = self.verificationreport_set.latest("timestamp") published = True if report.status == "Diterima" else False return published @property def published_date(self): published_date = None if self.verificationreport_set.exists(): report = self.verificationreport_set.latest("timestamp") if report.status == "Diterima": published_date = report.timestamp return published_date @property def like_count(self): count = Like.objects.filter(materi=self).count() return count @property def comment_count(self): count = Comment.objects.filter(materi=self).count() return count @property def review_count(self): count = Review.objects.filter(materi=self).count() return count @staticmethod def earliest_materi_timestamp(): return Materi.objects.earliest('date_created').date_created.timestamp() @property def seconds_since_earliest_materi(self): return self.date_created.timestamp() - Materi.earliest_materi_timestamp() @property def view_count(self): count = ViewStatistics.objects.filter(materi=self).count() return count @property def hot_score(self): view_score = math.log(max(self.view_count, 1), 10) time_score = self.seconds_since_earliest_materi / 604800 # 1 week return round(view_score + time_score, 7) @property def is_like(self): like = False if Like.objects.filter(materi=self).exists(): like = True return like class Comment(models.Model): username = models.CharField(max_length=100) profile = models.CharField(max_length=100, default=get_random_color) comment = models.CharField(max_length=240, default="comments") materi = models.ForeignKey(Materi, models.SET_NULL, null=True) user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True) timestamp = models.DateTimeField(default=timezone.now) def __str__(self): return self.username @property def like_count(self): count = LikeComment.objects.filter(comment=self).count() return count @property def dislike_count(self): count = DislikeComment.objects.filter(comment=self).count() return count class Review(models.Model): username = models.CharField(max_length=100) profile = models.CharField(max_length=100, default=get_random_color) review = models.TextField(default="review") materi = models.ForeignKey(Materi, models.SET_NULL, null=True) user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True) timestamp = models.DateTimeField(default=timezone.now) def __str__(self): return self.username class LikeComment(models.Model): comment = models.ForeignKey(Comment, models.SET_NULL, null=True) timestamp = models.DateTimeField(default=timezone.now) session_id = models.CharField(max_length=32, blank=False) class DislikeComment(models.Model): comment = models.ForeignKey(Comment, models.SET_NULL, null=True) timestamp = models.DateTimeField(default=timezone.now) session_id = models.CharField(max_length=32, blank=False) class Like(models.Model): materi = models.ForeignKey(Materi, models.SET_NULL, null=True) timestamp = models.DateTimeField(default=timezone.now) session_id = models.CharField(max_length=32, blank=False) class ReqMaterial(models.Model): title = models.CharField(max_length=100) timestamp = models.DateTimeField(default=timezone.now) class SubmitVisitor(models.Model): user_id = models.CharField(max_length=50) email = models.CharField(max_length=50) msg = models.CharField(max_length=100) timestamp = models.DateTimeField(default=timezone.now) class ViewStatistics(models.Model): materi = models.ForeignKey(Materi, models.SET_NULL, null=True, related_name="baca") timestamp = models.DateTimeField(default=timezone.now) class DownloadStatistics(models.Model): materi = models.ForeignKey(Materi, models.SET_NULL, null=True, related_name="unduh") downloader = models.ForeignKey(User, models.SET_NULL, blank=True, null=True, related_name="riwayat_unduh") timestamp = models.DateTimeField(default=timezone.now) class DummyLike(models.Model): item = models.ForeignKey(Like, on_delete=models.CASCADE) class DummyViewStatistics(models.Model): item = models.ForeignKey(ViewStatistics, on_delete=models.CASCADE) class DummyDownloadStatistics(models.Model): item = models.ForeignKey(DownloadStatistics, on_delete=models.CASCADE) class DummyComment(models.Model): item = models.ForeignKey(Comment, on_delete=models.CASCADE) class Rating(models.Model): materi = models.ForeignKey(Materi, models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True) timestamp = models.DateTimeField(default=timezone.now) score = models.IntegerField() def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if 1 <= self.score <= 5: super().save(force_insert, force_update, using, update_fields) else: raise ValidationError("Rating score must be integer between 1-5") def __str__(self): return "Material:{} | User:{} | Rating:{}".format(self.materi.title, self.user.name, self.score) class Meta: unique_together = ["materi", "user"] class RatingContributor(models.Model): timestamp = models.DateTimeField(auto_now=True) score = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)]) contributor = models.ForeignKey(User, on_delete=models.CASCADE, related_name='contributor') user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user') def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if 1 <= self.score <= 5: super().save(force_insert, force_update, using, update_fields) else: raise ValidationError("Rating score must be integer between 1-5") class Meta: unique_together = ["contributor", "user"] class LaporanMateri(models.Model): materi = models.ForeignKey(Materi, on_delete=models.CASCADE, max_length=120) user = models.ForeignKey(User, on_delete=models.CASCADE) laporan = models.TextField(validators=[MinValueValidator(30), MaxValueValidator(120)], default="") timestamp = models.DateTimeField(default=timezone.now) is_rejected = models.BooleanField(default=False) class ReadLater(models.Model): materi = models.ForeignKey(Materi, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE) timestamp = models.DateTimeField(default=timezone.now) class Meta: unique_together = ["materi", "user"] class NotifikasiKontributor(models.Model): materi = models.ForeignKey(Materi, on_delete=models.CASCADE, max_length=120) user = models.ForeignKey(User, on_delete=models.CASCADE) feedback = models.CharField(max_length=20, default="") def __str__(self): return "Status: {}".format(self.feedback) class AdminNotification(models.Model): materi = models.ForeignKey(Materi, on_delete=models.CASCADE)