diff --git a/administration/services.py b/administration/services.py new file mode 100644 index 0000000000000000000000000000000000000000..66cb34abca20fe9638c209298fa7796147453520 --- /dev/null +++ b/administration/services.py @@ -0,0 +1,206 @@ +from dateutil.relativedelta import relativedelta +from django.db.models import Count +from django.utils import timezone +from datetime import datetime + +from administration.forms import PeriodForm +from administration.models import VerificationSetting +from app.models import LaporanMateri, Materi + + +class DateHelperService: + + @staticmethod + def get_start_end_date(period): + if period == 'ALL_TIME': + return (None, None) + elif period.split("_")[0] == "LAST": + end, start = DateHelperService.handle_last(period) + return (start, end) + elif period.split("_")[0] == "CURRENT": + end, start = DateHelperService.handle_current(period) + return (start, end) + elif period.split("_")[0] == "MINUS": + end, start = DateHelperService.handle_minus(period) + return (start, end) + else: + return (None, None) + + @staticmethod + def handle_minus(period): + start = timezone.now() + end = None + delta = int(period.split("_")[1]) + if period.split("_")[2] == "MONTH": + start = start.replace(day=1, hour=0, minute=0, + second=0, microsecond=0) + start = start - relativedelta(months=delta) + end = start + relativedelta(months=1) + elif period.split("_")[2] == "YEAR": + start = start.replace(month=1, day=1, hour=0, + minute=0, second=0, microsecond=0) + start = start - relativedelta(years=delta) + end = start + relativedelta(years=1) + return end, start + + @staticmethod + def handle_current(period): + start = timezone.now() + end = None + if period.split("_")[1] == "MONTH": + start = start.replace(day=1, hour=0, minute=0, + second=0, microsecond=0) + end = start + relativedelta(months=1) + elif period.split("_")[1] == "YEAR": + start = start.replace(month=1, day=1, hour=0, + minute=0, second=0, microsecond=0) + end = start + relativedelta(years=1) + return end, start + + @staticmethod + def handle_last(period): + end = timezone.now() + delta = 7 + if period.split("_")[1] == "WEEK": + delta = 7 + if period.split("_")[1] == "MONTH": + delta = 30 + if period.split("_")[1] == "QUARTER": + delta = 90 + if period.split("_")[1] == "YEAR": + delta = 365 + start = end - relativedelta(days=delta) + return end, start + +class DetailVerificationService: + + @staticmethod + def init_data(context, materi): + context["materi_data"] = materi + context["kriteria_list"] = VerificationSetting.objects.filter( + archived=False) + if materi.status == "PENDING" or materi.status == "REVISION": + riwayat = False + else: + riwayat = True + context["riwayat"] = riwayat + context["verification_report"] = materi.verificationreport_set.first() + + @staticmethod + def initilize_kriteria(items) -> list: + kriteria = [] + for item in items: + if "kriteria" in item[0]: + verification_item_id = item[0].split("-")[1] + verif_obj = VerificationSetting.objects.get( + id=int(verification_item_id)) + kriteria_value = { + "title": verif_obj.title, + "status": True if item[1] == "1" else False + } + kriteria.append(kriteria_value) + return kriteria + + @staticmethod + def action_approve(materi): + DetailVerificationService.save_materi_status(materi, "APPROVE") + + @staticmethod + def action_disapprove(materi): + DetailVerificationService.save_materi_status(materi, "DISAPPROVE") + + @staticmethod + def save_materi_status(materi, status: str): + materi.status = status + materi.save() + +class StatisticService: + + @staticmethod + def get_filter_set(period, kwargs): + filter_set = {} + form = PeriodForm() + DEFAULT_PERIOD = "LAST_MONTH" + if form.is_valid(): + form_data = form.cleaned_data + if len(form_data["categories"]) == 0: + form_data["categories"] = None + + if form_data["period"] == "": + form_data["period"] = DEFAULT_PERIOD + + # check if using custom date range + end, start = StatisticService.init_date(DEFAULT_PERIOD, form_data) + filter_set["start"] = start + filter_set["end"] = end + filter_set["categories"] = form_data["categories"] + return filter_set + + @staticmethod + def init_date(default_period, form_data): + if form_data["start_date"] is not None and form_data["end_date"] is not None: + start = timezone.make_aware(datetime.combine( + form_data["start_date"], datetime.min.time())) + end = timezone.make_aware(datetime.combine( + form_data["end_date"], datetime.min.time())) + elif form_data["period"] is not None: + start, end = DateHelperService.get_start_end_date(form_data["period"]) + else: + start, end = DateHelperService.get_start_end_date(default_period) + return end, start + + @staticmethod + def generate_view_stat(chart_data, colors, data_sets, label, time_step): + for label, color, dataset in zip(label, colors, data_sets): + result = { + "label": label, + "backgroundColor": color, + "borderColor": color, + "borderWidth": 1 + } + data = [] + total = 0 + s = 0 + e = 1 + while e < len(time_step): + todays = dataset.filter( + timestamp__gte=time_step[s], timestamp__lt=time_step[e]).count() + data.append(todays) + total += todays + s += 1 + e += 1 + result["data"] = data + chart_data["charts"].append(result) + chart_data["total"].append(total) + + + @staticmethod + def get_query_set(user, object_statistic, **kwargs): + if user.is_admin: + return object_statistic.objects.filter(materi__status="APPROVE") + else: + return object_statistic.objects.filter(materi__status="APPROVE", materi__uploader=user.id) + + + @staticmethod + def filter_qs(qs, filter_set): + if filter_set.get("categories", None) is not None: + qs = qs.filter(materi__categories__in=filter_set.get("categories")) + if filter_set.get("start") is not None and filter_set.get("end") is not None: + s = filter_set.get("start") + e = filter_set.get("end") + qs = qs.filter(timestamp__gte=s, timestamp__lt=e) + return qs.distinct() + +class LaporanMateriService: + + @staticmethod + def initilize_report(): + laporan_materi = LaporanMateri.objects.filter(is_rejected=False) + materi_dilaporkan = Materi.objects \ + .filter(laporanmateri__id__in=laporan_materi, status="APPROVE") \ + .annotate(jumlah_laporan=Count('laporanmateri__materi_id')) \ + .order_by('-jumlah_laporan') \ + .distinct() + materi_diblokir = Materi.objects.filter(status="BLOCKED") + return laporan_materi, materi_dilaporkan, materi_diblokir diff --git a/administration/views.py b/administration/views.py index 864f01e4963587b36be31a21ab89cf0284ad9e97..a4cdab81fc557da42e75d73185dab0936dd81f49 100644 --- a/administration/views.py +++ b/administration/views.py @@ -1,19 +1,15 @@ -import json -from datetime import datetime, date - from django.core.exceptions import PermissionDenied from django.contrib.auth.hashers import make_password from django.db.models import Count from django.http import HttpResponseRedirect, JsonResponse -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404 from django.views.generic import TemplateView, View from django.contrib import messages -from django.utils import timezone, lorem_ipsum -from dateutil.relativedelta import relativedelta +from django.utils import timezone from administration.models import VerificationReport, VerificationSetting, DeletionHistory from administration.forms import CategoryForm, VerificationSettingForm, RegistrasiAdminForm, PeriodForm, EditAdminStatusForm -from app.models import Category, Materi, ViewStatistics, DownloadStatistics, Comment, Like, getRandomColor, LaporanMateri -from app.views import permission_denied +from administration.services import StatisticService, DetailVerificationService, LaporanMateriService +from app.models import Category, Materi, ViewStatistics, DownloadStatistics, Comment, Like, LaporanMateri from authentication.models import User from datetime import datetime @@ -24,52 +20,6 @@ from django.core import management ADMINISTRATION_MANAGEMENT = "/administration/kelola-admin/" ADMINISTRATION_REPORT = "/administration/laporan-materi/" -def get_start_end_date(period): - if period == 'ALL_TIME': - return (None, None) - elif period.split("_")[0] == "LAST": - end = timezone.now() - delta = 7 - if period.split("_")[1] == "WEEK": - delta = 7 - if period.split("_")[1] == "MONTH": - delta = 30 - if period.split("_")[1] == "QUARTER": - delta = 90 - if period.split("_")[1] == "YEAR": - delta = 365 - start = end - relativedelta(days=delta) - return (start, end) - elif period.split("_")[0] == "CURRENT": - start = timezone.now() - if period.split("_")[1] == "MONTH": - start = start.replace(day=1, hour=0, minute=0, - second=0, microsecond=0) - end = start + relativedelta(months=1) - return (start, end) - elif period.split("_")[1] == "YEAR": - start = start.replace(month=1, day=1, hour=0, - minute=0, second=0, microsecond=0) - end = start + relativedelta(years=1) - return (start, end) - elif period.split("_")[0] == "MINUS": - start = timezone.now() - delta = int(period.split("_")[1]) - if period.split("_")[2] == "MONTH": - start = start.replace(day=1, hour=0, minute=0, - second=0, microsecond=0) - start = start - relativedelta(months=delta) - end = start + relativedelta(months=1) - return (start, end) - elif period.split("_")[2] == "YEAR": - start = start.replace(month=1, day=1, hour=0, - minute=0, second=0, microsecond=0) - start = start - relativedelta(years=delta) - end = start + relativedelta(years=1) - return (start, end) - else: - return (None, None) - class VerificationView(TemplateView): template_name = "verif.html" @@ -104,44 +54,25 @@ class DetailVerificationView(TemplateView): context = super(DetailVerificationView, self).get_context_data(**kwargs) materi = get_object_or_404(Materi, pk=kwargs["pk"]) - context["materi_data"] = materi - context["kriteria_list"] = VerificationSetting.objects.filter( - archived=False) - if materi.status == "PENDING" or materi.status == "REVISION": - riwayat = False - else: - riwayat = True - context["riwayat"] = riwayat - context["verification_report"] = materi.verificationreport_set.first() + DetailVerificationService.init_data(context, materi) return context + def post(self, request, *args, **kwargs): materi = get_object_or_404(Materi, pk=kwargs["pk"]) feedback = request.POST.get("feedback", "") action = request.POST.get("action", None) report = {} - kriteria = [] - for item in request.POST.items(): - if "kriteria" in item[0]: - verification_item_id = item[0].split("-")[1] - verif_obj = VerificationSetting.objects.get( - id=int(verification_item_id)) - kriteria_value = { - "title": verif_obj.title, - "status": True if item[1] == "1" else False - } - kriteria.append(kriteria_value) - - report["kriteria"] = kriteria + items = request.POST.items() + + report["kriteria"] = DetailVerificationService.initilize_kriteria(items) report["feedback"] = feedback if action == "approve" and feedback != "": - materi.status = "APPROVE" - materi.save() + DetailVerificationService.action_approve(materi) elif action == "disapprove" and feedback != "": - materi.status = "DISAPPROVE" - materi.save() + DetailVerificationService.action_disapprove(materi) else: context = self.get_context_data(**kwargs) context["error"] = True @@ -151,6 +82,7 @@ class DetailVerificationView(TemplateView): verif_report.save() return HttpResponseRedirect("/administration/") + def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) return self.render_to_response(context=context) @@ -291,56 +223,9 @@ class StatisticsView(TemplateView): context["periodForm"] = PeriodForm(self.request.GET) return context - def get_filter_set(self, **kwargs): - filter_set = {} - form = PeriodForm(self.request.GET) - DEFAULT_PERIOD = "LAST_MONTH" - if form.is_valid(): - form_data = form.cleaned_data - if len(form_data["categories"]) == 0: - form_data["categories"] = None - - if form_data["period"] == "": - form_data["period"] = DEFAULT_PERIOD - - # check if using custom date range - if form_data["start_date"] is not None and form_data["end_date"] is not None: - start = timezone.make_aware(datetime.combine( - form_data["start_date"], datetime.min.time())) - end = timezone.make_aware(datetime.combine( - form_data["end_date"], datetime.min.time())) - elif form_data["period"] is not None: - start, end = get_start_end_date(form_data["period"]) - else: - start, end = get_start_end_date(DEFAULT_PERIOD) - filter_set["start"] = start - filter_set["end"] = end - filter_set["categories"] = form_data["categories"] - return filter_set - - def get_query_set(self, object_statistic, **kwargs): - if self.request.user.is_admin: - return object_statistic.objects.filter(materi__status="APPROVE") - else: - return object_statistic.objects.filter(materi__status="APPROVE", materi__uploader=self.request.user.id) - - def filter_qs(self, qs, filter_set): - if filter_set.get("categories", None) is not None: - qs = qs.filter(materi__categories__in=filter_set.get("categories")) - if filter_set.get("start") is not None and filter_set.get("end") is not None: - s = filter_set.get("start") - e = filter_set.get("end") - qs = qs.filter(timestamp__gte=s, timestamp__lt=e) - return qs.distinct() - # def generate_table_data(self, data_sets): - # table_data = {} - # counter = 1 - # row_materi_table = [] - # for item in data_sets: - # # return res def generate_chart_data(self, start, end, data_sets): chart_data = { @@ -354,41 +239,21 @@ class StatisticsView(TemplateView): for i in time_step: chart_data["label"].append(i.strftime("%d/%m/%Y")) - # generate view stat - for label, color, dataset in zip(label, colors, data_sets): - result = { - "label": label, - "backgroundColor": color, - "borderColor": color, - "borderWidth": 1 - } - data = [] - total = 0 - s = 0 - e = 1 - while e < len(time_step): - todays = dataset.filter( - timestamp__gte=time_step[s], timestamp__lt=time_step[e]).count() - data.append(todays) - total += todays - s += 1 - e += 1 - result["data"] = data - chart_data["charts"].append(result) - chart_data["total"].append(total) + StatisticService.generate_view_stat(chart_data, colors, data_sets, label, time_step) return chart_data + def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) - filter_set = self.get_filter_set(**kwargs) - view_set = self.get_query_set(ViewStatistics, **kwargs) - view_set = self.filter_qs(view_set, filter_set) - download_set = self.get_query_set(DownloadStatistics, **kwargs) - download_set = self.filter_qs(download_set, filter_set) - like_set = self.get_query_set(Like, **kwargs) - like_set = self.filter_qs(like_set, filter_set) - comment_set = self.get_query_set(Comment, **kwargs) - comment_set = self.filter_qs(comment_set, filter_set) + filter_set = StatisticService.get_filter_set(self.request.GET,**kwargs) + view_set = StatisticService.get_query_set(self.request.user,ViewStatistics, **kwargs) + view_set = StatisticService.filter_qs(view_set, filter_set) + download_set = StatisticService.get_query_set(self.request.user, DownloadStatistics, **kwargs) + download_set = StatisticService.filter_qs(download_set, filter_set) + like_set = StatisticService.get_query_set(self.request.user, Like, **kwargs) + like_set = StatisticService.filter_qs(like_set, filter_set) + comment_set = StatisticService.get_query_set(self.request.user, Comment, **kwargs) + comment_set = StatisticService.filter_qs(comment_set, filter_set) data_sets = [view_set, download_set, like_set, comment_set] chart_data = self.generate_chart_data( @@ -657,13 +522,11 @@ class LaporanMateriView(TemplateView): def get(self, request, *args, **kwargs): context = super(LaporanMateriView, self).get_context_data(**kwargs) - context["laporan_materi"] = LaporanMateri.objects.filter(is_rejected=False) - context["materi_dilaporkan"] = Materi.objects \ - .filter(laporanmateri__id__in=context["laporan_materi"], status="APPROVE") \ - .annotate(jumlah_laporan=Count('laporanmateri__materi_id')) \ - .order_by('-jumlah_laporan') \ - .distinct() - context["materi_diblokir"] = Materi.objects.filter(status="BLOCKED") + laporan_materi, materi_dilaporkan, \ + materi_diblokir = LaporanMateriService.initilize_report() + context["laporan_materi"] = laporan_materi + context["materi_dilaporkan"] = materi_dilaporkan + context["materi_diblokir"] = materi_diblokir return self.render_to_response(context=context) class LaporanMateriDetailView(TemplateView): @@ -700,4 +563,3 @@ def tolak_laporan(request, *args, **kwargs): laporan.save() return HttpResponseRedirect(ADMINISTRATION_REPORT + str(laporan.materi_id)) - diff --git a/app/services.py b/app/services.py new file mode 100644 index 0000000000000000000000000000000000000000..65199ff7103c9fb56c63cef5b89984cf38495765 --- /dev/null +++ b/app/services.py @@ -0,0 +1,426 @@ +import datetime +import os +import random + +from django.contrib import messages +from django.contrib.auth.models import AnonymousUser +from django.core.exceptions import ValidationError +from django.db.models import Count, Q +from django.shortcuts import get_object_or_404 +from pydrive.auth import GoogleAuth +from pydrive.drive import GoogleDrive + +from administration.models import VerificationReport +from app.forms import SuntingProfilForm +from app.models import Category, Like, LikeComment, DislikeComment, Materi, Comment, Rating, DownloadStatistics, \ + ViewStatistics +from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata +from digipus import settings + + +class DafterKatalogService: + + @staticmethod + def search_materi(get_search, lst_materi, url): + url = url + "&search={0}".format(get_search) + lst_materi = ( + lst_materi.search(get_search) + .filter( + Q(author__icontains=get_search) + | Q(uploader__name__icontains=get_search) + | Q(descriptions__icontains=get_search) + | Q(publisher__icontains=get_search) + ) + .distinct() + ) + return lst_materi, url + + @staticmethod + def filter_materi_by_kategori(get_kategori, lst_materi, url): + url = url + "&kategori={0}".format(get_kategori) + kategori = Category.objects.get(pk=get_kategori) + lst_materi = lst_materi.filter(categories=kategori.pk) + return lst_materi, url + + @staticmethod + def apply_sort(get_sort, lst_materi, url): + url = url + "&sort={0}".format(get_sort) + if (get_sort == "judul"): + lst_materi = lst_materi.order_by('title') + elif (get_sort == "penulis"): + lst_materi = lst_materi.order_by('author') + elif (get_sort == "pengunggah"): + lst_materi = lst_materi.order_by('uploader') + elif (get_sort == "terbaru"): + lst_materi = lst_materi.order_by('-date_created') + elif (get_sort == "terlama"): + lst_materi = lst_materi.order_by('date_created') + elif (get_sort == "terpopuler"): + lst_materi = lst_materi.annotate(count=Count('like__id')).order_by('-count') + elif (get_sort == "jumlah_unduh"): + lst_materi = lst_materi.annotate(count=Count('unduh__id')).order_by('-count') + return lst_materi, url + + @staticmethod + def apply_options(lst_materi, request, url): + should_random = bool(request.GET.get("random")) + get_search = request.GET.get("search") + if get_search: + lst_materi, url = DafterKatalogService.search_materi(get_search, lst_materi, url) + get_kategori = request.GET.get("kategori") + if get_kategori: + lst_materi, url = DafterKatalogService.filter_materi_by_kategori(get_kategori, lst_materi, url) + get_sort = request.GET.get("sort") + if get_sort: + lst_materi, url = DafterKatalogService.apply_sort(get_sort, lst_materi, url) + if should_random: + lst_materi = DafterKatalogService.apply_random(lst_materi) + return lst_materi, url + + @staticmethod + def apply_random(lst_materi): + return random.sample(list(lst_materi), len(lst_materi)) + + +class DetailMateriService: + + @staticmethod + def init_context_data(context, materi, session): + context["session_id"] = session.session_key + context["materi_data"] = materi + context["report"] = VerificationReport.objects.filter(materi=materi) + context["has_liked"] = Like.objects.filter(materi=materi, session_id=session.session_key).exists() + + + @staticmethod + def set_published_date(materi): + published_date = "" + if materi.published_date == None: + published_date = "n.d" + else: + published_date = materi.published_date.strftime("%Y-%m-%d %H:%M") + return published_date + + + @staticmethod + def init_citation_and_materi_rating(context, materi, published_date, request): + citation_apa = materi.author + " . (" + published_date + ") . " + materi.title + " . " + materi.publisher + context["citationAPA"] = citation_apa + context["citationIEEE"] = CitationService.get_citation_ieee(request, materi) + context["materi_rating_score"] = 0 + + @staticmethod + def find_comment_like_dislike(query_set_for_comment, session): + has_liked = {} + has_disliked = {} + for comment in query_set_for_comment: + has_liked[comment.id] = LikeComment.objects.filter(comment=comment, + session_id=session.session_key).exists() + has_disliked[comment.id] = DislikeComment.objects.filter(comment=comment, + session_id=session.session_key).exists() + return has_disliked, has_liked + + @staticmethod + def get_user_name(request): + user_name = "" + if isinstance(request.user, AnonymousUser): + user_name = "Anonim" + elif request.user.is_admin: + user_name = "Admin" + elif request.user.is_contributor: + user_name = request.user.name + return user_name + +class CitationService: + + @staticmethod + def get_citation_ieee(request, materi): + current_date = datetime.datetime.now() + current_day = str(current_date.day) + current_month = current_date.strftime("%b") + current_year = str(current_date.year) + published_date = CitationService._get_ieee_publish_date(materi) + + author_list = materi.author.split(",") + author_list_abbrv = "" + for author_name in author_list: + author_name_split = author_name.split(" ") + author_name_abbrv = "" + for j, name in enumerate(author_name_split): + if j < (len(author_name_split) - 1): + abbrv_name = name[0].upper() + author_name_abbrv = author_name_abbrv + abbrv_name + ". " + else: + author_name_abbrv = author_name_abbrv + name + author_list_abbrv = author_list_abbrv + author_name_abbrv + ", " + + citation_result = ( + author_list_abbrv + + materi.title + + ". " + + materi.publisher + + ", " + + published_date + + ". " + + "Accessed on: " + + current_month + + ". " + + current_day + + ", " + + current_year + + ". [Online]. " + + "Available: " + + request.build_absolute_uri() + ) + return citation_result + + @staticmethod + def _get_ieee_publish_date(materi): + if materi.published_date == None: + published_date = "n.d" + else: + published_date = materi.published_date.strftime("%Y") + return published_date + +class LikeDislikeService: + + @staticmethod + def apply_like_materi(materi_id, session_id): + materi = get_object_or_404(Materi, pk=materi_id) + has_liked = Like.objects.filter(materi=materi, session_id=session_id).exists() + if has_liked: + like = get_object_or_404(Like, materi=materi, session_id=session_id) + like.delete() + response = {"success": True, "liked": True} + else: + Like(materi=materi, session_id=session_id).save() + response = {"success": True, "liked": False} + return response + + + @staticmethod + def apply_comment_like(comment_id, session_id): + comment = get_object_or_404(Comment, pk=comment_id) + has_liked = LikeComment.objects.filter(comment=comment, session_id=session_id).exists() + if has_liked: + like = get_object_or_404(LikeComment, comment=comment, session_id=session_id) + like.delete() + response = {"success": True, "liked": True, "comment_id": comment_id} + else: + LikeComment(comment=comment, session_id=session_id).save() + response = {"success": True, "liked": False, "comment_id": comment_id} + return response + + + @staticmethod + def apply_comment_dislike(comment_id, session_id): + comment = get_object_or_404(Comment, pk=comment_id) + has_disliked = DislikeComment.objects.filter(comment=comment, session_id=session_id).exists() + if has_disliked: + dislike = get_object_or_404(DislikeComment, comment=comment, session_id=session_id) + dislike.delete() + response = ({"success": True, "disliked": True, "comment_id": comment_id}) + else: + DislikeComment(comment=comment, session_id=session_id).save() + response = ({"success": True, "disliked": False, "comment_id": comment_id}) + return response + + +class MateriFieldValidationHelperService: + + @staticmethod + def validate_materi_rating_params(materi_id, rating_score): + if materi_id is None or rating_score is None: + response = {"success": False, "msg": "Missing param"} + status_code = 422 + is_valid = False + return is_valid, materi_id, rating_score, response, status_code + try: + rating_score = int(rating_score) + except ValueError: + response = {"success": False, "msg": "rating_score must be an integer"} + status_code = 422 + is_valid = False + return is_valid, materi_id, rating_score, response, status_code + try: + materi_id = int(materi_id) + except ValueError: + response = {"success": False, "msg": "materi_id must be an integer"} + status_code = 422 + is_valid = False + return is_valid, materi_id, rating_score, response, status_code + if rating_score not in range(1, 6): + response = {"success": False, "msg": "Rating must be an integer from 1 to 5"} + status_code = 422 + is_valid = False + return is_valid, materi_id, rating_score, response, status_code + return True, materi_id, rating_score, {}, 200 + + + + @staticmethod + def validate_materi_rating(materi_id, user): + materi = Materi.objects.filter(pk=materi_id).first() + if materi is None: + response = {"success": False, "msg": "Materi does not exist"} + status_code = 422 + is_valid = False + return is_valid, materi, response, status_code + if Rating.objects.filter(materi=materi, user=user).first() is not None: + response = {"success": False, "msg": "Rating already exist"} + status_code = 409 + is_valid = False + return is_valid, materi, response, status_code + return True, materi, {}, 201 + + + +class DownloadViewMateriHelperService: + + @staticmethod + def build_materi_response(fh, file_path, materi, mimetype, request, response): + response["Content-Disposition"] = "attachment; filename=" + os.path.basename(file_path) + if request.user.is_authenticated: + DownloadStatistics(materi=materi, downloader=request.user).save() + else: + downloaded_materi = DownloadStatistics.objects.create(materi=materi) + if "downloaded_materi" not in request.session: + request.session["downloaded_materi"] = [] + request.session["downloaded_materi"].append(downloaded_materi.pk) + request.session.modified = True + return response + + @staticmethod + def build_view_materi_response(file_path, materi, response): + response["Content-Disposition"] = "inline; filename=" + os.path.basename(file_path) + ViewStatistics(materi=materi).save() + + +class UploadMateriService: + + @staticmethod + def validate_extension(value): + ext = os.path.splitext(value.name)[1] # [0] returns path+filename + valid_extensions = [".pdf", ".doc", ".docx", ".jpg", ".png", ".xlsx", ".xls", ".mp4", ".mp3"] + if not ext.lower() in valid_extensions: + raise ValidationError("Unsupported file extension.") + + @staticmethod + def validate_file_extension(konten, request): + is_file_extension_valid = True + try: + UploadMateriService.validate_extension(konten) + except ValidationError: + messages.error(request, "Materi gagal diunggah, format file tidak sesuai") + is_file_extension_valid = False + return is_file_extension_valid + + @staticmethod + def upload_materi(form, materi): + materi.save() + kateg = form.cleaned_data["categories"] + for i in kateg: + materi.categories.add(i) + + + @staticmethod + def validate_excel_categories(categories, excel, i, message): + for c in excel["Categories"][i].split(","): + sel_cat = categories.filter(name=c) + if sel_cat.count() == 0: + message = f"Kategori %s tidak ditemukan" % c + break + return message + + + @staticmethod + def validate_excel_field_length(excel, field_length, i, message): + if len(excel["Title"][i]) > field_length["title"]: + message = f"Title maksimal %d karakter" % field_length["title"] + if len(excel["Author"][i]) > field_length["author"]: + message = f"Author maksimal %d karakter" % field_length["author"] + if len(excel["Publisher"][i]) > field_length["publisher"]: + message = f"Publisher maksimal %d karakter" % field_length["publisher"] + return message + + @staticmethod + def upload_materi_excel(categories, excel, request, row): + for i in range(row): + materi = Materi( + title=excel["Title"][i], + author=excel["Author"][i], + publisher=excel["Publisher"][i], + descriptions=excel["Description"][i], + uploader=request.user, + ) + materi.save() + + for c in excel["Categories"][i].split(","): + materi.categories.add(categories.get(name=c)) + +class EditProfileService: + + @staticmethod + def update_profile_picture(current_user, request): + f_name = request.FILES["profile_picture"].name + f_name = get_random_filename(f_name) + f_path = settings.MEDIA_ROOT + "/" + f_name + request.FILES["profile_picture"].name = f_name + form = SuntingProfilForm(request.POST, request.FILES, instance=current_user) + form.save() + remove_image_exifdata(f_path) + +class RevisiMateriService: + + @staticmethod + def revisi_materi(form, request): + materi = form.save(commit=False) + materi.uploader = request.user + materi.status = "REVISION" + materi.save() + kateg = form.cleaned_data["categories"] + for i in kateg: + materi.categories.add(i) + materi.save() + + +class DownloadHistoryService: + + @staticmethod + def get_riwayat_as_guest(request): + has_downloaded_materi = "downloaded_materi" in request.session + downloaded_materi = request.session["downloaded_materi"] if has_downloaded_materi else [] + riwayat_list = DownloadStatistics.objects.filter(pk__in=downloaded_materi).order_by("-timestamp") + return riwayat_list + + @staticmethod + def get_riwayat_authenticated_user(current_user): + riwayat_list = current_user.riwayat_unduh.all().order_by("-timestamp") + return riwayat_list + + @staticmethod + def init_data_guest_user(context, request): + riwayat_list = DownloadHistoryService.get_riwayat_as_guest(request) + context["riwayat_list"] = riwayat_list + context["user_name"] = "Guest" + + @staticmethod + def init_data_authenticated_user(context, current_user): + riwayat_list = DownloadHistoryService.get_riwayat_authenticated_user(current_user) + context["riwayat_list"] = riwayat_list + context["user_name"] = current_user.name + + +class GoogleDriveUploadService: + + @staticmethod + def upload_to_gdrive(file_path, title): + gauth = GoogleAuth() + gauth.LocalWebserverAuth() + + drive = GoogleDrive(gauth) + file1 = drive.CreateFile() + file1.SetContentFile(file_path) + file1["title"] = title + print("title: %s, mimeType: %s" % (file1["title"], file1["mimeType"])) + file1.Upload() diff --git a/app/tests.py b/app/tests.py index 6f040bdb2730f9345e35723f04c5731ff4681194..a233ab6f822bf9590054382bd09b1ec5464561df 100644 --- a/app/tests.py +++ b/app/tests.py @@ -584,13 +584,13 @@ class DetailMateriTest(TestCase): response = Client().get(self.url) self.assertContains(response, "Google Drive") - @mock.patch("app.views.upload_to_gdrive") + @mock.patch("app.services.GoogleDriveUploadService.upload_to_gdrive") def test_save_to_gdrive_with_nonexistent_materi(self, mock_upload_to_gdrive): response = self.client.get("/materi/%s/save-to-gdrive/" % 0) mock_upload_to_gdrive.assert_not_called() self.assertEqual(response.status_code, 404) - @mock.patch("app.views.upload_to_gdrive") + @mock.patch("app.services.GoogleDriveUploadService.upload_to_gdrive") def test_save_to_gdrive_with_valid_materi(self, mock_upload_to_gdrive): response = self.client.get("/materi/%s/save-to-gdrive/" % self.materi1.id, follow=True) last_url, status_code = response.redirect_chain[-1] diff --git a/app/views.py b/app/views.py index c48aebbc8659d0532996163478278099cc975852..ce550f5f8f3e95999c111f89f65f3c9f8eb9bcf2 100644 --- a/app/views.py +++ b/app/views.py @@ -1,45 +1,35 @@ import mimetypes import os -import datetime +from io import BytesIO +import django +import pandas as pd from django.conf import settings from django.contrib import messages -from django.contrib.auth.models import AnonymousUser -from django.core.exceptions import PermissionDenied, ValidationError -from django.db.models import Q, Count +from django.core.exceptions import PermissionDenied +from django.core.paginator import Paginator +from django.db.models import Q from django.http import (Http404, HttpResponse, HttpResponseRedirect, JsonResponse) -from django.urls import reverse from django.shortcuts import get_object_or_404, redirect from django.template import loader +from django.urls import reverse from django.views import defaults -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.views.generic import TemplateView -from administration.models import VerificationReport + from app.forms import SuntingProfilForm, UploadMateriForm, RatingContributorForm from app.models import ( Category, Comment, - DislikeComment, Materi, - Like, - LikeComment, - ViewStatistics, - DownloadStatistics, ReqMaterial, Rating, - RatingContributor, ) -from app.utils.fileManagementUtil import get_random_filename, remove_image_exifdata from authentication.models import User -import django -import pandas as pd -from io import BytesIO -from django.contrib import messages -from pydrive.auth import GoogleAuth -from pydrive.drive import GoogleDrive -from pydrive.auth import AuthenticationRejected -import random +from .services import DafterKatalogService, DetailMateriService, LikeDislikeService, MateriFieldValidationHelperService, \ + DownloadViewMateriHelperService, UploadMateriService, EditProfileService, RevisiMateriService, \ + DownloadHistoryService, GoogleDriveUploadService + def permission_denied(request, exception, template_name="error_403.html"): return defaults.permission_denied(request, exception, template_name) @@ -59,47 +49,8 @@ class DaftarKatalog(TemplateView): lstMateri = Materi.objects.filter(status="APPROVE").order_by("date_modified") url = "" - getSearch = request.GET.get("search") - if getSearch: - url = url + "&search={0}".format(getSearch) - lstMateri = ( - lstMateri.search(getSearch) - .filter( - Q(author__icontains=getSearch) - | Q(uploader__name__icontains=getSearch) - | Q(descriptions__icontains=getSearch) - | Q(publisher__icontains=getSearch) - ) - .distinct() - ) + lstMateri, url = DafterKatalogService.apply_options(lstMateri, request, url) - getKategori = request.GET.get("kategori") - if getKategori: - url = url + "&kategori={0}".format(getKategori) - kategori = Category.objects.get(pk=getKategori) - lstMateri = lstMateri.filter(categories=kategori.pk) - - getSort = request.GET.get("sort") - if getSort: - url = url + "&sort={0}".format(getSort) - if(getSort == "judul"): - lstMateri = lstMateri.order_by('title') - elif(getSort == "penulis"): - lstMateri = lstMateri.order_by('author') - elif(getSort == "pengunggah"): - lstMateri = lstMateri.order_by('uploader') - elif(getSort == "terbaru"): - lstMateri = lstMateri.order_by('-date_created') - elif(getSort == "terlama"): - lstMateri = lstMateri.order_by('date_created') - elif(getSort == "terpopuler"): - lstMateri = lstMateri.annotate(count=Count('like__id')).order_by('-count') - elif(getSort == "jumlah_unduh"): - lstMateri = lstMateri.annotate(count=Count('unduh__id')).order_by('-count') - - should_random = bool(request.GET.get("random")) - if should_random: - lstMateri = random.sample(list(lstMateri), len(lstMateri)) context["materi_list"] = lstMateri paginator = Paginator(context["materi_list"], 15) @@ -150,19 +101,9 @@ class DetailMateri(TemplateView): if not self.request.session or not self.request.session.session_key: self.request.session.save() materi = get_object_or_404(Materi, pk=kwargs["pk"]) - context["session_id"] = self.request.session.session_key - context["materi_data"] = materi - context["report"] = VerificationReport.objects.filter(materi=materi) - context["has_liked"] = Like.objects.filter(materi=materi, session_id=self.request.session.session_key).exists() - publishedDate = "" - if materi.published_date == None: - publishedDate = "n.d" - else: - publishedDate = materi.published_date.strftime("%Y-%m-%d %H:%M") - citationAPA = materi.author + " . (" + publishedDate + ") . " + materi.title + " . " + materi.publisher - context["citationAPA"] = citationAPA - context["citationIEEE"] = get_citation_ieee(self.request, materi) - context["materi_rating_score"] = 0 + DetailMateriService.init_context_data(context, materi, self.request.session) + published_date = DetailMateriService.set_published_date(materi) + DetailMateriService.init_citation_and_materi_rating(context, materi, published_date, self.request) if self.request.user.is_authenticated: materi_rating = Rating.objects.filter(materi=materi, user=self.request.user).first() @@ -173,28 +114,17 @@ class DetailMateri(TemplateView): return context + def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) query_set_for_comment = Comment.objects.filter(materi=context["materi_data"]) - has_liked = {} - has_disliked = {} - for comment in query_set_for_comment: - has_liked[comment.id] = LikeComment.objects.filter(comment=comment, session_id=self.request.session.session_key).exists() - has_disliked[comment.id] = DislikeComment.objects.filter(comment=comment, session_id=self.request.session.session_key).exists() + has_disliked, has_liked = DetailMateriService.find_comment_like_dislike(query_set_for_comment, self.request.session) context["comment_data"] = query_set_for_comment context["has_liked"] = has_liked context["has_disliked"] = has_disliked return self.render_to_response(context=context) - def get_user_name(self, request): - user_name = "" - if isinstance(request.user, AnonymousUser): - user_name = "Anonim" - elif request.user.is_admin: - user_name = "Admin" - elif request.user.is_contributor: - user_name = request.user.name - return user_name + def post(self, request, *args, **kwargs): commentText = request.POST.get("comment", None) @@ -210,7 +140,7 @@ class DetailMateri(TemplateView): user_obj = request.user if request.user.is_authenticated else None if user_obj: comment = Comment.objects.create( - comment=commentText, username=self.get_user_name(request), materi=materi, user=user_obj + comment=commentText, username=DetailMateriService.get_user_name(request), materi=materi, user=user_obj ) comment.save() return HttpResponseRedirect(request.path) @@ -222,15 +152,7 @@ def toggle_like(request): session_id = request.POST.get("session_id", None) if materi_id is None or session_id is None: return JsonResponse({"success": False, "msg": "Missing parameter"}) - materi = get_object_or_404(Materi, pk=materi_id) - has_liked = Like.objects.filter(materi=materi, session_id=session_id).exists() - if has_liked: - like = get_object_or_404(Like, materi=materi, session_id=session_id) - like.delete() - return JsonResponse({"success": True, "liked": True}) - else: - Like(materi=materi, session_id=session_id).save() - return JsonResponse({"success": True, "liked": False}) + return JsonResponse(LikeDislikeService.apply_like_materi(materi_id, session_id)) else: return JsonResponse({"success": False, "msg": "Unsuported method"}) @@ -244,85 +166,28 @@ def delete_comment(request, pk_materi, pk_comment): return HttpResponseRedirect(url) def toggle_like_comment(request): + comment_id = 0 if request.method == "POST": comment_id = request.POST.get("comment_id", None) session_id = request.POST.get("session_id", None) if comment_id is None or session_id is None: return JsonResponse({"success": False, "msg": "Missing parameter", "comment_id": comment_id}) - comment = get_object_or_404(Comment, pk=comment_id) - has_liked = LikeComment.objects.filter(comment=comment, session_id=session_id).exists() - if has_liked: - like = get_object_or_404(LikeComment, comment=comment, session_id=session_id) - like.delete() - return JsonResponse({"success": True, "liked": True, "comment_id": comment_id}) - else: - LikeComment(comment=comment, session_id=session_id).save() - return JsonResponse({"success": True, "liked": False, "comment_id": comment_id}) + return JsonResponse(LikeDislikeService.apply_comment_like(comment_id, session_id)) else: return JsonResponse({"success": False, "msg": "Unsuported method", "comment_id": comment_id}) + def toggle_dislike_comment(request): + comment_id = 0 if request.method == "POST": comment_id = request.POST.get("comment_id", None) session_id = request.POST.get("session_id", None) if comment_id is None or session_id is None: return JsonResponse({"success": False, "msg": "Missing parameter", "comment_id": comment_id}) - comment = get_object_or_404(Comment, pk=comment_id) - has_disliked = DislikeComment.objects.filter(comment=comment, session_id=session_id).exists() - if has_disliked: - dislike = get_object_or_404(DislikeComment, comment=comment, session_id=session_id) - dislike.delete() - return JsonResponse({"success": True, "disliked": True, "comment_id": comment_id}) - else: - DislikeComment(comment=comment, session_id=session_id).save() - return JsonResponse({"success": True, "disliked": False, "comment_id": comment_id}) + return JsonResponse(LikeDislikeService.apply_comment_dislike(comment_id, session_id)) else: return JsonResponse({"success": False, "msg": "Unsuported method", "comment_id": comment_id}) -def get_citation_ieee(request, materi): - current_date = datetime.datetime.now() - current_day = str(current_date.day) - current_month = current_date.strftime("%b") - current_year = str(current_date.year) - published_date = "" - if materi.published_date == None: - published_date = "n.d" - else: - published_date = materi.published_date.strftime("%Y") - - author_list = materi.author.split(",") - author_list_abbrv = "" - for author_name in author_list: - author_name_split = author_name.split(" ") - author_name_abbrv = "" - for j, name in enumerate(author_name_split): - if j < (len(author_name_split) - 1): - abbrv_name = name[0].upper() - author_name_abbrv = author_name_abbrv + abbrv_name + ". " - else: - author_name_abbrv = author_name_abbrv + name - author_list_abbrv = author_list_abbrv + author_name_abbrv + ", " - - citation_result = ( - author_list_abbrv - + materi.title - + ". " - + materi.publisher - + ", " - + published_date - + ". " - + "Accessed on: " - + current_month - + ". " - + current_day - + ", " - + current_year - + ". [Online]. " - + "Available: " - + request.build_absolute_uri() - ) - return citation_result - def add_rating_materi(request): if request.method == "POST" and request.user.is_authenticated: @@ -330,28 +195,21 @@ def add_rating_materi(request): materi_id = request.POST.get("materi_id", None) rating_score = request.POST.get("rating_score", None) - if materi_id is None or rating_score is None: - return JsonResponse({"success": False, "msg": "Missing param"}, status=422) - try: - rating_score = int(rating_score) - except ValueError: - return JsonResponse({"success": False, "msg": "rating_score must be an integer"}, status=422) - try: - materi_id = int(materi_id) - except ValueError: - return JsonResponse({"success": False, "msg": "materi_id must be an integer"}, status=422) + is_valid_params, materi_id, \ + rating_score, response, \ + status_code = MateriFieldValidationHelperService.\ + validate_materi_rating_params(materi_id,rating_score) - if rating_score not in range(1, 6): - return JsonResponse({"success": False, "msg": "Rating must be an integer from 1 to 5"}, status=422) + if not is_valid_params: + return JsonResponse(response, status=status_code) - materi = Materi.objects.filter(pk=materi_id).first() + is_valid_rating, materi, \ + response, status_code = MateriFieldValidationHelperService.\ + validate_materi_rating(materi_id, request.user) - if materi is None: - return JsonResponse({"success": False, "msg": "Materi does not exist"}, status=422) - - if Rating.objects.filter(materi=materi, user=request.user).first() is not None: - return JsonResponse({"success": False, "msg": "Rating already exist"}, status=409) + if not is_valid_rating: + return JsonResponse(response, status=status_code) Rating(materi=materi, user=request.user, score=rating_score).save() return JsonResponse( @@ -360,6 +218,7 @@ def add_rating_materi(request): return JsonResponse({"success": False, "msg": "Forbidden"}, status=403) + def download_materi(request, pk): materi = get_object_or_404(Materi, pk=pk) path = materi.content.path @@ -368,17 +227,9 @@ def download_materi(request, pk): try: mimetype = mimetypes.guess_type(file_path) with open(file_path, "rb") as fh: - response = HttpResponse(fh.read(), content_type=mimetype[0]) - response["Content-Disposition"] = "attachment; filename=" + os.path.basename(file_path) - if request.user.is_authenticated: - DownloadStatistics(materi=materi, downloader=request.user).save() - else: - downloaded_materi = DownloadStatistics.objects.create(materi=materi) - if "downloaded_materi" not in request.session: - request.session["downloaded_materi"] = [] - request.session["downloaded_materi"].append(downloaded_materi.pk) - request.session.modified = True - return response + return DownloadViewMateriHelperService.build_materi_response(fh, file_path, + materi, mimetype, request, + HttpResponse(fh.read(), content_type=mimetype[0])) except Exception as e: raise Http404("File tidak dapat ditemukan.") else: @@ -394,8 +245,7 @@ def view_materi(request, pk): try: with open(file_path, "rb") as fh: response = HttpResponse(fh.read(), content_type=mimetype[0]) - response["Content-Disposition"] = "inline; filename=" + os.path.basename(file_path) - ViewStatistics(materi=materi).save() + DownloadViewMateriHelperService.build_view_materi_response(file_path, materi, response) return response except Exception as e: raise Http404("File tidak dapat ditemukan.") @@ -424,15 +274,9 @@ class UploadMateriView(TemplateView): materi = form.save(commit=False) materi.uploader = request.user konten = form.cleaned_data["content"] - try: - self.validate_file_extension(konten) - except ValidationError: - messages.error(request, "Materi gagal diunggah, format file tidak sesuai") + if not UploadMateriService.validate_file_extension(konten, request): return HttpResponseRedirect("/unggah/") - materi.save() - kateg = form.cleaned_data["categories"] - for i in kateg: - materi.categories.add(i) + UploadMateriService.upload_materi(form, materi) messages.success(request, "Materi berhasil diunggah, periksa riwayat unggah anda") return HttpResponseRedirect("/unggah/") else: @@ -441,6 +285,8 @@ class UploadMateriView(TemplateView): messages.error(request, "Terjadi kesalahan pada pengisian data") return self.render_to_response(context) + + def get(self, request, *args, **kwargs): if request.user.is_authenticated == False or not request.user.is_contributor: raise PermissionDenied(request) @@ -449,11 +295,7 @@ class UploadMateriView(TemplateView): context["form"] = UploadMateriForm return self.render_to_response(context) - def validate_file_extension(self, value): - ext = os.path.splitext(value.name)[1] # [0] returns path+filename - valid_extensions = [".pdf", ".doc", ".docx", ".jpg", ".png", ".xlsx", ".xls", ".mp4", ".mp3"] - if not ext.lower() in valid_extensions: - raise ValidationError("Unsupported file extension.") + class UploadMateriHTML(TemplateView): @@ -518,20 +360,9 @@ class UploadMateriExcelView(TemplateView): for i in range(row): # Validate Categories - for c in excel["Categories"][i].split(","): - sel_cat = categories.filter(name=c) - if sel_cat.count() == 0: - message = f"Kategori %s tidak ditemukan" % c - break - - if len(excel["Title"][i]) > field_length["title"]: - message = f"Title maksimal %d karakter" % field_length["title"] - - if len(excel["Author"][i]) > field_length["author"]: - message = f"Author maksimal %d karakter" % field_length["author"] + message = UploadMateriService.validate_excel_categories(categories, excel, i, message) - if len(excel["Publisher"][i]) > field_length["publisher"]: - message = f"Publisher maksimal %d karakter" % field_length["publisher"] + message = UploadMateriService.validate_excel_field_length(excel, field_length, i, message) if message != None: break @@ -542,24 +373,15 @@ class UploadMateriExcelView(TemplateView): # Second pass, save data with django.db.transaction.atomic(): - for i in range(row): - materi = Materi( - title=excel["Title"][i], - author=excel["Author"][i], - publisher=excel["Publisher"][i], - descriptions=excel["Description"][i], - uploader=request.user, - ) - materi.save() - - for c in excel["Categories"][i].split(","): - materi.categories.add(categories.get(name=c)) + UploadMateriService.upload_materi_excel(categories, excel, request, row) messages.success(request, "Materi berhasil diunggah") return HttpResponseRedirect("/unggah_excel/") + + class DashboardKontributorView(TemplateView): template_name = "dashboard.html" @@ -651,15 +473,7 @@ class SuntingProfilView(TemplateView): # Removing exifdata from profile picture on upload if request.FILES: - f_name = request.FILES["profile_picture"].name - f_name = get_random_filename(f_name) - f_path = settings.MEDIA_ROOT + "/" + f_name - request.FILES["profile_picture"].name = f_name - - form = SuntingProfilForm(request.POST, request.FILES, instance=current_user) - form.save() - - remove_image_exifdata(f_path) + EditProfileService.update_profile_picture(current_user, request) else: form.save() return HttpResponseRedirect("/profil/") @@ -702,15 +516,7 @@ class SuntingProfilAdminView(TemplateView): # Removing exifdata from profile picture on upload if request.FILES: - f_name = request.FILES["profile_picture"].name - f_name = get_random_filename(f_name) - f_path = settings.MEDIA_ROOT + "/" + f_name - request.FILES["profile_picture"].name = f_name - - form = SuntingProfilForm(request.POST, request.FILES, instance=current_user) - form.save() - - remove_image_exifdata(f_path) + EditProfileService.update_profile_picture(current_user, request) else: form.save() return HttpResponseRedirect("/profil-admin/") @@ -839,14 +645,7 @@ class RevisiMateriView(TemplateView): current_materi = get_object_or_404(Materi, pk=kwargs["pk"]) form = UploadMateriForm(request.POST, request.FILES, instance=current_materi) if form.is_valid(): - materi = form.save(commit=False) - materi.uploader = request.user - materi.status = "REVISION" - materi.save() - kateg = form.cleaned_data["categories"] - for i in kateg: - materi.categories.add(i) - materi.save() + RevisiMateriService.revisi_materi(form, request) return HttpResponseRedirect("/dashboard/") else: context = self.get_context_data(**kwargs) @@ -881,36 +680,18 @@ class DownloadHistoryView(TemplateView): context = self.get_context_data(**kwargs) if request.user.is_authenticated: current_user = self.request.user - riwayat_list = current_user.riwayat_unduh.all().order_by("-timestamp") - context["riwayat_list"] = riwayat_list - context["user_name"] = current_user.name + DownloadHistoryService.init_data_authenticated_user(context, current_user) else: - has_downloaded_materi = "downloaded_materi" in request.session - downloaded_materi = request.session["downloaded_materi"] if has_downloaded_materi else [] - riwayat_list = DownloadStatistics.objects.filter(pk__in=downloaded_materi).order_by("-timestamp") - context["riwayat_list"] = riwayat_list - context["user_name"] = "Guest" + DownloadHistoryService.init_data_guest_user(context, request) return self.render_to_response(context) -def upload_to_gdrive(file_path, title): - gauth = GoogleAuth() - gauth.LocalWebserverAuth() - - drive = GoogleDrive(gauth) - file1 = drive.CreateFile() - file1.SetContentFile(file_path) - file1["title"] = title - print("title: %s, mimeType: %s" % (file1["title"], file1["mimeType"])) - file1.Upload() - - def save_to_gdrive(request, pk): materi = get_object_or_404(Materi, pk=pk) path = materi.content.path file_path = os.path.join(settings.MEDIA_ROOT, path) if os.path.exists(file_path): - upload_to_gdrive(file_path, materi.title) + GoogleDriveUploadService.upload_to_gdrive(file_path, materi.title) else: raise Http404("File tidak dapat ditemukan.") diff --git a/authentication/services.py b/authentication/services.py new file mode 100644 index 0000000000000000000000000000000000000000..9b3c3d30dc97ea00637f3a41726f950b745101e3 --- /dev/null +++ b/authentication/services.py @@ -0,0 +1,22 @@ +from django.contrib.auth import login +from django.http import QueryDict + + +class LoginService: + @staticmethod + def get_user_redirect_url(request): + if request.user.is_admin: + redirect_to = "/sukses-admin/" + elif request.user.is_contributor: + redirect_to = "/sukses-kontributor/" + else: + redirect_to = "/" + return redirect_to + @staticmethod + def get_redirect_login_sucess(request, user): + login(request, user) + querystring = QueryDict(request.META['QUERY_STRING']) + redirect_to = LoginService.get_user_redirect_url(request) + if 'next' in querystring: + redirect_to = querystring['next'] + return redirect_to diff --git a/authentication/views.py b/authentication/views.py index 03408cbaf9d5204e6bf89900b327cc6f1e564691..c33e983e6562b3e40b242cdd54a447a0db57d7ff 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -3,20 +3,49 @@ from django.http import HttpResponseRedirect, QueryDict from django.views.generic import TemplateView from django.conf import settings from django.contrib import messages +from .services import LoginService import urllib import json + + class Login(TemplateView): def dispatch(self, request, *args, **kwargs): if request.user.is_authenticated: - if request.user.is_admin: - redirect_to = "/sukses-admin/" - elif request.user.is_contributor: - redirect_to = "/sukses-kontributor/" + redirect_to = LoginService.get_user_redirect_url(request) return HttpResponseRedirect(redirect_to) return super(Login, self).dispatch(request, *args, **kwargs) + + def do_login(self, context, email, password, request, result): + if result['success']: + return self.handle_recaptcha_success(context, email, password, request) + else: + messages.error(request, 'Invalid reCAPTCHA. Please try again.') + return self.render_to_response(context=context) + + def handle_recaptcha_success(self, context, email, password, request): + user = authenticate(email=email, password=password) + if user is not None: + redirect_to = LoginService.get_redirect_login_sucess(request, user) + return HttpResponseRedirect(redirect_to) + else: + context["error_message"] = "Email atau Password anda salah." + return self.render_to_response(context=context) + + def get_recaptcha_result(self, recaptcha_response): + url = 'https://www.google.com/recaptcha/api/siteverify' + values = { + 'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY, + 'response': recaptcha_response + } + data = urllib.parse.urlencode(values).encode() + req = urllib.request.Request(url, data=data) + response = urllib.request.urlopen(req) + result = json.loads(response.read().decode()) + return result + def get_template_names(self): if self.request.path == "/login_admin/": template_name = "login_admin.html" @@ -44,32 +73,7 @@ class Login(TemplateView): return self.render_to_response(context=context) else: recaptcha_response = request.POST.get('g-recaptcha-response') - url = 'https://www.google.com/recaptcha/api/siteverify' - values = { - 'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY, - 'response': recaptcha_response - } - data = urllib.parse.urlencode(values).encode() - req = urllib.request.Request(url, data=data) - response = urllib.request.urlopen(req) - result = json.loads(response.read().decode()) - - if result['success']: - user = authenticate(email=email, password=password) - if user is not None: - login(request, user) - redirect_to = "/" - querystring = QueryDict(request.META['QUERY_STRING']) - if request.user.is_admin: - redirect_to = "/sukses-admin/" - elif request.user.is_contributor: - redirect_to = "/sukses-kontributor/" - if 'next' in querystring: - redirect_to = querystring['next'] - return HttpResponseRedirect(redirect_to) - else: - context["error_message"] = "Email atau Password anda salah." - return self.render_to_response(context=context) - else: - messages.error(request, 'Invalid reCAPTCHA. Please try again.') - return self.render_to_response(context=context) + result = self.get_recaptcha_result(recaptcha_response) + + return self.do_login(context, email, password, request, result) + diff --git a/forum/services.py b/forum/services.py new file mode 100644 index 0000000000000000000000000000000000000000..56c5e72eff676dde768e7c905ca9ce64f4944b4c --- /dev/null +++ b/forum/services.py @@ -0,0 +1,33 @@ + +class ForumService: + + @staticmethod + def create_forum_discussion(form, user): + form.instance.user = user + form.save() + materi = form.cleaned_data.get('materi') + for data in materi: + form.instance.materi.add(data) + + + @staticmethod + def build_forum_form_detail_data(discussion_object, form, user): + form.instance.user = user + form.instance.discussion = discussion_object + form.save() + materi = form.cleaned_data.get('materi') + for data in materi: + form.instance.materi.add(data) + + + @staticmethod + def delete_comment(discussion_comment, request): + discussion = discussion_comment.discussion + if discussion_comment.user == request.user: + discussion_comment.delete() + return discussion + + @staticmethod + def delete_discussion(discussion, request): + if discussion.user == request.user: + discussion.delete() \ No newline at end of file diff --git a/forum/views.py b/forum/views.py index 233eb833e0d2f53e0a6b5ca67c451e7a226f7c00..45a3b0e6efdb0aa7e32fbe8351d2af12bbd36149 100644 --- a/forum/views.py +++ b/forum/views.py @@ -8,6 +8,7 @@ from django.views.generic.list import MultipleObjectMixin from forum.forms import DiscussionForm, DiscussionCommentForm from forum.models import Discussion, DiscussionComment +from forum.services import ForumService URL_FORUM_HOME_PAGE = '/forum' URL_LOGIN_PAGE = '/login' @@ -26,11 +27,8 @@ class ForumCreateDiscussion(LoginRequiredMixin, CreateView): login_url = URL_LOGIN_PAGE def form_valid(self, form): - form.instance.user = self.request.user - form.save() - materi = form.cleaned_data.get('materi') - for data in materi: - form.instance.materi.add(data) + user = self.request.user + ForumService.create_forum_discussion(form, user) return super().form_valid(form) @@ -42,8 +40,7 @@ class ForumDeleteDiscussion(LoginRequiredMixin, DeleteView): def delete(self, request, *args, **kwargs): discussion = self.get_object() - if discussion.user == request.user: - discussion.delete() + ForumService.delete_discussion(discussion, request) return redirect(URL_FORUM_HOME_PAGE) @@ -64,19 +61,16 @@ class ForumDiscussionDetail(FormMixin, DetailView, MultipleObjectMixin): def post(self, request, *args, **kwargs): form = self.get_form() - form.instance.user = self.request.user - form.instance.discussion = self.get_object() - form.save() - - materi = form.cleaned_data.get('materi') - for data in materi: - form.instance.materi.add(data) + user = self.request.user + discussion_object = self.get_object() + ForumService.build_forum_form_detail_data(discussion_object, form, user) if form.is_valid(): return self.form_valid(form) else: return self.form_invalid(form) + def form_valid(self, form): form.save() return super().form_valid(form) @@ -89,7 +83,5 @@ class ForumDiscussionCommentDelete(LoginRequiredMixin, DeleteView): def delete(self, request, *args, **kwargs): discussion_comment = self.get_object() - discussion = discussion_comment.discussion - if discussion_comment.user == request.user: - discussion_comment.delete() + discussion = ForumService.delete_comment(discussion_comment, request) return HttpResponseRedirect(reverse('forum_discussion_detail', kwargs={'pk': discussion.pk})) diff --git a/news/services.py b/news/services.py new file mode 100644 index 0000000000000000000000000000000000000000..813b4a5bdac282152aaa74674239135f1754879e --- /dev/null +++ b/news/services.py @@ -0,0 +1,31 @@ +from news.forms import NewsForm +from news.models import News + + +class NewsService: + + @staticmethod + def create_news(content, cover, title): + news_data = News.objects.create( + title=title, + content=content, + cover=cover + ) + news_data.save() + + + @staticmethod + def update_news_form(news_data, request): + form = NewsForm(request.POST or None, + request.FILES or None, + instance=news_data) + is_form_valid = form.is_valid() + if form.is_valid(): + form.save() + return is_form_valid + + + @staticmethod + def delete_news(news_data): + news_data.cover.delete() + news_data.delete() diff --git a/news/views.py b/news/views.py index 296f774b906429eb9cd3b2902d50d6143ce3537d..f16a1dec553cd9608d3ff7b2cd9a21f39218a7d5 100644 --- a/news/views.py +++ b/news/views.py @@ -6,6 +6,7 @@ from django.views.decorators.csrf import csrf_exempt from .forms import NewsForm from .models import News +from .services import NewsService html_news_form = "news_form.html" html_news_list = "news_list.html" @@ -20,15 +21,14 @@ def post_news_form(request): form = NewsForm(request.POST or None, request.FILES or None) if form.is_valid(): - news_data = News.objects.create( - title=request.POST["title"], - content=request.POST["content"], - cover=request.FILES.get("cover", False) - ) - news_data.save() + title = request.POST["title"] + content = request.POST["content"] + cover = request.FILES.get("cover", False) + NewsService.create_news(content, cover, title) return redirect(news_list_url) return redirect("/") + @csrf_exempt @login_required(login_url=login_url) def delete_news_by_id(request, id_news): @@ -39,10 +39,10 @@ def delete_news_by_id(request, id_news): news_data = News.objects.get(pk=id_news) except News.DoesNotExist: return redirect(news_list_url) - news_data.cover.delete() - news_data.delete() + NewsService.delete_news(news_data) return redirect(news_list_url) + @login_required(login_url=login_url) def show_news_form(request): if request.user.is_admin is False: @@ -64,13 +64,8 @@ def update_news_form(request, id_news): return redirect(news_list_url) form = NewsForm(instance=news_data) - if request.method == "POST": - form = NewsForm(request.POST or None, - request.FILES or None, - instance=news_data) - if form.is_valid(): - form.save() - return redirect(news_list_url) + if request.method == "POST" and NewsService.update_news_form(news_data, request): + return redirect(news_list_url) response = { 'form': form, @@ -79,6 +74,7 @@ def update_news_form(request, id_news): } return render(request, html_news_form, response) + @login_required(login_url=login_url) def show_news_list(request): if request.user.is_admin is False: @@ -87,4 +83,4 @@ def show_news_list(request): response = { "news_list" : News.objects.all() } - return render(request, html_news_list, response) \ No newline at end of file + return render(request, html_news_list, response) diff --git a/register/services.py b/register/services.py new file mode 100644 index 0000000000000000000000000000000000000000..7d2b7971869329a0b4edd6f9336334b16fd5b6a7 --- /dev/null +++ b/register/services.py @@ -0,0 +1,20 @@ +from django.contrib.auth.hashers import make_password + +class RegistrationService: + + @staticmethod + def create_new_contributor(data, form): + new_user = form.save(commit=False) + new_user.password = make_password(data["password"]) + new_user.is_contributor = True + new_user.save() + return new_user + + + @staticmethod + def create_new_admin(data, form): + new_user = form.save(commit=False) + new_user.password = make_password(data["password"]) + new_user.is_admin = True + new_user.is_active = False + new_user.save() \ No newline at end of file diff --git a/register/views.py b/register/views.py index 4d9d339882ddaea3f1583b7cd4a7342c214476ee..d54425ead396c475f3a35d6d8b2b0580fb5a8b7d 100644 --- a/register/views.py +++ b/register/views.py @@ -1,11 +1,10 @@ from django.contrib.auth import login -from django.contrib.auth.hashers import make_password from django.http import HttpResponseRedirect from django.views.generic import TemplateView from register.forms import UserForm - # Create your views here. +from register.services import RegistrationService class index(TemplateView): @@ -19,10 +18,7 @@ class index(TemplateView): data = request.POST.copy() form = UserForm(request.POST) if form.is_valid(): - new_user = form.save(commit=False) - new_user.password = make_password(data["password"]) - new_user.is_contributor = True - new_user.save() + new_user = RegistrationService.create_new_contributor(data, form) login(request, new_user) return HttpResponseRedirect("/sukses-kontributor/") else: @@ -30,6 +26,7 @@ class index(TemplateView): context["form"] = form return self.render_to_response(context) + def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) context["form"] = UserForm @@ -49,16 +46,12 @@ class RegistrasiAdmin(TemplateView): context = self.get_context_data(**kwargs) context["form"] = form if form.is_valid(): - new_user = form.save(commit=False) - new_user.password = make_password(data["password"]) - new_user.is_admin = True - new_user.is_active = False - new_user.save() + RegistrationService.create_new_admin(data, form) context["message"] = "Please wait for our internal team to accept your admin account" return self.render_to_response(context) - return self.render_to_response(context) + def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) context["form"] = UserForm diff --git a/traffic_statistics/service.py b/traffic_statistics/service.py new file mode 100644 index 0000000000000000000000000000000000000000..96b3464c0beeb843408819edbdb8833f7abc1a2a --- /dev/null +++ b/traffic_statistics/service.py @@ -0,0 +1,27 @@ +from django.utils.timezone import localdate + +from app.models import Like, Comment, ViewStatistics, DownloadStatistics +from traffic_statistics.constants import FIELD_LIKES, FIELD_COMMENTS, FIELD_VIEWS, FIELD_DOWNLOADS, DATE_FORMAT +from traffic_statistics.utils.statistics import generate_initial_traffic_count_dict, get_daily_traffic_count + + +def build_statistic_data(end_date, start_date): + today = localdate() + start_date = min(start_date, today) + end_date = min(end_date, today) + statistics_dict = generate_initial_traffic_count_dict(start_date, end_date) + daily_traffic_counts = [ + get_daily_traffic_count(Like, start_date, end_date), + get_daily_traffic_count(Comment, start_date, end_date), + get_daily_traffic_count(ViewStatistics, start_date, end_date), + get_daily_traffic_count(DownloadStatistics, start_date, end_date), + ] + labels = [FIELD_LIKES, FIELD_COMMENTS, FIELD_VIEWS, FIELD_DOWNLOADS] + for (daily_all_traffic_count, label) in zip(daily_traffic_counts, labels): + + for daily_traffic_count in daily_all_traffic_count: + date, count = daily_traffic_count + + date_str = date.strftime(DATE_FORMAT) + statistics_dict[date_str][label] = count + return statistics_dict \ No newline at end of file diff --git a/traffic_statistics/views.py b/traffic_statistics/views.py index 6dfdea2791b9a3fb96027b43d47be0684eea154c..c71fa370d4c0aa3c4f81cc7161bf47f702de5a5a 100644 --- a/traffic_statistics/views.py +++ b/traffic_statistics/views.py @@ -1,26 +1,8 @@ from django.http import JsonResponse from django.views.generic import ListView -from django.utils.timezone import localdate -from app.models import ( - Comment, - DownloadStatistics, - Like, - ViewStatistics, -) -from traffic_statistics.constants import ( - DATE_FORMAT, - FIELD_COMMENTS, - FIELD_DOWNLOADS, - FIELD_LIKES, - FIELD_VIEWS, -) from traffic_statistics.utils.requests import extract_date_range_params -from traffic_statistics.utils.statistics import ( - generate_initial_traffic_count_dict, - get_daily_traffic_count, -) - +from .service import build_statistic_data class StatisticsAPIView(ListView): @@ -30,26 +12,6 @@ class StatisticsAPIView(ListView): except Exception as e: return JsonResponse(data={"error": str(e)}, status=400) - today = localdate() - start_date = min(start_date, today) - end_date = min(end_date, today) - - statistics_dict = generate_initial_traffic_count_dict(start_date, end_date) - - daily_traffic_counts = [ - get_daily_traffic_count(Like, start_date, end_date), - get_daily_traffic_count(Comment, start_date, end_date), - get_daily_traffic_count(ViewStatistics, start_date, end_date), - get_daily_traffic_count(DownloadStatistics, start_date, end_date), - ] - labels = [FIELD_LIKES, FIELD_COMMENTS, FIELD_VIEWS, FIELD_DOWNLOADS] - - for (daily_all_traffic_count, label) in zip(daily_traffic_counts, labels): - - for daily_traffic_count in daily_all_traffic_count: - date, count = daily_traffic_count - - date_str = date.strftime(DATE_FORMAT) - statistics_dict[date_str][label] = count + statistics_dict = build_statistic_data(end_date, start_date) return JsonResponse(data=statistics_dict, status=200)