Fakultas Ilmu Komputer UI

Commit 4b5f8019 authored by Muhammad Feril Bagus Perkasa's avatar Muhammad Feril Bagus Perkasa
Browse files

[#64] News: Create News (Admin)

parent ecadc079
......@@ -50,4 +50,12 @@
<!-- Divider -->
<hr class="sidebar-divider my-0">
<li class="nav-item">
<a class="nav-link" href="/administration/news/list">
<span>Kelola Berita</span></a>
</li>
<!-- Divider -->
<hr class="sidebar-divider my-0">
</ul>
\ No newline at end of file
......@@ -46,6 +46,7 @@ INSTALLED_APPS = [
"register.apps.RegisterConfig",
"administration.apps.AdministrationConfig",
'crispy_forms',
"news.apps.NewsConfig",
"traffic_statistics",
]
......
......@@ -24,5 +24,6 @@ urlpatterns = [
path("", include("authentication.urls"), name="auth"),
path("", include("app.urls"), name="app"),
path("administration/", include("administration.urls")),
path("", include("news.urls"), name="news"),
path("statistics/", include("traffic_statistics.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class NewsConfig(AppConfig):
name = 'news'
from django import forms
from .models import News
class NewsForm(forms.ModelForm):
class Meta:
attribute_text_input = {
'class' : 'form-control col-6'
}
attribute_text_area = {
'class' : 'form-control col-8',
'rows' : "5"
}
model = News
fields = [
"title", "cover", "content"
]
widgets = {
"title" : forms.TextInput(
attrs=attribute_text_input
),
"content" : forms.Textarea(
attrs=attribute_text_area
)
}
\ No newline at end of file
# Generated by Django 3.1 on 2020-10-03 07:47
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='News',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('content', models.TextField()),
('timestamp', models.DateTimeField(auto_now=True)),
('cover', models.ImageField(upload_to='news')),
],
),
]
from django.db import models
class News(models.Model):
title = models.CharField(max_length = 100)
content = models.TextField()
timestamp = models.DateTimeField(auto_now = True)
cover = models.ImageField(upload_to = 'news')
\ No newline at end of file
{% extends 'administration/base_administrasi2.html' %}
{% load static %}
{% block title %}
<title>Form Pembuatan Berita</title>
{% endblock %}
{% block stylesheets %}
<script src="https://code.jquery.com/jquery-3.4.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.16/dist/summernote-bs4.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.16/dist/summernote-bs4.min.js"></script>
{% endblock stylesheets %}
{% block content %}
<div class="row-12">
{% if type == 'update' %}
<form method="POST" action="{% url 'update_news_form' form.instance.pk%}" enctype="multipart/form-data">
{% csrf_token %}
<h1 class="h3 mb-2 text-gray-800">
Formulir Sunting Berita
</h1>
<br>
{{form.as_p}}
<br>
<button type="submit" class="btn btn-primary">Update News</button>
</form>
{% else %}
<form method="POST" action="{% url 'post_news_form' %}" enctype="multipart/form-data">
{% csrf_token %}
<h1 class="h3 mb-2 text-gray-800">
Formulir Tambah Berita
</h1>
<br>
{{form.as_p}}
<br>
<button type="submit" class="btn btn-primary">Post News</button>
</form>
{% endif %}
</div>
<script>
$('#id_content').summernote({
placeholder: 'Write here ...',
tabsize: 2,
height: 300,
codeviewFilter: false,
codeviewIframeFilter: true,
toolbar: [
['style', ['style']],
['font', ['bold', 'italic', 'underline', 'clear']],
['fontname', ['fontname']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['insert', ['link', 'picture', 'video']],
['view', ['fullscreen']],
]
});
</script>
{% endblock %}
{% extends 'administration/base_administrasi2.html' %}
{% load static %}
{% block title %}
<title>Kelola Berita | Digipus</title>
{% endblock %}
{% block content %}
<!-- Page Heading -->
<h1 class="h3 mb-2 text-gray-800">Kelola Berita</h1>
<br>
<!-- DataTales Example -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<div class="d-flex">
<div class="mr-auto p-2">
<h6 class="m-0 font-weight-bold text-primary">Tabel Daftar Berita</h6>
</div>
<div class="p-2">
<a href="/administration/news/form" class="accept-button button-decoration button-header">Buat Berita Baru</a>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="dataTable">
<caption class="table_caption">Daftar Berita</caption>
<thead>
<tr>
<th id="table_title">Judul</th>
<th id="table_timestamp">Waktu Diperbarui Terakhir</th>
<th id="table_buttons">Pilihan</th>
</tr>
</thead>
<tbody>
{% for current in news_list %}
<tr>
<td>{{ current.title }}</td>
<td>{{ current.timestamp }}</td>
<td class="verif-buttons">
<span>
<a href="/administration/news/form/edit/{{ current.id }}" class="accept-button button-decoration">Update</a>
<button type="button" class="reject-button button-decoration" data-toggle="modal" data-target="#confirmModal{{ current.id }}">Hapus</button>
<div class="modal fade" id="confirmModal{{ current.id }}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Konfirmasi Penghapusan Berita "{{current.title}}"</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>Silahkan konfirmasi penghapusan berita dengan tekan tombol hapus di bawah</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<a href="/administration/news/delete/{{current.id}}" type="button" class="btn btn-danger">Hapus</a>
</div>
</div>
</div>
</div>
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
\ No newline at end of file
import shutil
import tempfile
import uuid
from django.contrib.auth import get_user_model
from django.test import TestCase, override_settings, Client
from django.core.files.uploadedfile import SimpleUploadedFile
from io import BytesIO
from PIL import Image
from django.core.files.base import File
from .models import News
from administration import models
MOCK_MEDIA_ROOT = tempfile.mkdtemp()
@override_settings(MEDIA_ROOT = MOCK_MEDIA_ROOT)
class NewsModelTest(TestCase):
def setUp(self):
self.test_file_jpg1 = SimpleUploadedFile("foto1.jpg", b"file_content")
self.test_file_jpg2 = SimpleUploadedFile("foto2.jpg", b"file_content")
self.news1 = News.objects.create(
title = 'title1',
content = 'content news1',
cover = self.test_file_jpg1
)
self.news2 = News.objects.create(
title = 'title2',
content = 'content news2',
cover = self.test_file_jpg2
)
self.news1.save()
self.news2.save()
@classmethod
def tearDownClass(cls):
shutil.rmtree(MOCK_MEDIA_ROOT, ignore_errors=True)
super().tearDownClass()
def test_object_news_is_created(self):
self.assertTrue(type(self.news1), News)
self.assertTrue(type(self.news2), News)
def test_id_news_is_generated(self):
self.assertIsNotNone(self.news1.id)
self.assertIsNotNone(self.news2.id)
def test_news1_title_is_title1(self):
self.assertEqual(self.news1.title, "title1")
def test_news1_adn_news2_title_is_not_equal(self):
self.assertNotEqual(self.news1.title, self.news2.title)
def test_news1_content_is_content_news1(self):
self.assertEqual(self.news1.content, "content news1")
def test_news1_and_news2_content_is_not_equal(self):
self.assertNotEqual(self.news1.content, self.news2.content)
def test_news1_timestamp_is_not_none(self):
self.assertIsNotNone(self.news1.timestamp)
def test_news1_cover_is_not_none(self):
self.assertIsNotNone(self.news1.cover)
def test_news1_and_news2_cover_is_not_equal(self):
self.assertNotEqual(self.news1.cover, self.news2.cover)
@override_settings(MEDIA_ROOT = MOCK_MEDIA_ROOT)
class NewsFormTest(TestCase):
def setUp(self):
self.client = Client()
self.root_url = "/"
self.base_url = '/administration/news/'
self.form_url = self.base_url+"form/"
self.post_form_url = self.form_url+"post"
self.delete_news_url = self.base_url+"delete/"
self.update_form_url = self.form_url+"edit/"
self.list_news_url = self.base_url+"list"
self.admin_jpg_name = "news_admin.jpg"
self.admin_credential = {
"email": "admin@gov.id",
"password": str(uuid.uuid4())
}
self.contributor_credential = {
"email": "kontributor@gov.id",
"password": str(uuid.uuid4())
}
self.admin = get_user_model().objects.create_user(
**self.admin_credential, name="Admin", is_admin=True)
self.contributor = get_user_model().objects.create_user(
**self.contributor_credential, name="Kontributor", is_contributor=True
)
self.test_file_jpg3 = SimpleUploadedFile("foto3.jpg", b"file_content")
self.test_file_jpg4 = SimpleUploadedFile("foto4.jpg", b"file_content")
self.news3 = News.objects.create(
title = 'title3',
content = 'content news3',
cover = self.test_file_jpg3
)
self.news4 = News.objects.create(
title = 'title4',
content = 'content news4',
cover = self.test_file_jpg4
)
self.news3.save()
self.news4.save()
@classmethod
def tearDownClass(cls):
shutil.rmtree(MOCK_MEDIA_ROOT, ignore_errors=True)
super().tearDownClass()
@staticmethod
def get_image_file(name, ext='png', size=(50, 50), color=(256, 0, 0)):
file_obj = BytesIO()
image = Image.new("RGBA", size=size, color=color)
image.save(file_obj, ext)
file_obj.seek(0)
return File(file_obj, name=name)
def test_post_form_news_open_raw_url(self):
self.client.login(**self.admin_credential)
response = self.client.get(self.post_form_url)
self.assertRedirects(response, self.root_url)
self.client.logout()
def test_post_form_news_by_admin_and_form_is_valid_data_exist(self):
self.client.login(**self.admin_credential)
news_data = {
"title" : "news_admin",
"content" : "content_admin",
"cover" : self.get_image_file(self.admin_jpg_name)
}
response = self.client.post(self.post_form_url, news_data, format='multipart')
self.assertTrue(News.objects.filter(title="news_admin").exists())
self.assertRedirects(response, self.list_news_url)
self.client.logout()
def test_post_form_news_by_admin_and_form_is_not_valid_and_data_not_exist(self):
self.client.login(**self.admin_credential)
news_data = {
"title" : "not_news_admin",
}
response = self.client.post(self.post_form_url, news_data, format='multipart')
self.assertFalse(News.objects.filter(title="not_news_admin").exists())
self.assertRedirects(response, self.root_url)
self.client.logout()
def test_post_form_news_by_kontributi_and_form_is_valid_data_not_exist(self):
self.client.login(**self.contributor_credential)
news_data = {
"title" : "news_contrib",
"content" : "content_admin",
"cover" : self.get_image_file(self.admin_jpg_name)
}
response = self.client.post(self.post_form_url, news_data, format='multipart')
self.assertFalse(News.objects.filter(title="news_admin").exists())
self.assertRedirects(response, self.root_url)
self.client.logout()
def test_delete_news_by_admin_and_data_is_exist_and_data_deleted(self):
self.client.login(**self.admin_credential)
id_news4 = self.news4.id
response = self.client.post(self.delete_news_url+str(id_news4), {})
self.assertFalse(News.objects.filter(pk=id_news4).exists())
self.assertRedirects(response, self.list_news_url)
self.client.logout()
def test_delete_news_by_contributor_and_data_is_exist_and_data_not_deleted(self):
self.client.login(**self.contributor_credential)
id_news4 = self.news4.id
response = self.client.post(self.delete_news_url+str(id_news4), {})
self.assertTrue(News.objects.filter(pk=id_news4).exists())
self.assertRedirects(response, self.root_url)
self.client.logout()
def test_delete_news_by_anonymous_and_data_is_exist_and_data_not_deleted(self):
id_news4 = self.news4.id
response = self.client.post(self.delete_news_url+str(id_news4), {})
self.assertTrue(News.objects.filter(pk=id_news4).exists())
self.assertEqual(response.status_code, 302)
def test_delete_news_by_admin_and_data_is_not_exist(self):
self.client.login(**self.admin_credential)
response = self.client.post(self.delete_news_url+"4124123", {})
self.assertRedirects(response, self.list_news_url)
self.client.logout()
def test_form_news_url_is_exist_for_admin(self):
self.client.login(**self.admin_credential)
response = self.client.get(self.form_url)
self.assertEqual(response.status_code, 200)
self.client.logout()
def test_form_news_url_is_not_exist_for_contributor(self):
self.client.login(**self.contributor_credential)
response = self.client.get(self.form_url)
self.assertRedirects(response, self.root_url)
self.client.logout()
def test_form_news_url_is_not_exist_for_anonymous(self):
response = self.client.get(self.form_url)
self.assertEqual(response.status_code, 302)
def test_form_news_using_news_form_html(self):
self.client.login(**self.admin_credential)
response = self.client.get(self.form_url)
self.assertTemplateUsed(response, 'news_form.html')
self.client.logout()
def test_update_form_news_using_news_form_html(self):
self.client.login(**self.admin_credential)
id_news4 = str(self.news4.id)
response = self.client.get(self.update_form_url+id_news4)
self.assertTemplateUsed(response, 'news_form.html')
self.client.logout()
def test_update_form_news_but_news_not_exist(self):
self.client.login(**self.admin_credential)
response = self.client.get(self.update_form_url+"283912")
self.assertEqual(response.status_code, 302)
self.client.logout()
def test_update_news_form_for_non_admin(self):
self.client.login(**self.contributor_credential)
id_news3 = str(self.news3.id)
response = self.client.get(self.update_form_url+id_news3)
self.assertRedirects(response, self.root_url)
self.client.logout()
def test_update_form_news_by_admin_and_form_is_valid_change_data(self):
self.client.login(**self.admin_credential)
id_news3 = self.news3.id
news_data = {
"id" : id_news3,
"title" : "title3_new",
"content" : "content_admin",
"cover" : self.get_image_file(self.admin_jpg_name)
}
response = self.client.post(self.update_form_url+str(id_news3), news_data, format='multipart')
self.assertTrue(News.objects.filter(title="title3_new").exists())
self.assertFalse(News.objects.filter(title="title3").exists())
self.assertEqual(response.status_code, 302)
self.client.logout()
def test_update_form_news_by_contributor_and_form_is_valid_not_change_data(self):
self.client.login(**self.contributor_credential)
id_news3 = self.news3.id
news_data = {
"id" : id_news3,
"title" : "title3_new",
"content" : "content_admin",
"cover" : self.get_image_file(self.admin_jpg_name)
}
response = self.client.post(self.update_form_url+str(id_news3), news_data, format='multipart')
self.assertFalse(News.objects.filter(title="title3_new").exists())
self.assertTrue(News.objects.filter(title="title3").exists())
self.assertEqual(response.status_code, 302)
self.client.logout()
def test_list_news_url_is_exist_for_admin(self):
self.client.login(**self.admin_credential)
response = self.client.get(self.list_news_url)
self.assertEqual(response.status_code, 200)
self.client.logout()
def test_list_news_url_is_not_exist_for_contributor(self):
self.client.login(**self.contributor_credential)
response = self.client.get(self.list_news_url)
self.assertRedirects(response, self.root_url)
self.client.logout()
def test_list_news_url_is_not_exist_for_anonymous(self):
response = self.client.get(self.list_news_url)
self.assertEqual(response.status_code, 302)
def test_list_news_using_news_form_html(self):
self.client.login(**self.admin_credential)
response = self.client.get(self.list_news_url)
self.assertTemplateUsed(response, 'news_list.html')
self.client.logout()
\ No newline at end of file
from django.urls import path
from . import views
urlpatterns = [
path(
'administration/news/form/post',
views.post_news_form,
name = "post_news_form"
),
path(
'administration/news/delete/<int:id_news>',
views.delete_news_by_id,
name = "delete_news_by_id"
),
path(
'administration/news/form/',
views.show_news_form,
name = "show_news_form"
),
path(
'administration/news/form/edit/<int:id_news>',
views.update_news_form,
name = "update_news_form"
),
path(
'administration/news/list',
views.show_news_list,
name = "show_news_list"
),
]
\ No newline at end of file
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_exempt
from .forms import NewsForm
from .models import News
html_news_form = "news_form.html"
html_news_list = "news_list.html"
login_url = "/login/"
root_url = "/"
news_list_url = "/administration/news/list"
@login_required(login_url=login_url)
def post_news_form(request):
if request.user.is_admin is False:
return redirect(root_url)
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()
return redirect(news_list_url)
return redirect("/")
@csrf_exempt
@login_required(login_url=login_url)