Fakultas Ilmu Komputer UI

Commit fd8b2ad4 authored by Edward's avatar Edward
Browse files

Merge branch 'master' of...

Merge branch 'master' of https://gitlab.cs.ui.ac.id/pmpl/class-project/marjinal-digipus into 1706979215-129
parents cbf99a0b 8e7e67a9
Pipeline #60187 passed with stages
in 21 minutes and 46 seconds
from django import forms
from app.models import Materi, Category, RatingContributor
from app.models import Materi, Category, RatingContributor, GuestBook
from authentication.models import User
import datetime
def year_choices():
return[(r,r) for r in range(2000, datetime.date.today().year+1)]
return[(r, r) for r in range(2000, datetime.date.today().year+1)]
class UploadMateriForm(forms.ModelForm):
categories = forms.ModelMultipleChoiceField(queryset=Category.objects.all(),widget=forms.CheckboxSelectMultiple(attrs={'style' : 'column-count:2'}),required=True)
release_year = forms.TypedChoiceField(coerce=int, choices=year_choices, initial=datetime.date.today().year)
yt_video_id = forms.CharField(label="Youtube Video Id", \
help_text="This is not required.<br>\
categories = forms.ModelMultipleChoiceField(queryset=Category.objects.all(
), widget=forms.CheckboxSelectMultiple(attrs={'style': 'column-count:2'}), required=True)
release_year = forms.TypedChoiceField(
coerce=int, choices=year_choices, initial=datetime.date.today().year)
yt_video_id = forms.CharField(label="Youtube Video Id",
help_text="This is not required.<br>\
Please insert only Youtube link videos! Take a note for the video id!<br>\
Example : https://www.youtube.com/watch?v=DkJ-50GLi2I <br> has video id DkJ-50GLi2I", required=False)
......@@ -29,14 +33,13 @@ class UploadMateriForm(forms.ModelForm):
field.widget.attrs["class"] = "form-control"
field.widget.attrs["placeholder"] = field.initial
field.initial = ""
self.fields['categories'].widget.attrs.update({'class' : "native"})
self.fields['categories'].widget.attrs.update({'class': "native"})
class SuntingProfilForm(forms.ModelForm):
class Meta:
model = User
fields = ["email","name","instansi", "nik", "alamat", "nomor_telpon",
fields = ["email", "name", "instansi", "nik", "alamat", "nomor_telpon",
"profile_picture", "linkedin",
"facebook", "twitter", "instagram", "biography",
"is_subscribing_to_material_comments"]
......@@ -50,7 +53,6 @@ class SuntingProfilForm(forms.ModelForm):
if any(self.errors):
key = list(self.errors)[0]
self.fields[key].widget.attrs["autofocus"] = ""
self.fields["email"].widget.attrs["readonly"] = True
......@@ -61,14 +63,43 @@ class RatingContributorForm(forms.ModelForm):
fields = ['score', 'user', 'contributor']
SCORE_CHOICE = (
('', 'Select score'),
('1', '1'), # First one is the value of select option and second is the displayed value in option
# First one is the value of select option and second is the displayed value in option
('1', '1'),
('2', '2'),
('3', '3'),
('4', '4'),
('5', '5'),
)
widgets = {
'score': forms.Select(choices=SCORE_CHOICE, attrs={'class': 'form-control', 'id':'form-rating'},),
'score': forms.Select(choices=SCORE_CHOICE, attrs={'class': 'form-control', 'id': 'form-rating'},),
'user': forms.HiddenInput(),
'contributor': forms.HiddenInput()
}
class GuestBookForm(forms.models.ModelForm):
class Meta:
model = GuestBook
fields = ['name', 'job', 'gender']
gender_choices = (
('Male', 'Male'),
('Female', 'Female')
)
widgets = {
'name': forms.fields.TextInput(attrs={
'placeholder': 'Input your name',
'class': 'form-control input-lg'
}),
'job': forms.fields.TextInput(attrs={
'placeholder': 'Input your job',
'class': 'form-control input-lg'
}),
'gender': forms.fields.Select(choices=gender_choices, attrs={
'class': 'form-control input-lg'
})
}
error_messages = {
'name': {'required': 'Name is required'},
'job': {'required': 'Job is required'},
'gender': {'required': 'Gender is required'}
}
# Generated by Django 3.1 on 2020-10-31 18:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0028_adminnotification'),
]
operations = [
migrations.CreateModel(
name='GuestBook',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField(default='')),
('job', models.TextField(default='')),
('gender', models.CharField(default='Male', max_length=6)),
],
),
]
# Generated by Django 3.1 on 2020-10-31 23:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('app', '0029_guestbook'),
('app', '0028_notifikasikontributor'),
]
operations = [
]
# Generated by Django 3.1 on 2020-11-01 10:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('app', '0030_merge_20201101_0621'),
('app', '0029_merge_20201101_0217'),
]
operations = [
]
......@@ -26,9 +26,11 @@ 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="")
......@@ -48,7 +50,7 @@ class MateriManager(models.Manager):
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:
......@@ -75,18 +77,20 @@ 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()
......@@ -96,8 +100,9 @@ class Materi(SoftDeleteModel):
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])
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)
......@@ -117,7 +122,8 @@ class Materi(SoftDeleteModel):
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
search_index = {field: weight for (field, weight) in Materi.SEARCH_INDEX}
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"])
......@@ -127,9 +133,11 @@ class Materi(SoftDeleteModel):
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)
self._search_vector = search.SearchVector(
field, weight=weight)
else:
self._search_vector += search.SearchVector(field, weight=weight)
self._search_vector += search.SearchVector(
field, weight=weight)
self.save(update_fields=["_search_vector"])
......@@ -154,7 +162,7 @@ class Materi(SoftDeleteModel):
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()
......@@ -181,7 +189,7 @@ class Materi(SoftDeleteModel):
@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
time_score = self.seconds_since_earliest_materi / 604800 # 1 week
return round(view_score + time_score, 7)
@property
......@@ -197,7 +205,8 @@ class Comment(models.Model):
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)
user = models.ForeignKey(
User, on_delete=models.SET_NULL, blank=True, null=True)
timestamp = models.DateTimeField(default=timezone.now)
def __str__(self):
......@@ -207,18 +216,20 @@ class Comment(models.Model):
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)
user = models.ForeignKey(
User, on_delete=models.SET_NULL, blank=True, null=True)
timestamp = models.DateTimeField(default=timezone.now)
def __str__(self):
......@@ -247,6 +258,7 @@ 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)
......@@ -255,13 +267,16 @@ class SubmitVisitor(models.Model):
class ViewStatistics(models.Model):
materi = models.ForeignKey(Materi, models.SET_NULL, null=True, related_name="baca")
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")
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)
......@@ -302,27 +317,39 @@ class Rating(models.Model):
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')
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)
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="")
laporan = models.TextField(
validators=[MinValueValidator(30), MaxValueValidator(120)], default="")
timestamp = models.DateTimeField(default=timezone.now)
is_rejected = models.BooleanField(default=False)
class GuestBook(models.Model):
name = models.TextField(default='')
job = models.TextField(default='')
gender = models.CharField(max_length=6, default="Male")
class ReadLater(models.Model):
materi = models.ForeignKey(Materi, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
......@@ -331,13 +358,16 @@ class ReadLater(models.Model):
class Meta:
unique_together = ["materi", "user"]
class NotifikasiKontributor(models.Model):
materi = models.ForeignKey(Materi, on_delete=models.CASCADE, max_length=120)
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)
.form-style-6{
font: 95% Arial, Helvetica, sans-serif;
max-width: 500px;
margin: 25px auto;
padding: 16px;
background: #F7F7F7;
align-content: center;
justify-content: center;
}
.form-style-6 h1{
background: #43D1AF;
padding: 20px 0;
font-size: 140%;
font-weight: 300;
text-align: center;
color: #fff;
margin: -16px -16px 16px -16px;
}
.form-style-6 input[type="text"],
.form-style-6 select
{
-webkit-transition: all 0.30s ease-in-out;
-moz-transition: all 0.30s ease-in-out;
-ms-transition: all 0.30s ease-in-out;
-o-transition: all 0.30s ease-in-out;
outline: none;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
width: 100%;
background: #fff;
margin-bottom: 4%;
border: 1px solid #ccc;
color: #555;
font: 95% Arial, Helvetica, sans-serif;
}
.form-style-6 input[type="text"]:focus,
.form-style-6 textarea:focus,
.form-style-6 select:focus
{
box-shadow: 0 0 5px #43D1AF;
padding: 3%;
border: 1px solid #43D1AF;
}
.form-style-6 button[type="submit"]{
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
width: 100%;
padding: 3%;
background: #1da1f2;
border-bottom: 2px solid #1da1f2;
border-top-style: none;
border-right-style: none;
border-left-style: none;
color: #fff;
}
.form-style-6 input[type="submit"]:hover,
.form-style-6 input[type="button"]:hover{
background: #2EBC99;
}
\ No newline at end of file
......@@ -23,6 +23,9 @@
<!-- Custom styles for this page -->
<link href="{% static 'vendor/datatables/dataTables.bootstrap4.min.css' %}" rel="stylesheet">
<!-- Bootstrap CSS -->
{% block stylesheets %}{% endblock %}
{% block scripts %}{% endblock %}
</head>
......@@ -69,6 +72,10 @@
<em class="fas fa-angle-up"></em>
</a>
<!-- Bootstrap scripts -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>
\ No newline at end of file
......@@ -71,6 +71,10 @@
<em class="fas fa-angle-up"></em>
</a>
<!-- Bootstrap scripts -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>
\ No newline at end of file
......@@ -11,19 +11,26 @@
<ul class="navbar-nav ml-auto">
{% if request.user.is_contributor %}
<li class="nav-item dropdown">
<a id="notifBtn" class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Notifikasi</a>
<div class="dropdown-menu show" aria-labelledby="notifBtn">
<li class="nav-item">
<div class="dropdown">
{% if kontributor_notif %}
{% for notif in kontributor_notif %}
<a class="dropdown-item" style="white-space: break-spaces;" href="{% url 'dashboard' %}">Materi "{{notif.materi.title}}" <br>{{ notif }}</a>
<div class="dropdown-divider"></div>
{% endfor %}
<a id="notifBtn" class="nav-link dropdown-toggle" data-toggle="dropdown" href="#"role="button" aria-haspopup="true" aria-expanded="false" style="color: #4e73df;">
Notifikasi</a>
<div class="dropdown-menu" aria-labelledby="notifBtn">
{% for notif in kontributor_notif %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" style="white-space: break-spaces;" href="{% url 'dashboard' %}">Materi "{{notif.materi.title}}" <br>{{ notif }}</a>
<div class="dropdown-divider"></div>
{% endfor %}
{% else %}
<a id="notifBtn" class="nav-link dropdown-toggle" data-toggle="dropdown" href="#"role="button" aria-haspopup="true" aria-expanded="false">
Notifikasi</a>
<div class="dropdown-menu" aria-labelledby="notifBtn">
<a class="dropdown-item disabled" href="#" style="white-space: break-spaces;">Anda tidak memiliki notifikasi baru</a>
{% endif %}
</div>
</a>
</div>
</a>
</div>
</li>
<li class="nav-item">
......
{% extends 'base.html' %}
{% load static %}
{% block title %}Digipus Home{% endblock %}
{% block header %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Digipus Home</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, inital-scale=1">
<link rel="stylesheet" type="text/css" href="{% static 'app/css/guest_book.css' %}">
{% endblock header %}
</head>
<!-- Page Content -->
{% block content %}
<div class="form-group form-style-6" id="myForm">
<form method="POST">
<h3 style="text-align: center;">Buku Tamu Non Anggota</h3>
{% csrf_token %}
{{form}}
<button type="submit" class="btn btn-success">Submit</button>
</form>
</div>
{% endblock %}
</html>
\ No newline at end of file
This diff is collapsed.
......@@ -5,8 +5,7 @@ from app import views
from app.views import (DashboardKontributorView, ProfilView, StatisticsView,
SuksesLoginAdminView, SuksesLoginKontributorView, DownloadHistoryView,
SuntingProfilView, UploadMateriHTML, UploadMateriView, UploadMateriExcelView, PostsView,
ReqMateriView, KatalogPerKontributorView, MateriFavorite, PasswordChangeViews, password_success,
SubmitVisitorView, ReadLaterView, MostContributor)
ReqMateriView, KatalogPerKontributorView, MateriFavorite, PasswordChangeViews, password_success, SubmitVisitorView, GuestBookView, ReadLaterView, MostContributor)
urlpatterns = [
......@@ -18,13 +17,16 @@ urlpatterns = [
path("review/delete/<int:pk_materi>/<int:pk_review>",
views.delete_review, name="delete-review"),
path("comment/like/", views.toggle_like_comment, name="comment-like-toggle"),
path("comment/dislike/", views.toggle_dislike_comment, name="comment-dislike-toggle"),
path("comment/dislike/", views.toggle_dislike_comment,
name="comment-dislike-toggle"),
path("materi/<int:pk>/delete", views.delete_materi, name="detele-materi"),
path("materi/<int:pk>/unduh", views.download_materi, name="download-materi"),
path("materi/<int:pk>/view", views.view_materi, name="view-materi"),
path("dashboard/", DashboardKontributorView.as_view(), name="dashboard"),
path("download-history/", DownloadHistoryView.as_view(), name="download-history"),
path("revisi/materi/<int:pk>/", views.RevisiMateriView.as_view(), name="revisi"),
path("download-history/", DownloadHistoryView.as_view(),
name="download-history"),
path("revisi/materi/<int:pk>/",
views.RevisiMateriView.as_view(), name="revisi"),
path("unggah/", UploadMateriView.as_view(), name="unggah"),
path("unggah_excel/", UploadMateriExcelView.as_view(), name="unggah_excel"),
path("profil/", ProfilView.as_view(), name="profil"),
......@@ -38,26 +40,33 @@ urlpatterns = [
path("profil/<str:email>/", KatalogPerKontributorView.as_view(),
name="katalog-per-kontributor"),
path("materi/rate/", views.add_rating_materi, name="rate-materi"),
path("materi/<int:pk>/save-to-gdrive/", views.save_to_gdrive, name="save-to-gdrive"),
path("materi/<int:pk>/save-to-gdrive/",
views.save_to_gdrive, name="save-to-gdrive"),
path("favorite/", MateriFavorite.as_view(), name="favorite"),
path("change-password/", PasswordChangeViews.as_view(template_name='change-password.html')),
path("change-password/",
PasswordChangeViews.as_view(template_name='change-password.html')),
path("password_success/", views.password_success, name="password_success"),
path("given-rating/", views.see_given_rating, name="see_given_rating"),
path("submit-visitor/", SubmitVisitorView.as_view(), name="submit-visitor"),
path("baca-nanti/", ReadLaterView.as_view(), name="read-later"),
path("baca-nanti-toggle/", views.toggle_readlater, name="toggle-read-later"),
path("stats/", StatisticsView.as_view(), name="stats"),
path("reset_password/",
auth_views.PasswordResetView.as_view(template_name="password_reset.html"),
name="reset_password"),
path("reset_password_sent/",
auth_views.PasswordResetDoneView.as_view(template_name="password_reset_sent.html"),
name="password_reset_done"),
path("reset/<uidb64>/<token>/",
auth_views.PasswordResetConfirmView.as_view(template_name="password_reset_form.html"),
name="password_reset_confirm"),
path("reset_password_complete/",
auth_views.PasswordResetCompleteView.as_view(template_name="password_reset_done.html"),
name="password_reset_complete"),
path("most-contributor/", MostContributor.as_view(), name="most-contributor")
path("reset_password/",
auth_views.PasswordResetView.as_view(
template_name="password_reset.html"),
name="reset_password"),