Fakultas Ilmu Komputer UI

Commit ec6413bc 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-63
parents 3e654d3f 512cbda6
Pipeline #59570 failed with stages
in 3 minutes and 6 seconds
# Created by https://www.gitignore.io/api/venv,django,python,visualstudiocode
# Edit at https://www.gitignore.io/?templates=venv,django,python,visualstudiocode
### Django ###
*.log
*.pot
*.pyc
__pycache__/
local_settings.py
db.sqlite3
db.sqlite3-journal
media
*/__pycache__/*
/static/
media/
.coverage
virtualenv
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
# in your Git repository. Update and uncomment the following line accordingly.
# <django-project-name>/staticfiles/
### Django.Python Stack ###
# Byte-compiled / optimized / DLL files
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
.pip_cache
pylint
coverage.svg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
### Python ###
# Byte-compiled / optimized / DLL files
# C extensions
# Distribution / packaging
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
# Installer logs
# Unit test / coverage reports
# Translations
# Scrapy stuff:
# Sphinx documentation
# PyBuilder
# pyenv
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
# celery beat schedule file
# SageMath parsed files
# Spyder project settings
# Rope project settings
# Mr Developer
# mkdocs documentation
# mypy
# Pyre type checker
### venv ###
# Virtualenv
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
pyvenv.cfg
# .env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
pip-selfcheck.json
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.vscode/
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
# End of https://www.gitignore.io/api/venv,django,python,visualstudiocode
#misc
__pycache__/
db.sqlite3
.coverage
htmlcov/
flowchart/
client_secrets.json
# Pycharm IDE
.idea
# MacOS
.DS_Store
......@@ -179,6 +179,7 @@ dmypy.json
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
pyvenv.cfg
.env
.env.db
.venv
env/
venv/
......
......@@ -4,10 +4,8 @@ stages:
- deploy
pylint:
image: python:3.7
image: farhanazmi/digipus-base
stage: test
before_script:
- pip install -r requirements.txt
script:
- mkdir ./pylint
- git ls-files | grep -v 'migrations' | grep -v 'settings.py' | grep -v 'manage.py' | grep -E '.py$' | xargs pylint -E --load-plugins=pylint_django --output-format=text --score=yes | tee ./pylint/pylint.log || pylint-exit $?
......@@ -25,12 +23,11 @@ UnitTest:
POSTGRES_DB: gitlab_test
POSTGRES_USER: gitlab_test
POSTGRES_PASSWORD: SebuahPassword
image: python:3.7
image: farhanazmi/digipus-base
stage: test
coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+)%/'
before_script:
- export DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres:5432/$POSTGRES_DB
- pip install -r requirements.txt
- python manage.py makemigrations
- python manage.py migrate
- python manage.py collectstatic --no-input
......
# BUILDER
FROM python:3.7-slim as builder
RUN apt-get update \
&& apt-get -y upgrade \
&& apt-get -y install --no-install-recommends gcc python3-dev libpq-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
COPY . .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt
# FINAL
FROM python:3.7-slim
RUN apt-get update \
&& apt-get -y upgrade \
&& apt-get -y install --no-install-recommends gcc python3-dev libpq-dev
RUN mkdir -p /home/digipus
ENV APP_HOME=/home/digipus/
RUN mkdir $APP_HOME/staticfiles
RUN mkdir $APP_HOME/mediafiles
WORKDIR $APP_HOME
COPY . .
COPY --from=builder /usr/src/app/wheels /wheels
COPY --from=builder /usr/src/app/requirements.txt .
RUN pip install --no-cache /wheels/*
EXPOSE 8000
VOLUME ["/home/digipus/staticfiles", "/home/digipus/mediafiles"]
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "digipus.wsgi:application", "--reload"]
FROM python:3.7-slim
RUN apt-get update \
&& apt-get -y upgrade \
&& apt-get -y install --no-install-recommends gcc python3-dev libpq-dev git-all \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
COPY requirements.txt .
RUN pip install -r requirements.txt
CMD [ "python" ]
\ No newline at end of file
......@@ -10,6 +10,7 @@
- [Description](#description)
- [Initial Setup](#initial-setup)
- [Running Development Environment](#running-development-environment)
- [Running Development Environment with Docker Compose](#running-development-environment-with-docker-compose)
- [Contributors](#contributors)
## Description
......@@ -114,6 +115,62 @@ You can load an some initial data with this command:
python3 manage.py loaddata */fixtures/initial.json
```
## Running Development Environment with Docker Compose
Requires [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/).
If you have already created a `.env` file, adjust its content to:
```bash
SECRET_KEY=place-your-secret-key-here
IS_LOCAL=True
DB_NAME=marjinal
DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=db
DB_PORT=5432
```
Create a `.env.db` file, fill its content with:
```bash
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=marjinal
```
To build and run the containers:
```bash
docker-compose up -d --build
```
To run the containers without rebuilding them first:
```bash
docker-compose up -d
```
To stop the containers:
```bash
docker-compose down
```
To run any command related to the DIGIPUS container:
```bash
docker-compose exec digipus <command>
```
For example, to migrate the database:
```bash
docker-compose exec digipus python manage.py migrate
```
To run any command related to the database container:
```bash
docker-compose exec db <command>
```
For example, to run `psql`:
```bash
docker-compose exec db psql -U postgres
```
## Contributors
This repository is one of the projects from the Software Project course.
......
......@@ -12,7 +12,7 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/profil-admin/">
<a class="nav-link" href="/profil/">
<span class="mr-2 d-none d-lg-inline text-gray-600 small">Profil</span>
</a>
</li>
......
......@@ -30,6 +30,7 @@
<th scope="col">Kontributor</th>
<th scope="col">Status</th>
<th scope="col">Jumlah Like</th>
<th scope="col">Pilihan</th>
</tr>
</thead>
<tfoot>
......@@ -40,6 +41,7 @@
<th scope="col">Kontributor</th>
<th scope="col">Status</th>
<th scope="col">Jumlah Like</th>
<th scope="col">Pilihan</th>
</tr>
</tr>
</tfoot>
......@@ -57,6 +59,11 @@
<td>{{ materi.uploader }}</td>
<td>{{ materi.status }}</td>
<td>{{ materi.like_count }}</td>
<td>
{% if not materi.deleted_at %}<a type="button" href="/materi/{{materi.id}}/delete" class="reject-button button-decoration"
style="background-color:red">Hapus</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
......
......@@ -118,7 +118,7 @@
</li>
<li class="nav-item">
<a class="nav-link" href="/profil-admin/">
<a class="nav-link" href="/profil/">
<span class="mr-2 d-none d-lg-inline text-gray-600 small">Profil</span>
</a>
</li>
......
......@@ -127,7 +127,7 @@
</li>
<li class="nav-item">
<a class="nav-link" href="/profil-admin/">
<a class="nav-link" href="/profil/">
<span class="mr-2 d-none d-lg-inline text-gray-600 small">Profil</span>
</a>
</li>
......
......@@ -151,7 +151,7 @@
</li>
<li class="nav-item">
<a class="nav-link" href="/profil-admin/">
<a class="nav-link" href="/profil/">
<span
class="mr-2 d-none d-lg-inline text-gray-600 small"
>Profil</span
......
......@@ -505,7 +505,7 @@ class KelolaMateriView(TemplateView):
def get_context_data(self, **kwargs):
context = super(KelolaMateriView, self).get_context_data(**kwargs)
context['materi_list'] = Materi.objects.all()
context['materi_list'] = Materi.all_objects.all()
return context
def get(self, request, *args, **kwargs):
......
# Generated by Django 3.1 on 2020-10-23 03:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0022_merge_20201011_1122'),
]
operations = [
migrations.AddField(
model_name='materi',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True),
),
]
......@@ -39,6 +39,15 @@ class Category(models.Model):
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 = search.SearchVector("title", weight="A")
search_query = search.SearchQuery(search_text)
......@@ -51,8 +60,23 @@ class MateriManager(models.Manager):
return search_result
class SoftDeletionQuerySet(models.query.QuerySet):
def delete(self):
return super(SoftDeletionQuerySet, self).update(deleted_at=timezone.now())
class Materi(models.Model):
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")
......@@ -61,7 +85,7 @@ class Materi(models.Model):
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")
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)
......@@ -100,6 +124,11 @@ class Materi(models.Model):
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 is_like(self):
......
......@@ -46,12 +46,12 @@
<!-- Nav Item - Dashboard -->
<li class="nav-item">
<a class="nav-link" href="/profil-admin/">
<a class="nav-link" href="/profil/">
<span>Halaman Profil</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/sunting-admin/">
<a class="nav-link" href="/sunting/">
<span>Sunting Profil</span></a>
</li>
......@@ -86,7 +86,7 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/profil-admin/">
<a class="nav-link" href="/profil/">
<span class="mr-2 d-none d-lg-inline text-gray-600 small">Profil</span>
</a>
</li>
......
......@@ -278,6 +278,7 @@
</div>
{% if materi_data.status == "APPROVE" %}
<div id="komentar" class="container-fluid comments-wrapper p-0">
<h1>Komentar ({{ materi_data.comment_count }})</h1>
{% if is_authenticated %}
<div class="add-comments col col-8 bg-white shadow-sm rounded p-3 mb-3">
<form method="POST">
......
......@@ -31,14 +31,10 @@
<li class="nav-item">
<a class="nav-link" href="/administration">Administrasi</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/profil-admin">Profil</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="/profil">Profil</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="/profil">Profil</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">Logout</a>
</li>
......
{% extends "base.html" %}
{% load static %}
{% load humanize %}
{% block title %}Digipus - {% endblock %}
{% block header %}
......@@ -86,6 +87,8 @@
</table>
</div>
</div>
<h4 id="rating">Rating: {% if avg_rating.avg_rating %}{{ avg_rating.avg_rating|floatformat:"2"|intcomma }}{% else %}0{% endif %}</h4>
<h6>oleh: {{ count_rating }} orang</h6>
<div class="row"><form method="post">{{ form_rating }} {% csrf_token %}
<button type="submit" class="form-control" style="margin-top: 1rem">Submit</button></form> </div>
</div>
......
{% extends 'app/base_admin.html'%}
{% load static %}
{% block title %}
<title>Halaman Profil Admin | Digipus</title>
{% endblock %}
{% block content %}
<div class="profile-content white-text">
<div>
{% if not user.default_profile_picture %}
<img class="img-profile rounded-circle" src="https://i.ibb.co/9wgPzyZ/default-image.png" alt="User profile picture">
{% else %}
<img class="img-profile rounded-circle" src="{{ user.profile_picture.url }}" alt="User profile picture">
{% endif %}
<div class="profile-margin"></div>
<h2>{{ user.name }}</h2>
<h4>{{ user.email }}</h4>
<h4>{{ user.biography }}</h4>
<div class="profile-margin"></div>
<div class="row">