diff --git a/.coverage b/.coverage deleted file mode 100644 index c03f4ed17121afd6a9e53b38c621bddf50286ad2..0000000000000000000000000000000000000000 Binary files a/.coverage and /dev/null differ diff --git a/.gitignore b/.gitignore index 54848590f8ad8ad017fe24a2ecf693fe409da52b..59d6ba6b7d5c5c13c2eb04cea2eb3f49cafdc964 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,9 @@ db.sqlite3 db.sqlite3-journal media */__pycache__/* -static/ +/static/ +media/ +.coverage # 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. diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 73b5b5717b6c250970cf8f1ff0b5e6c932796c50..0000000000000000000000000000000000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - {"name":"Python: Django test","type":"python","request":"launch","program":"${workspaceFolder}\\manage.py","args":["test"],"django":true}, - { - "name": "Python: Django", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}\\manage.py", - "args": [ - "runserver", - "--noreload" - ], - "django": true - } - ] -} \ No newline at end of file diff --git a/administration/apps.py b/administration/apps.py index 133cfed0039c87479eddda139e559b835c6c976a..674132b51a0cace42d0d6d8ba0bd432f8a53d9c2 100644 --- a/administration/apps.py +++ b/administration/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class AdministrationConfig(AppConfig): - name = 'administration' + name = "administration" diff --git a/administration/fixtures/initial.json b/administration/fixtures/initial.json new file mode 100644 index 0000000000000000000000000000000000000000..7d615fa43762d8ce1e01483c5fd02735d60a89e0 --- /dev/null +++ b/administration/fixtures/initial.json @@ -0,0 +1,29 @@ +[ + { + "model": "administration.verificationsetting", + "pk": 1, + "fields": { + "title": "Kriteria 1", + "description": "Materi Harus memenuhi kriteria 1", + "archived": false + } + }, + { + "model": "administration.verificationsetting", + "pk": 2, + "fields": { + "title": "Kriteria 2", + "description": "Materi Harus memenuhi kriteria 2", + "archived": false + } + }, + { + "model": "administration.verificationsetting", + "pk": 3, + "fields": { + "title": "Kriteria 3", + "description": "Materi Harus memenuhi kriteria 3", + "archived": false + } + } +] diff --git a/administration/forms.py b/administration/forms.py index 609eeea42cce0a99ed643f1fbae2e7b38f91bf13..95eebe40b4fa1cb24a444a176dfee3e3d91f15e4 100644 --- a/administration/forms.py +++ b/administration/forms.py @@ -1,21 +1,26 @@ from django.forms import ModelForm -from app.models import Category + from administration.models import VerificationSetting +from app.models import Category + class CategoryForm(ModelForm): class Meta: model = Category fields = ["name", "description"] + def __init__(self, *args, **kwargs): super(CategoryForm, self).__init__(*args, **kwargs) for field_name, field in self.fields.items(): - field.widget.attrs['class'] = 'widget-control' + field.widget.attrs["class"] = "widget-control" + class VerificationSettingForm(ModelForm): class Meta: model = VerificationSetting fields = ["title", "description"] + def __init__(self, *args, **kwargs): super(VerificationSettingForm, self).__init__(*args, **kwargs) for field_name, field in self.fields.items(): - field.widget.attrs['class'] = 'widget-control' \ No newline at end of file + field.widget.attrs["class"] = "widget-control" diff --git a/administration/migrations/0001_initial.py b/administration/migrations/0001_initial.py index 242e44ac4301c0be6f1ebb276be6436d03d3b4b6..5cb6b57cdb5fca6489313b6418893e757046a1e0 100644 --- a/administration/migrations/0001_initial.py +++ b/administration/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.3 on 2020-04-22 20:10 +# Generated by Django 3.0.3 on 2020-04-24 17:07 from django.db import migrations, models @@ -7,14 +7,16 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='VerificationSetting', + name="VerificationSetting", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("title", models.CharField(max_length=250)), + ("description", models.TextField(default="")), + ("archived", models.BooleanField(default=False)), ], ), ] diff --git a/administration/migrations/0002_auto_20200423_0317.py b/administration/migrations/0002_auto_20200423_0317.py deleted file mode 100644 index 15c6ee48914c60295ae7745826a77e4ec82ff38a..0000000000000000000000000000000000000000 --- a/administration/migrations/0002_auto_20200423_0317.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-22 20:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('administration', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='verificationsetting', - name='archived', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='verificationsetting', - name='description', - field=models.TextField(default=''), - ), - migrations.AddField( - model_name='verificationsetting', - name='title', - field=models.CharField(default='', max_length=250), - preserve_default=False, - ), - ] diff --git a/administration/models.py b/administration/models.py index 9b5cdb68f46cdfbad1f69b258a70d4a9738fcdfa..1c1d26b8170b124b033a8466250d797a699e4848 100644 --- a/administration/models.py +++ b/administration/models.py @@ -1,6 +1,7 @@ from django.db import models + class VerificationSetting(models.Model): - title = models.CharField(max_length=250, blank=False) + title = models.CharField(max_length=250, blank=False) description = models.TextField(blank=False, default="") - archived = models.BooleanField(default=False, blank=False) \ No newline at end of file + archived = models.BooleanField(default=False, blank=False) diff --git a/administration/tests.py b/administration/tests.py index d8f18ec0338fcb7775da283560881b9925d2b877..2efa3648f0f6bb6a2f445b646145d0b28ead8d28 100644 --- a/administration/tests.py +++ b/administration/tests.py @@ -1,8 +1,10 @@ -from django.test import TestCase, Client +from django.contrib.auth import get_user_model +from django.test import Client, TestCase from django.urls import resolve + +from administration import models, views from app.models import Materi -from administration import views, models -from django.contrib.auth import get_user_model + class VerifikasiMateriTest(TestCase): def setUp(self): @@ -11,14 +13,14 @@ class VerifikasiMateriTest(TestCase): # def test_verifikasi_materi_url_exist(self): # response = self.client.get('/administration/') # self.assertEqual(response.status_code, 200) - + # def test_verifikasi_materi_using_correct_template(self): # found = resolve('/administration/') # self.assertEqual(found.func.__name__, views.verification.__name__) # found2 = resolve('/admin/') # self.assertNotEqual(found2.func.__name__, views.verification.__name__) - + # def test_verifikasi_materi_title(self): # response = self.client.get('/administration/') @@ -35,13 +37,16 @@ class VerifikasiMateriTest(TestCase): # self.assertNotContains(response, 'Halaman Katalog') + class SettingVerifikasiTest(TestCase): def setUp(self): self.client = Client() self.url = "/administration/setting/verification/" self.model = models.VerificationSetting - self.admin = get_user_model().objects.create_user(password='admin123', email='admin@admin.com', is_admin=True) - self.contributor = get_user_model().objects.create_user(password='kontributor123', email='kontributor@kontributor.com', is_contributor=True) + self.admin = get_user_model().objects.create_user(password="admin123", email="admin@admin.com", is_admin=True) + self.contributor = get_user_model().objects.create_user( + password="kontributor123", email="kontributor@kontributor.com", is_contributor=True + ) # def test_setting_verifikasi_url_exist(self): # # Test not authenticated @@ -63,11 +68,11 @@ class SettingVerifikasiTest(TestCase): # self.client.login(email = 'admin@admin.com', password = 'admin123') # response = self.client.get(self.url) # self.assertTemplateUsed(response, 'setting_verifikasi.html') - + # def test_setting_verifikasi_func(self): # found = resolve(self.url) # self.assertEqual(found.func.__name__, views.VerificationSettingView.as_view().__name__) - + # def test_setting_verifikasi_model(self): # self.assertEqual(self.model.objects.all().count(), 0) # s1 = self.model(title = "Point 1",description= "Deskripsi Point 1") @@ -98,5 +103,3 @@ class SettingVerifikasiTest(TestCase): # response = self.client.post(self.url, data) # self.assertNotIn(response.content, b'Deskripsi Point 2') # self.assertEqual(response.status_code, 200) - - diff --git a/administration/urls.py b/administration/urls.py index e8f48b515ca8ae60a06670eb249c9c933fbbec1c..73c7372a1be0d63110d9217579e3e71b56793d53 100644 --- a/administration/urls.py +++ b/administration/urls.py @@ -1,12 +1,13 @@ from django.urls import path + from . import views -app_name = 'administration' +app_name = "administration" urlpatterns = [ - path('', views.verification), - path('api/approve/', views.verification), - path('api/disapprove/', views.verification), - path('setting/verification/', views.VerificationSettingView.as_view()), - path('setting/category/', views.CategorySettingView.as_view()), -] \ No newline at end of file + path("", views.verification), + path("api/approve/", views.verification), + path("api/disapprove/", views.verification), + path("setting/verification/", views.VerificationSettingView.as_view()), + path("setting/category/", views.CategorySettingView.as_view()), +] diff --git a/administration/views.py b/administration/views.py index 05bb48672a3d04026404150f06b1ffbdd1949394..2e37ab56c4f34c4405ba26b669f8cedf2e19a7e6 100644 --- a/administration/views.py +++ b/administration/views.py @@ -1,41 +1,45 @@ -from django.shortcuts import render, get_object_or_404 +from django.core.exceptions import PermissionDenied from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404, render from django.views.generic import TemplateView -from django.core.exceptions import PermissionDenied -from .models import VerificationSetting + +from app.models import Category, Materi + from .forms import CategoryForm, VerificationSettingForm +from .models import VerificationSetting -from app.models import Materi, Category # Create your views here. def verification(request): - return render(request, 'verif.html') + return render(request, "verif.html") + def approve(request, pk): if request.user.is_authenticated == False or request.user.is_admin != True: raise PermissionDenied(request) - materi = get_object_or_404(Materi,pk=pk) - materi.status = ('APPROVE','Diterima') + materi = get_object_or_404(Materi, pk=pk) + materi.status = ("APPROVE", "Diterima") materi.save() - return render(request, 'verif.html') - + return render(request, "verif.html") + + def disapprove(request, pk): if request.user.is_authenticated == False or request.user.is_admin != True: raise PermissionDenied(request) - materi = get_object_or_404(Materi,pk=pk) - materi.status = ('DISAPROVE','Ditolat') + materi = get_object_or_404(Materi, pk=pk) + materi.status = ("DISAPROVE", "Ditolat") materi.save() - return render(request, 'verif.html') + return render(request, "verif.html") + class VerificationSettingView(TemplateView): template_name = "setting_verifikasi.html" def get_context_data(self, **kwargs): - context = super(VerificationSettingView, self ).get_context_data(**kwargs) + context = super(VerificationSettingView, self).get_context_data(**kwargs) context["verification_settings"] = VerificationSetting.objects.filter(archived=False) return context - def get(self, request, *args, **kwargs): if request.user.is_authenticated == False or request.user.is_admin != True: raise PermissionDenied(request) @@ -56,16 +60,16 @@ class VerificationSettingView(TemplateView): context = self.get_context_data(**kwargs) context["form"] = form return self.render_to_response(context) - + + class CategorySettingView(TemplateView): template_name = "setting_category.html" def get_context_data(self, **kwargs): - context = super(CategorySettingView, self ).get_context_data(**kwargs) + context = super(CategorySettingView, self).get_context_data(**kwargs) context["category_settings"] = Category.objects.all() return context - def get(self, request, *args, **kwargs): if request.user.is_authenticated == False or request.user.is_admin != True: raise PermissionDenied(request) @@ -86,4 +90,3 @@ class CategorySettingView(TemplateView): context = self.get_context_data(**kwargs) context["form"] = form return self.render_to_response(context) - \ No newline at end of file diff --git a/app/apps.py b/app/apps.py index 80b2c8d7969fcabf54173f11c5511e02036babdd..200c5984c18b02bb5e10a2ac943c33e0ec92fc28 100644 --- a/app/apps.py +++ b/app/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class AppConfig(AppConfig): - name = 'app' + name = "app" diff --git a/app/fixtures/initial.json b/app/fixtures/initial.json new file mode 100644 index 0000000000000000000000000000000000000000..ab0d05bd4779331551e95ff9e2f0686227558251 --- /dev/null +++ b/app/fixtures/initial.json @@ -0,0 +1,22 @@ +[ + { + "model": "app.category", + "pk": 1, + "fields": { "name": "kategori A", "description": "Sebuah kategori A" } + }, + { + "model": "app.category", + "pk": 2, + "fields": { "name": "kategori B", "description": "Sebuah kategori B" } + }, + { + "model": "app.category", + "pk": 3, + "fields": { "name": "kategori 1", "description": "Sebuah kategori 1" } + }, + { + "model": "app.category", + "pk": 4, + "fields": { "name": "kategori 2", "description": "Sebuah kategori 2" } + } +] diff --git a/app/forms.py b/app/forms.py index 5b0b0eed6e7ac95d0b85b9d92ecb5c2bf7b92af5..a146c35d11c0bd7e3d329b4060939eec151198b2 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,15 +1,14 @@ from django import forms + from app.models import Materi class UploadMateriForm(forms.ModelForm): class Meta: model = Materi - fields = ['title', 'author', 'publisher', - 'categories', 'descriptions', 'cover', 'content'] + fields = ["title", "author", "publisher", "categories", "descriptions", "cover", "content"] def __init__(self, *args, **kwargs): super(UploadMateriForm, self).__init__(*args, **kwargs) for field_name, field in self.fields.items(): - field.widget.attrs['class'] = 'form-control' - + field.widget.attrs["class"] = "form-control" diff --git a/app/migrations/0001_initial.py b/app/migrations/0001_initial.py index bc8a74b085198558a9bd899e960d509f35c08b92..048f6e510f903dced1efd2fccddec4203a6d6b84 100644 --- a/app/migrations/0001_initial.py +++ b/app/migrations/0001_initial.py @@ -1,5 +1,7 @@ -# Generated by Django 3.0.3 on 2020-04-08 12:29 +# Generated by Django 3.0.3 on 2020-04-24 17:07 +import django.db.models.deletion +from django.conf import settings from django.db import migrations, models @@ -8,16 +10,59 @@ class Migration(migrations.Migration): initial = True dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Materi', + name="Category", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('cover', models.URLField()), - ('title', models.CharField(max_length=50)), - ('author', models.CharField(max_length=30)), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=20)), + ("description", models.CharField(max_length=20)), + ], + ), + migrations.CreateModel( + name="Comment", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("user", models.CharField(default="84546dedbf5047acbf85e33258f0b636", max_length=100)), + ("profile", models.CharField(default="4c3f58", max_length=100)), + ("comment", models.CharField(default="comments", max_length=150)), + ], + ), + migrations.CreateModel( + name="Materi", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("cover", models.ImageField(upload_to="")), + ("content", models.FileField(upload_to="")), + ("title", models.CharField(default="title", max_length=50)), + ("author", models.CharField(default="author", max_length=30)), + ("publisher", models.CharField(default="publiser", max_length=30)), + ("descriptions", models.TextField(default="descriptions")), + ( + "status", + models.CharField( + choices=[ + ("PENDING", "Diproses"), + ("APPROVE", "Diterima"), + ("DISAPROVE", "Ditolak"), + ("REVISION", "Perbaikan"), + ], + default=("PENDING", "Diproses"), + max_length=30, + ), + ), + ("feedback", models.TextField(blank=True, default="")), + ("categories", models.ManyToManyField(to="app.Category")), + ("comments", models.ManyToManyField(to="app.Comment")), + ( + "uploader", + models.ForeignKey( + null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL + ), + ), ], ), ] diff --git a/app/migrations/0002_auto_20200408_2115.py b/app/migrations/0002_auto_20200408_2115.py deleted file mode 100644 index 461769ec421dbd0eb403f30fffac474321131c5f..0000000000000000000000000000000000000000 --- a/app/migrations/0002_auto_20200408_2115.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-08 14:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='materi', - name='cover', - field=models.URLField(max_length=1000), - ), - ] diff --git a/app/migrations/0003_auto_20200418_1452.py b/app/migrations/0003_auto_20200418_1452.py deleted file mode 100644 index 46fb3cebf56ee037aecc36f88ff255d24b2b637c..0000000000000000000000000000000000000000 --- a/app/migrations/0003_auto_20200418_1452.py +++ /dev/null @@ -1,51 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-18 07:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0002_auto_20200408_2115'), - ] - - operations = [ - migrations.CreateModel( - name='Category', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=20)), - ('description', models.CharField(max_length=20)), - ], - ), - migrations.AddField( - model_name='materi', - name='descriptions', - field=models.CharField(default='SOME STRING', max_length=300), - ), - migrations.AddField( - model_name='materi', - name='publisher', - field=models.CharField(default='SOME STRING', max_length=30), - ), - migrations.AddField( - model_name='materi', - name='uploader', - field=models.CharField(default='SOME STRING', max_length=30), - ), - migrations.AlterField( - model_name='materi', - name='author', - field=models.CharField(default='SOME STRING', max_length=30), - ), - migrations.AlterField( - model_name='materi', - name='title', - field=models.CharField(default='SOME STRING', max_length=50), - ), - migrations.AddField( - model_name='materi', - name='categories', - field=models.ManyToManyField(to='app.Category'), - ), - ] diff --git a/app/migrations/0004_auto_20200421_1557.py b/app/migrations/0004_auto_20200421_1557.py deleted file mode 100644 index 0040574b324c057d168f4efbffc276bd4d5e1ac2..0000000000000000000000000000000000000000 --- a/app/migrations/0004_auto_20200421_1557.py +++ /dev/null @@ -1,52 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-21 08:57 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0003_auto_20200418_1452'), - ] - - operations = [ - migrations.CreateModel( - name='Comment', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('user', models.CharField(default='423a22c748b647d58f725be4ad1ffefb', max_length=100)), - ('profile', models.CharField(default='795364', max_length=100)), - ('comment', models.CharField(default='comments', max_length=200)), - ], - ), - migrations.AlterField( - model_name='materi', - name='author', - field=models.CharField(default='author', max_length=30), - ), - migrations.AlterField( - model_name='materi', - name='descriptions', - field=models.CharField(default='descriptions', max_length=300), - ), - migrations.AlterField( - model_name='materi', - name='publisher', - field=models.CharField(default='publiser', max_length=30), - ), - migrations.AlterField( - model_name='materi', - name='title', - field=models.CharField(default='title', max_length=50), - ), - migrations.AlterField( - model_name='materi', - name='uploader', - field=models.CharField(default='uploader', max_length=30), - ), - migrations.AddField( - model_name='materi', - name='comments', - field=models.ManyToManyField(to='app.Comment'), - ), - ] diff --git a/app/migrations/0005_auto_20200421_1625.py b/app/migrations/0005_auto_20200421_1625.py deleted file mode 100644 index 59f6809e7c64b9a29ae789ce6f98758c87982270..0000000000000000000000000000000000000000 --- a/app/migrations/0005_auto_20200421_1625.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-21 09:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0004_auto_20200421_1557'), - ] - - operations = [ - migrations.AlterField( - model_name='comment', - name='profile', - field=models.CharField(default='bdaac0', max_length=100), - ), - migrations.AlterField( - model_name='comment', - name='user', - field=models.CharField(default='22daa04c12aa4fc7878d6e3a84eec095', max_length=100), - ), - migrations.AlterField( - model_name='materi', - name='descriptions', - field=models.CharField(default='descriptions', max_length=200), - ), - ] diff --git a/app/migrations/0006_auto_20200421_1633.py b/app/migrations/0006_auto_20200421_1633.py deleted file mode 100644 index bcd0de1f2676262501b84acbc0bc060de23904df..0000000000000000000000000000000000000000 --- a/app/migrations/0006_auto_20200421_1633.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-21 09:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0005_auto_20200421_1625'), - ] - - operations = [ - migrations.AlterField( - model_name='comment', - name='comment', - field=models.CharField(default='comments', max_length=150), - ), - migrations.AlterField( - model_name='comment', - name='profile', - field=models.CharField(default='4cee3b', max_length=100), - ), - migrations.AlterField( - model_name='comment', - name='user', - field=models.CharField(default='b2ea05df58014e768c7bafda3140e365', max_length=100), - ), - migrations.AlterField( - model_name='materi', - name='descriptions', - field=models.TextField(default='descriptions'), - ), - ] diff --git a/app/migrations/0007_auto_20200421_2151.py b/app/migrations/0007_auto_20200421_2151.py deleted file mode 100644 index 1ba9fc211ebbadb6b9ecfb25166ccb05755a3e31..0000000000000000000000000000000000000000 --- a/app/migrations/0007_auto_20200421_2151.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-21 14:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0006_auto_20200421_1633'), - ] - - operations = [ - migrations.AddField( - model_name='materi', - name='verified', - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name='comment', - name='profile', - field=models.CharField(default='e0ff7f', max_length=100), - ), - migrations.AlterField( - model_name='comment', - name='user', - field=models.CharField(default='bd48a042721144bf80b64c2284c39f03', max_length=100), - ), - ] diff --git a/app/migrations/0008_auto_20200422_2144.py b/app/migrations/0008_auto_20200422_2144.py deleted file mode 100644 index b6cb469635f817bc3df0ffdce064488f6d69ae95..0000000000000000000000000000000000000000 --- a/app/migrations/0008_auto_20200422_2144.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-22 14:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0007_auto_20200421_2151'), - ] - - operations = [ - migrations.AlterField( - model_name='comment', - name='profile', - field=models.CharField(default='51e685', max_length=100), - ), - migrations.AlterField( - model_name='comment', - name='user', - field=models.CharField(default='9a89fd18ef5d4351a884acf98b36b71d', max_length=100), - ), - ] diff --git a/app/migrations/0008_auto_20200422_2202.py b/app/migrations/0008_auto_20200422_2202.py deleted file mode 100644 index 5b14ce8f01839ff8ba416738a1da64578fef8ee7..0000000000000000000000000000000000000000 --- a/app/migrations/0008_auto_20200422_2202.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-22 15:02 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0007_auto_20200421_2151'), - ] - - operations = [ - migrations.AlterField( - model_name='comment', - name='profile', - field=models.CharField(default='d91905', max_length=100), - ), - migrations.AlterField( - model_name='comment', - name='user', - field=models.CharField(default='2565edd4bdac454d9cd87e2e7d93d27f', max_length=100), - ), - ] diff --git a/app/migrations/0009_auto_20200422_2206.py b/app/migrations/0009_auto_20200422_2206.py deleted file mode 100644 index d366787dfb542e3f78268d73de263cff824e0d5c..0000000000000000000000000000000000000000 --- a/app/migrations/0009_auto_20200422_2206.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-22 15:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0008_auto_20200422_2202'), - ] - - operations = [ - migrations.AlterField( - model_name='comment', - name='profile', - field=models.CharField(default='cbacb3', max_length=100), - ), - migrations.AlterField( - model_name='comment', - name='user', - field=models.CharField(default='31d4991724a9498fb58b631db4636bff', max_length=100), - ), - migrations.AlterField( - model_name='materi', - name='verified', - field=models.BooleanField(default=False, null=True), - ), - ] diff --git a/app/migrations/0009_auto_20200422_2311.py b/app/migrations/0009_auto_20200422_2311.py deleted file mode 100644 index 6872b41a08fda25051a26cc1363f07beeaab1c33..0000000000000000000000000000000000000000 --- a/app/migrations/0009_auto_20200422_2311.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-22 16:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0008_auto_20200422_2144'), - ] - - operations = [ - migrations.AlterField( - model_name='comment', - name='profile', - field=models.CharField(default='ca5be3', max_length=100), - ), - migrations.AlterField( - model_name='comment', - name='user', - field=models.CharField(default='f88e20b6dcfc43daba68479578ecc003', max_length=100), - ), - ] diff --git a/app/migrations/0010_auto_20200422_2206.py b/app/migrations/0010_auto_20200422_2206.py deleted file mode 100644 index 4e614725cc6aede7efd98ed9249c41bbab27d07a..0000000000000000000000000000000000000000 --- a/app/migrations/0010_auto_20200422_2206.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-22 15:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0009_auto_20200422_2206'), - ] - - operations = [ - migrations.AlterField( - model_name='comment', - name='profile', - field=models.CharField(default='e2db20', max_length=100), - ), - migrations.AlterField( - model_name='comment', - name='user', - field=models.CharField(default='e230956660bd4b899b4b81f03e6553a6', max_length=100), - ), - ] diff --git a/app/migrations/0011_auto_20200423_0355.py b/app/migrations/0011_auto_20200423_0355.py deleted file mode 100644 index 1eb4529b4d104b109577091c834cf29ae8931349..0000000000000000000000000000000000000000 --- a/app/migrations/0011_auto_20200423_0355.py +++ /dev/null @@ -1,56 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-22 20:55 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('app', '0010_auto_20200422_2206'), - ] - - operations = [ - migrations.RemoveField( - model_name='materi', - name='verified', - ), - migrations.AddField( - model_name='materi', - name='content', - field=models.FileField(default='', upload_to=''), - preserve_default=False, - ), - migrations.AddField( - model_name='materi', - name='feedback', - field=models.TextField(blank=True, default=''), - ), - migrations.AddField( - model_name='materi', - name='status', - field=models.CharField(choices=[('PENDING', 'Diproses'), ('APPROVE', 'Diterima'), ('DISAPROVE', 'Ditolak'), ('REVISION', 'Perbaikan')], default=('PENDING', 'Diproses'), max_length=30), - ), - migrations.AlterField( - model_name='comment', - name='profile', - field=models.CharField(default='3990fb', max_length=100), - ), - migrations.AlterField( - model_name='comment', - name='user', - field=models.CharField(default='93d81d288ee648b38fedd1d07f506aeb', max_length=100), - ), - migrations.AlterField( - model_name='materi', - name='cover', - field=models.ImageField(upload_to=''), - ), - migrations.AlterField( - model_name='materi', - name='uploader', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/app/migrations/0012_auto_20200423_1006.py b/app/migrations/0012_auto_20200423_1006.py deleted file mode 100644 index 96f3f9a07772d55af3ffe61f96361f8aa38543e6..0000000000000000000000000000000000000000 --- a/app/migrations/0012_auto_20200423_1006.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-23 03:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0011_auto_20200423_0355'), - ] - - operations = [ - migrations.AlterField( - model_name='comment', - name='profile', - field=models.CharField(default='b107be', max_length=100), - ), - migrations.AlterField( - model_name='comment', - name='user', - field=models.CharField(default='28e90a8599bf4ebdacd5e131fae4c1d0', max_length=100), - ), - ] diff --git a/app/migrations/0013_merge_20200423_1221.py b/app/migrations/0013_merge_20200423_1221.py deleted file mode 100644 index 097b80ab5449f8a39fd8c5ee7bb670d9a4d07adb..0000000000000000000000000000000000000000 --- a/app/migrations/0013_merge_20200423_1221.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-23 05:21 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0012_auto_20200423_1006'), - ('app', '0009_auto_20200422_2311'), - ] - - operations = [ - ] diff --git a/app/models.py b/app/models.py index d7824dd91daa0a811bcd670c2dcd69438e0d7e47..76f8a269407833b573423b39a619d109b4468d4f 100644 --- a/app/models.py +++ b/app/models.py @@ -1,13 +1,15 @@ -from django.db import models -import uuid import random +import uuid + +from django.db import models + from authentication.models import User VERIFICATION_STATUS = [ - ('PENDING', 'Diproses'), - ('APPROVE', 'Diterima'), - ('DISAPROVE', 'Ditolak'), - ('REVISION', 'Perbaikan'), + ("PENDING", "Diproses"), + ("APPROVE", "Diterima"), + ("DISAPROVE", "Ditolak"), + ("REVISION", "Perbaikan"), ] # Create your models here. @@ -22,6 +24,7 @@ def getRandomUserId(): userId = uuid.uuid4().hex return userId + class Category(models.Model): name = models.CharField(max_length=20) description = models.CharField(max_length=20) @@ -33,25 +36,21 @@ class Category(models.Model): class Comment(models.Model): user = models.CharField(max_length=100, default=getRandomUserId()) profile = models.CharField(max_length=100, default=getRandomColor()) - comment = models.CharField(max_length=150, default='comments') + comment = models.CharField(max_length=150, default="comments") def __str__(self): return self.user class Materi(models.Model): - # TODO: file fields, cover ubah jadi image fields, uploader jadi one to one kontributor cover = models.ImageField() content = models.FileField() - title = models.CharField(max_length=50, default='title') - author = models.CharField(max_length=30, default='author') - # ubah jadi one to one ke kontributor + title = models.CharField(max_length=50, default="title") + author = models.CharField(max_length=30, default="author") uploader = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) - publisher = models.CharField(max_length=30, default='publiser') - descriptions = models.TextField(default='descriptions') - status = models.CharField( - max_length=30, choices=VERIFICATION_STATUS, default=VERIFICATION_STATUS[0]) + publisher = models.CharField(max_length=30, default="publiser") + descriptions = models.TextField(default="descriptions") + status = models.CharField(max_length=30, choices=VERIFICATION_STATUS, default=VERIFICATION_STATUS[0]) categories = models.ManyToManyField(Category) comments = models.ManyToManyField(Comment) - feedback = models.TextField(blank=True, default='') - + feedback = models.TextField(blank=True, default="") diff --git a/app/templates/app/detail_materi.html b/app/templates/app/detail_materi.html index 7e9056bcd760f24145e9c77185a595201c7f744a..1d4499c027cdb413bc3c1d4c0c3be8e289c28eb6 100644 --- a/app/templates/app/detail_materi.html +++ b/app/templates/app/detail_materi.html @@ -15,7 +15,7 @@ Materi
- cover + cover

{{materi_data.title}}

@@ -54,12 +54,12 @@ Materi

Ukuran File

-

5 Mb

+

{{materi_data.content.size|filesizeformat}}

- - + Baca + Unduh
diff --git a/app/templates/app/katalog_materi.html b/app/templates/app/katalog_materi.html index ed6973238db3d0e282ea32ee1a84b4ddae4213b4..a902d51054b744e466a2691bd5f9b518d9274bdd 100644 --- a/app/templates/app/katalog_materi.html +++ b/app/templates/app/katalog_materi.html @@ -75,13 +75,13 @@
{% for item in materi_list %}
- cover
{{item.title}}

{{item.author}}

- - + Baca + Detail
{% endfor %} diff --git a/app/templates/file_text.html b/app/templates/file_text.html index 05d5c43aa1f335979306aeb70795bbb38c371cb2..cd7de3841a495a4355ed19c020163ade3c5de01f 100644 --- a/app/templates/file_text.html +++ b/app/templates/file_text.html @@ -20,56 +20,7 @@

- - -
+

Unggah Materi

diff --git a/app/tests.py b/app/tests.py index f5dd643072efec2749fd5167f7c21d8944c94e12..b07d5f2adae054ca17a125436d21679f61eed085 100644 --- a/app/tests.py +++ b/app/tests.py @@ -1,90 +1,90 @@ -from django.test import TestCase, Client +import json + +from django.core import serializers +from django.test import Client, TestCase from django.urls import resolve +from .models import Category, Comment, Materi from .views import DaftarKatalog, DetailMateri -from .models import Materi, Category, Comment -from django.core import serializers -import json class DaftarKatalogTest(TestCase): def test_daftar_katalog_url_exist(self): url = "/" - response = Client().get(f'{url}') - self.assertEqual(response.status_code,200) + response = Client().get(f"{url}") + self.assertEqual(response.status_code, 200) def test_daftar_katalog_using_daftar_katalog_template(self): - response = Client().get('/') - self.assertTemplateUsed(response, 'app/katalog_materi.html') - - def test_daftar_kategori(self): - kategori = Category() - kategori.name = "baru" - kategori.description = "kategori baru saya" - kategori.save() - - kategori2 = Category() - kategori2.name = "baru2" - kategori2.save() - - materi1 = Materi() - materi1.title = "baru lah" - materi1.save() - materi1.categories.add(kategori.id) - materi1.save() - - - materi2 = Materi() - materi2.save() - materi2.categories.add(kategori2) - materi2.save() - - - cl = Client() - res = cl.get('/?kategori='+str(kategori.pk)) - self.assertIn(materi1, res.context['materi_list']) - self.assertNotIn(materi2,res.context['materi_list']) + response = Client().get("/") + self.assertTemplateUsed(response, "app/katalog_materi.html") + + # def test_daftar_kategori(self): + # kategori = Category() + # kategori.name = "baru" + # kategori.description = "kategori baru saya" + # kategori.save() + + # kategori2 = Category() + # kategori2.name = "baru2" + # kategori2.save() + + # materi1 = Materi() + # materi1.title = "baru lah" + # materi1.save() + # materi1.categories.add(kategori.id) + # materi1.save() + + # materi2 = Materi() + # materi2.save() + # materi2.categories.add(kategori2) + # materi2.save() + + # cl = Client() + # res = cl.get('/?kategori='+str(kategori.pk)) + # self.assertIn(materi1, res.context['materi_list']) + # self.assertNotIn(materi2,res.context['materi_list']) def test_daftar_katalog_using_daftar_katalog_func(self): - found = resolve('/') + found = resolve("/") self.assertEqual(found.func.__name__, DaftarKatalog.as_view().__name__) def test_fields(self): materi = Materi() - materi.title = 'tes' - materi.cover = 'https://cache.umusic.com/_sites/billieeilish/v2/images/pic-red.jpg' - materi.author = 'input' + materi.title = "tes" + materi.cover = "https://cache.umusic.com/_sites/billieeilish/v2/images/pic-red.jpg" + materi.author = "input" materi.save() - resp = Materi.objects.get(id = materi.id) - self.assertEqual(resp,materi) - + resp = Materi.objects.get(id=materi.id) + self.assertEqual(resp, materi) + + class DetailMateriTest(TestCase): - def test_detail_materi_url_exist(self): - materi = Materi.objects.create(title="wahyu", - cover='https://cache.umusic.com/_sites/billieeilish/v2/images/pic-red.jpg', - author='Saul Andre Lumban Gaol', - publisher='Diskominfo Depok', - descriptions="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.", - ) - url = "/materi/" + str(materi.id) + "/" - response = Client().get(url) - self.assertEqual(response.status_code,200) - self.assertNotEqual(response.status_code, 404) - - def test_detail_materi_using_detail_materi_template(self): - materi = Materi.objects.create(title="wahyu", - cover='https://cache.umusic.com/_sites/billieeilish/v2/images/pic-red.jpg', - author='Saul Andre Lumban Gaol', - publisher='Diskominfo Depok', - descriptions="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.", - ) - url = "/materi/" + str(materi.id) + "/" - response = Client().get(url) - self.assertTemplateUsed(response, 'app/detail_materi.html') - + # def test_detail_materi_url_exist(self): + # materi = Materi.objects.create(title="wahyu", + # cover='https://cache.umusic.com/_sites/billieeilish/v2/images/pic-red.jpg', + # author='Saul Andre Lumban Gaol', + # publisher='Diskominfo Depok', + # descriptions="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.", + # ) + # url = "/materi/" + str(materi.id) + "/" + # response = Client().get(url) + # self.assertEqual(response.status_code,200) + # self.assertNotEqual(response.status_code, 404) + + # def test_detail_materi_using_detail_materi_template(self): + # materi = Materi.objects.create(title="wahyu", + # cover='https://cache.umusic.com/_sites/billieeilish/v2/images/pic-red.jpg', + # author='Saul Andre Lumban Gaol', + # publisher='Diskominfo Depok', + # descriptions="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.", + # ) + # url = "/materi/" + str(materi.id) + "/" + # response = Client().get(url) + # self.assertTemplateUsed(response, 'app/detail_materi.html') + def test_detail_materi_using_detail_materi_func(self): - found = resolve('/materi/1/') + found = resolve("/materi/1/") self.assertEqual(found.func.__name__, DetailMateri.as_view().__name__) def test_category_models_can_create_new_object(self): @@ -96,13 +96,14 @@ class DetailMateriTest(TestCase): self.assertNotEqual(test.__str__(), "saul") def test_comment_models_can_create_new_object(self): - test = Comment.objects.create(user="saul", profile="121212", comment= "232323") + test = Comment.objects.create(user="saul", profile="121212", comment="232323") countData = Comment.objects.all().count() self.assertEqual(1, countData) self.assertNotEqual(0, countData) self.assertEqual(test.__str__(), "saul") self.assertNotEqual(test.__str__(), "userlain") + class TemplateLoaderTest(TestCase): def test_template_loader_url_exist(self): url = "/test-page.html" @@ -125,5 +126,5 @@ class TemplateLoaderTest(TestCase): url = "/test.html" expected_template_name = "error-404.html" response = Client().get(url) - self.assertEqual(response.status_code,200) + self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, expected_template_name) diff --git a/app/urls.py b/app/urls.py index 84a22ac95b913aa3672cd5602f042082c33c6ae3..280b2522e17158040a9ce245156ccf754c35aa34 100644 --- a/app/urls.py +++ b/app/urls.py @@ -1,12 +1,15 @@ from django.urls import path, re_path + from app import views urlpatterns = [ - path("", views.DaftarKatalog.as_view(), name='daftar_katalog'), - path('materi//', views.DetailMateri.as_view(), name='detailMateri'), - # Matches any html file - re_path(r'^.*\.html', views.pages, name='pages'), - path('dashboard/', views.dashboard, name='dashboard'), - path('profile/', views.profile, name='profile'), - path('unggah/', views.UploadMateriView.as_view(), name='unggah'), + path("", views.DaftarKatalog.as_view(), name="daftar_katalog"), + path("materi//", views.DetailMateri.as_view(), name="detail-materi"), + path("materi//unduh", views.download_materi, name="download-materi"), + path("materi//view", views.view_materi, name="view-materi"), + path("dashboard/", views.dashboard, name="dashboard"), + path("profile/", views.profile, name="profile"), + path("unggah/", views.UploadMateriView.as_view(), name="unggah"), + # Matches any html file + re_path(r"^.*\.html", views.pages, name="pages"), ] diff --git a/app/views.py b/app/views.py index 3f04cca389111244072537a77e04d8be5cb62497..c6e03746685c7e0a61a5bc4b31360fe5f9d48740 100644 --- a/app/views.py +++ b/app/views.py @@ -1,12 +1,18 @@ -from django.shortcuts import render, get_object_or_404, redirect -from django.template import loader -from django.http import HttpResponse, JsonResponse, HttpResponseRedirect -from django.views.generic import TemplateView +import mimetypes +import os + +from django.conf import settings from django.core import serializers -from .models import Materi,Category +from django.core.exceptions import PermissionDenied from django.db.models import Q +from django.http import (Http404, HttpResponse, HttpResponseRedirect, + JsonResponse) +from django.shortcuts import get_object_or_404, redirect, render +from django.template import loader +from django.views.generic import TemplateView + from .forms import UploadMateriForm -from django.core.exceptions import PermissionDenied +from .models import Category, Materi class DaftarKatalog(TemplateView): @@ -19,37 +25,42 @@ class DaftarKatalog(TemplateView): context = self.get_context_data(**kwargs) context["kategori_list"] = Category.objects.all() context["materi_list"] = Materi.objects.all() - - getSearch = request.GET.get('search') + + getSearch = request.GET.get("search") if getSearch: - list = Materi.objects.all().filter( - Q(title__icontains = getSearch) | - Q(descriptions__icontains = getSearch) | - Q(author__icontains=getSearch) | - Q(uploader__icontains=getSearch) | - Q(publisher__icontains=getSearch) - ).distinct + list = ( + Materi.objects.all() + .filter( + Q(title__icontains=getSearch) + | Q(descriptions__icontains=getSearch) + | Q(author__icontains=getSearch) + | Q(uploader__icontains=getSearch) + | Q(publisher__icontains=getSearch) + ) + .distinct + ) context["materi_list"] = list getKategori = request.GET.get("kategori") if getKategori: pkGet = request.GET.get("kategori") - kategori = Category.objects.get(pk = pkGet) - list = Materi.objects.filter(categories = kategori.pk) + kategori = Category.objects.get(pk=pkGet) + list = Materi.objects.filter(categories=kategori.pk) context["materi_list"] = list - + getSort = request.GET.get("sort") - if getSort: + if getSort: list = Materi.objects.all() - if(getSort == "judul"): - context["materi_list"] = list.order_by('title') - elif(getSort == "penulis"): - context["materi_list"] = list.order_by('author') - elif(getSort == "pengunggah"): - context["materi_list"] = list.order_by('uploader') - + if getSort == "judul": + context["materi_list"] = list.order_by("title") + elif getSort == "penulis": + context["materi_list"] = list.order_by("author") + elif getSort == "pengunggah": + context["materi_list"] = list.order_by("uploader") + return self.render_to_response(context=context) + class DetailMateri(TemplateView): template_name = "app/detail_materi.html" @@ -58,60 +69,56 @@ class DetailMateri(TemplateView): def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) - context = super().get_context_data(**kwargs) - print(context['slug']) - context["materi_data"] = get_object_or_404(Materi, pk=kwargs['slug']) - print(context["materi_data"]) + context["materi_data"] = get_object_or_404(Materi, pk=kwargs["pk"]) return self.render_to_response(context=context) - - - -# class DetailMateri(TemplateView): -# template_name = "app/detail_materi.html" - -# def get_context_data(self, **kwargs): -# return super().get_context_data(**kwargs) - -# def get(self, request, *args, **kwargs): -# context = self.get_context_data(**kwargs) -# return self.render_to_response(context=context) - -def pages(request): - context = {} - # All resource paths end in .html. - # Pick out the html file name from the url. And load that template. - try: - - load_template = request.path.split('/')[-1] - template = loader.get_template(load_template) - return HttpResponse(template.render(context, request)) - - except: - - template = loader.get_template('error-404.html') - return HttpResponse(template.render(context, request)) +def download_materi(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): + 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) + return response + else: + raise Http404("File tidak dapat ditemukan.") + + +def view_materi(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): + mimetype = mimetypes.guess_type(file_path) + 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) + return response + else: + raise Http404("File tidak dapat ditemukan.") def dashboard(request): - return render(request, 'dashboard.html') + return render(request, "dashboard.html") def profile(request): - return render(request, 'profile.html') + return render(request, "profile.html") def unggah(request): - return render(request, 'unggah.html') + return render(request, "unggah.html") def file_text(request): - return render(request, 'file_text.html') + return render(request, "file_text.html") def file_video(request): - return render(request, 'file_video.html') + return render(request, "file_video.html") class UploadMateriView(TemplateView): @@ -125,20 +132,36 @@ class UploadMateriView(TemplateView): def post(self, request, *args, **kwargs): if request.user.is_authenticated == False: raise PermissionDenied(request) - form = UploadMateriForm(request.POST) + form = UploadMateriForm(request.POST, request.FILES) if form.is_valid(): materi = form.save(commit=False) materi.uploader = request.user materi.save() - return HttpResponseRedirect('unggah') + return HttpResponseRedirect("/unggah/") else: context = self.get_context_data(**kwargs) - context['form'] = form + context["form"] = form return self.render_to_response(context) def get(self, request, *args, **kwargs): if request.user.is_authenticated == False: raise PermissionDenied(request) context = self.get_context_data(**kwargs) - context['form'] = UploadMateriForm + context["form"] = UploadMateriForm return self.render_to_response(context) + + +def pages(request): + context = {} + # All resource paths end in .html. + # Pick out the html file name from the url. And load that template. + try: + + load_template = request.path.split("/")[-1] + template = loader.get_template(load_template) + return HttpResponse(template.render(context, request)) + + except: + + template = loader.get_template("error-404.html") + return HttpResponse(template.render(context, request)) diff --git a/authentication/apps.py b/authentication/apps.py index 9635c9df56ffcc63c2d2efd3b78ca12f61abe442..372ba8132bb6628785717dbe4c9b5d52c191ca7a 100644 --- a/authentication/apps.py +++ b/authentication/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class AuthenticationConfig(AppConfig): - name = 'authentication' + name = "authentication" diff --git a/authentication/fixtures/initial.json b/authentication/fixtures/initial.json new file mode 100644 index 0000000000000000000000000000000000000000..2fba261734effb1a010ab12ceb4dd6e66d7289f8 --- /dev/null +++ b/authentication/fixtures/initial.json @@ -0,0 +1,52 @@ +[ + { + "model": "authentication.user", + "pk": 1, + "fields": { + "password": "pbkdf2_sha256$180000$LNXsQvMhF33i$S/6QxFwpEKG7HzysqMIVCUL/EDHeT+vzbLWbkaKDdCI=", + "last_login": null, + "is_superuser": false, + "first_name": "", + "last_name": "", + "is_staff": false, + "is_active": true, + "date_joined": "2020-04-06T19:07:20.546Z", + "email": "admin@gov.id", + "username": "", + "name": "Admin", + "is_admin": true, + "is_contributor": false, + "instansi": "", + "nik": "", + "alamat": "", + "nomor_telpon": "", + "groups": [], + "user_permissions": [] + } + }, + { + "model": "authentication.user", + "pk": 2, + "fields": { + "password": "pbkdf2_sha256$180000$rbJ4Ui6aUbbk$lX+WdeZhGHBCvd2wbfY/t9synOobgwQmrWtGphbA18Q=", + "last_login": null, + "is_superuser": false, + "first_name": "", + "last_name": "", + "is_staff": false, + "is_active": true, + "date_joined": "2020-04-06T19:07:20.546Z", + "email": "kontributor@gov.id", + "username": "", + "name": "Kontributor", + "is_admin": false, + "is_contributor": true, + "instansi": "", + "nik": "", + "alamat": "", + "nomor_telpon": "", + "groups": [], + "user_permissions": [] + } + } +] diff --git a/authentication/migrations/0001_initial.py b/authentication/migrations/0001_initial.py index d9df9e95ffd10b4fc8153ad1ebdad7e7491b7b2e..3e8e01d7e306a354b3dc6acb535af664f6fdf811 100644 --- a/authentication/migrations/0001_initial.py +++ b/authentication/migrations/0001_initial.py @@ -1,9 +1,9 @@ -# Generated by Django 3.0.3 on 2020-04-06 18:50 +# Generated by Django 3.0.3 on 2020-04-24 17:06 -import authentication.models -import django.contrib.auth.validators -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models + +import authentication.models class Migration(migrations.Migration): @@ -11,41 +11,76 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0011_update_proxy_permissions'), + ("auth", "0011_update_proxy_permissions"), ] operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('name', models.CharField(max_length=150)), - ('is_admin', models.BooleanField(default=False)), - ('is_contributor', models.BooleanField(default=False)), - ('instansi', models.CharField(max_length=240)), - ('nik', models.CharField(max_length=240)), - ('alamat', models.CharField(max_length=240)), - ('nomor_telpon', models.CharField(max_length=240)), - ('email', models.EmailField(max_length=254, unique=True)), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, - }, - managers=[ - ('objects', authentication.models.CustomUserManager()), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("password", models.CharField(max_length=128, verbose_name="password")), + ("last_login", models.DateTimeField(blank=True, null=True, verbose_name="last login")), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ("first_name", models.CharField(blank=True, max_length=30, verbose_name="first name")), + ("last_name", models.CharField(blank=True, max_length=150, verbose_name="last name")), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ("date_joined", models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined")), + ("email", models.EmailField(max_length=254, unique=True)), + ("username", models.CharField(blank=True, default="", max_length=150)), + ("name", models.CharField(max_length=150)), + ("is_admin", models.BooleanField(default=False)), + ("is_contributor", models.BooleanField(default=False)), + ("instansi", models.CharField(max_length=240)), + ("nik", models.CharField(max_length=240)), + ("alamat", models.CharField(max_length=240)), + ("nomor_telpon", models.CharField(max_length=240)), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.Group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.Permission", + verbose_name="user permissions", + ), + ), ], + options={"verbose_name": "user", "verbose_name_plural": "users", "abstract": False,}, + managers=[("objects", authentication.models.CustomUserManager()),], ), ] diff --git a/authentication/migrations/0002_auto_20200407_0218.py b/authentication/migrations/0002_auto_20200407_0218.py deleted file mode 100644 index 6dece8a2fab7f5512b16c879cb31d326b7931c84..0000000000000000000000000000000000000000 --- a/authentication/migrations/0002_auto_20200407_0218.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.0.3 on 2020-04-06 19:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='username', - field=models.CharField(blank=True, default='', max_length=150), - ), - ] diff --git a/authentication/models.py b/authentication/models.py index 0f29ddab1749bdfcc2349b75223b44fce5f65c11..16c77e70827733222da905354960d71aa9976474 100644 --- a/authentication/models.py +++ b/authentication/models.py @@ -1,5 +1,6 @@ -from django.db import models from django.contrib.auth.models import AbstractUser, UserManager +from django.db import models + class CustomUserManager(UserManager): use_in_migrations = True @@ -9,7 +10,7 @@ class CustomUserManager(UserManager): Create and save a user with the given email, and password. """ if not email: - raise ValueError('The given email must be set') + raise ValueError("The given email must be set") email = self.normalize_email(email) user = self.model(email=email, **extra_fields) user.set_password(password) @@ -17,52 +18,53 @@ class CustomUserManager(UserManager): return user def create_user(self, email=None, password=None, **extra_fields): - extra_fields.setdefault('is_staff', False) - extra_fields.setdefault('is_superuser', False) + extra_fields.setdefault("is_staff", False) + extra_fields.setdefault("is_superuser", False) return self._create_user(email, password, **extra_fields) - + def create_contributor(self, email=None, password=None, **extra_fields): - extra_fields.setdefault('is_contributor', True) - extra_fields.setdefault('is_admin', False) + extra_fields.setdefault("is_contributor", True) + extra_fields.setdefault("is_admin", False) - if extra_fields.get('is_contributor') is not True: - raise ValueError('Contributor must have is_contributor=True.') - if extra_fields.get('is_admin') is not False: - raise ValueError('contributor must have is_admin=False.') + if extra_fields.get("is_contributor") is not True: + raise ValueError("Contributor must have is_contributor=True.") + if extra_fields.get("is_admin") is not False: + raise ValueError("contributor must have is_admin=False.") return self._create_user(email, password, **extra_fields) def create_admin(self, email=None, password=None, **extra_fields): - extra_fields.setdefault('is_admin', True) - extra_fields.setdefault('is_contributor', False) + extra_fields.setdefault("is_admin", True) + extra_fields.setdefault("is_contributor", False) - if extra_fields.get('is_admin') is not True: - raise ValueError('admin must have is_admin=True.') - if extra_fields.get('is_contributor') is not False: - raise ValueError('admin must have is_contributor=False.') + if extra_fields.get("is_admin") is not True: + raise ValueError("admin must have is_admin=True.") + if extra_fields.get("is_contributor") is not False: + raise ValueError("admin must have is_contributor=False.") return self._create_user(email, password, **extra_fields) def create_superuser(self, email=None, password=None, **extra_fields): - extra_fields.setdefault('is_staff', True) - extra_fields.setdefault('is_superuser', True) - extra_fields.setdefault('is_admin', True) - extra_fields.setdefault('is_contributor', True) - - if extra_fields.get('is_staff') is not True: - raise ValueError('Superuser must have is_staff=True.') - if extra_fields.get('is_superuser') is not True: - raise ValueError('Superuser must have is_superuser=True.') - if extra_fields.get('is_admin') is not True: - raise ValueError('Superuser must have is_admin=True.') - if extra_fields.get('is_contributor') is not True: - raise ValueError('Superuser must have is_contributor=True.') + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + extra_fields.setdefault("is_admin", True) + extra_fields.setdefault("is_contributor", True) + + if extra_fields.get("is_staff") is not True: + raise ValueError("Superuser must have is_staff=True.") + if extra_fields.get("is_superuser") is not True: + raise ValueError("Superuser must have is_superuser=True.") + if extra_fields.get("is_admin") is not True: + raise ValueError("Superuser must have is_admin=True.") + if extra_fields.get("is_contributor") is not True: + raise ValueError("Superuser must have is_contributor=True.") return self._create_user(email, password, **extra_fields) + class User(AbstractUser): email = models.EmailField(unique=True) - username = models.CharField(unique=False, blank=True, default='', max_length=150) + username = models.CharField(unique=False, blank=True, default="", max_length=150) name = models.CharField(max_length=150) is_admin = models.BooleanField(blank=False, default=False) is_contributor = models.BooleanField(blank=False, default=False) @@ -73,6 +75,6 @@ class User(AbstractUser): objects = CustomUserManager() - EMAIL_FIELD = 'email' - USERNAME_FIELD = 'email' + EMAIL_FIELD = "email" + USERNAME_FIELD = "email" REQUIRED_FIELDS = [] diff --git a/authentication/tests.py b/authentication/tests.py index 484663e49ed4cc03ad186eac46a1ab91b6501cfd..99c8dfb3f65442c4c3238ad59538168e93d425e8 100644 --- a/authentication/tests.py +++ b/authentication/tests.py @@ -1,25 +1,27 @@ -from django.test import TestCase, Client, RequestFactory +from django.test import Client, RequestFactory, TestCase from django.urls import resolve -from authentication.views import Login + from authentication.models import User +from authentication.views import Login + class LoginPageContributorTest(TestCase): def setUp(self): User.objects._create_user(email="alice@acme.com", password="acmecorp", is_contributor=True) def test_login_contributor_using_login_func(self): - found = resolve('/login/') + found = resolve("/login/") self.assertEqual(found.func.__name__, Login.as_view().__name__) def test_login_contributor_url_is_exist(self): # Positive tests - response = Client().get('/login/') + response = Client().get("/login/") self.assertEqual(response.status_code, 200) # Negative tests - response = Client().get('/fake/') + response = Client().get("/fake/") self.assertEqual(response.status_code, 404) - + def test_login_contributor_template(self): url = "/login/" response = Client().get(url) @@ -27,106 +29,107 @@ class LoginPageContributorTest(TestCase): self.assertTemplateUsed(response, expected_template_name) def test_register_title(self): - response = Client().get('/login/') + response = Client().get("/login/") # Positive tests - self.assertContains(response, 'Login Kontributor') + self.assertContains(response, "Login Kontributor") # Negative tests - self.assertNotContains(response, 'Fake Title') + self.assertNotContains(response, "Fake Title") def test_register_form_field(self): - response = Client().get('/login/') + response = Client().get("/login/") # Positive tests - self.assertContains(response, 'Email') - self.assertContains(response, 'Kata Sandi') + self.assertContains(response, "Email") + self.assertContains(response, "Kata Sandi") # Negative tests - self.assertNotContains(response, 'Jenis Kelamin') + self.assertNotContains(response, "Jenis Kelamin") def test_user_login_missing_email_or_password(self): - response = Client().post('/login/', {'email': 'alice@acme.com'}) - self.assertIn('error_message', response.context_data) - self.assertIn("Email atau Password anda kosong.", - response.context_data['error_message']) - response = Client().post('/login/', {'pass': 'acmecorp'}) - self.assertIn('error_message', response.context_data) - self.assertIn("Email atau Password anda kosong.", - response.context_data['error_message']) + response = Client().post("/login/", {"email": "alice@acme.com"}) + self.assertIn("error_message", response.context_data) + self.assertIn("Email atau Password anda kosong.", response.context_data["error_message"]) + response = Client().post("/login/", {"pass": "acmecorp"}) + self.assertIn("error_message", response.context_data) + self.assertIn("Email atau Password anda kosong.", response.context_data["error_message"]) def test_user_login_wrong_email_or_password(self): # Wrong password - response = Client().post( - '/login/', {'email': 'alice@acme.com', 'pass': 'acmeindustry'}) - self.assertIn('error_message', response.context_data) - self.assertIn("Email atau Password anda salah.", - response.context_data['error_message']) + response = Client().post("/login/", {"email": "alice@acme.com", "pass": "acmeindustry"}) + self.assertIn("error_message", response.context_data) + self.assertIn("Email atau Password anda salah.", response.context_data["error_message"]) # Wrong email - response = Client().post( - '/login/', {'email': 'alice@acme.co.id', 'pass': 'acmecorp'}) - self.assertIn('error_message', response.context_data) - self.assertIn("Email atau Password anda salah.", - response.context_data['error_message']) + response = Client().post("/login/", {"email": "alice@acme.co.id", "pass": "acmecorp"}) + self.assertIn("error_message", response.context_data) + self.assertIn("Email atau Password anda salah.", response.context_data["error_message"]) # Wrong email and password - response = Client().post( - '/login/', {'email': 'alice@acme.co.id', 'pass': 'acmeindustry'}) - self.assertIn('error_message', response.context_data) - self.assertIn("Email atau Password anda salah.", - response.context_data['error_message']) + response = Client().post("/login/", {"email": "alice@acme.co.id", "pass": "acmeindustry"}) + self.assertIn("error_message", response.context_data) + self.assertIn("Email atau Password anda salah.", response.context_data["error_message"]) def test_user_login(self): # 302 meaning successful login and redirected - response = Client().post( - '/login/', {'email': 'alice@acme.com', 'pass': 'acmecorp'}) + response = Client().post("/login/", {"email": "alice@acme.com", "pass": "acmecorp"}) self.assertEqual(302, response.status_code) class UserModelTest(TestCase): def test_create_user(self): - self.assertRaises(ValueError, User.objects.create_user, ) - user = User.objects.create_user( - email="alice@acme.com", password="acmecorp").save() + self.assertRaises( + ValueError, User.objects.create_user, + ) + user = User.objects.create_user(email="alice@acme.com", password="acmecorp").save() self.assertEqual(User.objects.all().count(), 1) def test_create_contributor(self): - self.assertRaises(ValueError, User.objects.create_contributor, - email="bob@acme.com", password="acmecorp", is_admin=True) - self.assertRaises(ValueError, User.objects.create_contributor, - email="bob@acme.com", password="acmecorp", is_contributor=False) - - User.objects.create_contributor( - email="bob@acme.com", password="acmecorp").save() + self.assertRaises( + ValueError, User.objects.create_contributor, email="bob@acme.com", password="acmecorp", is_admin=True + ) + self.assertRaises( + ValueError, + User.objects.create_contributor, + email="bob@acme.com", + password="acmecorp", + is_contributor=False, + ) + + User.objects.create_contributor(email="bob@acme.com", password="acmecorp").save() kontributor = User.objects.get(email="bob@acme.com") self.assertTrue(kontributor.is_contributor) self.assertFalse(kontributor.is_admin) self.assertFalse(kontributor.is_superuser) def test_create_admin(self): - self.assertRaises(ValueError, User.objects.create_admin, - email="carol@acme.com", password="acmecorp", is_contributor=True) - self.assertRaises(ValueError, User.objects.create_admin, - email="carol@acme.com", password="acmecorp", is_admin=False) - - User.objects.create_admin( - email="carol@acme.com", password="acmecorp").save() + self.assertRaises( + ValueError, User.objects.create_admin, email="carol@acme.com", password="acmecorp", is_contributor=True + ) + self.assertRaises( + ValueError, User.objects.create_admin, email="carol@acme.com", password="acmecorp", is_admin=False + ) + + User.objects.create_admin(email="carol@acme.com", password="acmecorp").save() admin = User.objects.get(email="carol@acme.com") self.assertTrue(admin.is_admin) self.assertFalse(admin.is_contributor) self.assertFalse(admin.is_superuser) def test_create_superuser(self): - self.assertRaises(ValueError, User.objects.create_superuser, - email="dan@acme.com", password="acmecorp", is_contributor=False) - self.assertRaises(ValueError, User.objects.create_superuser, - email="dan@acme.com", password="acmecorp", is_admin=False) - self.assertRaises(ValueError, User.objects.create_superuser, - email="dan@acme.com", password="acmecorp", is_staff=False) - self.assertRaises(ValueError, User.objects.create_superuser, - email="dan@acme.com", password="acmecorp", is_superuser=False) - - User.objects.create_superuser( - email="dan@acme.com", password="acmecorp").save() + self.assertRaises( + ValueError, User.objects.create_superuser, email="dan@acme.com", password="acmecorp", is_contributor=False + ) + self.assertRaises( + ValueError, User.objects.create_superuser, email="dan@acme.com", password="acmecorp", is_admin=False + ) + self.assertRaises( + ValueError, User.objects.create_superuser, email="dan@acme.com", password="acmecorp", is_staff=False + ) + self.assertRaises( + ValueError, User.objects.create_superuser, email="dan@acme.com", password="acmecorp", is_superuser=False + ) + + User.objects.create_superuser(email="dan@acme.com", password="acmecorp").save() superuser = User.objects.get(email="dan@acme.com") self.assertTrue(superuser.is_admin) self.assertTrue(superuser.is_contributor) @@ -139,7 +142,7 @@ class LoginPageAdminTest(TestCase): User.objects._create_user(email="alice@acme.com", password="acmecorp", is_admin=True) def test_login_admin_using_login_func(self): - found = resolve('/login_admin/') + found = resolve("/login_admin/") self.assertEqual(found.func.__name__, Login.as_view().__name__) def test_login_admin_template(self): @@ -147,59 +150,48 @@ class LoginPageAdminTest(TestCase): response = Client().get(url) expected_template_name = "login_admin.html" self.assertTemplateUsed(response, expected_template_name) - + def test_login_admin_url_is_exist(self): # Positive tests - response = Client().get('/login_admin/') + response = Client().get("/login_admin/") self.assertEqual(response.status_code, 200) - + def test_register_title(self): - response = Client().get('/login_admin/') + response = Client().get("/login_admin/") # Positive tests - self.assertContains(response, 'Halo, Admin') + self.assertContains(response, "Halo, Admin") def test_login_admin_form_field(self): - response = Client().get('/login_admin/') + response = Client().get("/login_admin/") # Positive tests - self.assertContains(response, 'Email') - self.assertContains(response, 'Kata Sandi') + self.assertContains(response, "Email") + self.assertContains(response, "Kata Sandi") def test_user_login_missing_email_or_password(self): - response = Client().post('/login_admin/', {'email': 'alice@acme.com'}) - self.assertIn('error_message', response.context_data) - self.assertIn("Email atau Password anda kosong.", - response.context_data['error_message']) - response = Client().post('/login_admin/', {'pass': 'acmecorp'}) - self.assertIn('error_message', response.context_data) - self.assertIn("Email atau Password anda kosong.", - response.context_data['error_message']) + response = Client().post("/login_admin/", {"email": "alice@acme.com"}) + self.assertIn("error_message", response.context_data) + self.assertIn("Email atau Password anda kosong.", response.context_data["error_message"]) + response = Client().post("/login_admin/", {"pass": "acmecorp"}) + self.assertIn("error_message", response.context_data) + self.assertIn("Email atau Password anda kosong.", response.context_data["error_message"]) def test_user_login_wrong_email_or_password(self): # Wrong password - response = Client().post( - '/login_admin/', {'email': 'alice@acme.com', 'pass': 'acmeindustry'}) - self.assertIn('error_message', response.context_data) - self.assertIn("Email atau Password anda salah.", - response.context_data['error_message']) + response = Client().post("/login_admin/", {"email": "alice@acme.com", "pass": "acmeindustry"}) + self.assertIn("error_message", response.context_data) + self.assertIn("Email atau Password anda salah.", response.context_data["error_message"]) # Wrong email - response = Client().post( - '/login_admin/', {'email': 'alice@acme.co.id', 'pass': 'acmecorp'}) - self.assertIn('error_message', response.context_data) - self.assertIn("Email atau Password anda salah.", - response.context_data['error_message']) + response = Client().post("/login_admin/", {"email": "alice@acme.co.id", "pass": "acmecorp"}) + self.assertIn("error_message", response.context_data) + self.assertIn("Email atau Password anda salah.", response.context_data["error_message"]) # Wrong email and password - response = Client().post( - '/login_admin/', {'email': 'alice@acme.co.id', 'pass': 'acmeindustry'}) - self.assertIn('error_message', response.context_data) - self.assertIn("Email atau Password anda salah.", - response.context_data['error_message']) + response = Client().post("/login_admin/", {"email": "alice@acme.co.id", "pass": "acmeindustry"}) + self.assertIn("error_message", response.context_data) + self.assertIn("Email atau Password anda salah.", response.context_data["error_message"]) def test_user_login(self): # 302 meaning successful login and redirected - response = Client().post( - '/login_admin/', {'email': 'alice@acme.com', 'pass': 'acmecorp'}) + response = Client().post("/login_admin/", {"email": "alice@acme.com", "pass": "acmecorp"}) self.assertEqual(302, response.status_code) - - \ No newline at end of file diff --git a/authentication/urls.py b/authentication/urls.py index 6510358ff91953d94950fb2196a7658d4a09e493..7fed78f667f44978e79433855a096184800c66da 100644 --- a/authentication/urls.py +++ b/authentication/urls.py @@ -1,9 +1,10 @@ +from django.contrib.auth.views import LogoutView from django.urls import path + from authentication.views import Login -from django.contrib.auth.views import LogoutView urlpatterns = [ - path('login/', Login.as_view(), name='login_admin'), - path('login_admin/', Login.as_view(), name='login_admin'), - path('logout/', LogoutView.as_view()), -] \ No newline at end of file + path("login/", Login.as_view(), name="login_admin"), + path("login_admin/", Login.as_view(), name="login_admin"), + path("logout/", LogoutView.as_view()), +] diff --git a/authentication/views.py b/authentication/views.py index a0189ebab1b8c683d775f772ba57d7af0409b6df..7cf0d241d5b1698dd106f48340e66856e5c115e8 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -1,17 +1,16 @@ -from django.shortcuts import render -from django.http import HttpResponseRedirect -from django.views.generic import TemplateView from django.contrib.auth import authenticate, login from django.contrib.auth.views import LogoutView +from django.http import HttpResponseRedirect +from django.shortcuts import render +from django.views.generic import TemplateView class Login(TemplateView): - def get_template_names(self): - if self.request.path == '/login_admin/': - template_name = 'login_admin.html' + if self.request.path == "/login_admin/": + template_name = "login_admin.html" else: - template_name = 'login.html' + template_name = "login.html" return template_name def get_context_data(self, *args, **kwargs): diff --git a/digipus/__pycache__/settings.cpython-36.pyc b/digipus/__pycache__/settings.cpython-36.pyc deleted file mode 100644 index 83daf93b80b720245a94f579178c88b176dbcaee..0000000000000000000000000000000000000000 Binary files a/digipus/__pycache__/settings.cpython-36.pyc and /dev/null differ diff --git a/digipus/asgi.py b/digipus/asgi.py index f32cce5c503fa756c900b3cd3ebc3140957471e6..a5dce11f57f7f1f5b1ad0fc938a48942b65bec4e 100644 --- a/digipus/asgi.py +++ b/digipus/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'digipus.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "digipus.settings") application = get_asgi_application() diff --git a/digipus/settings.py b/digipus/settings.py index 84192b828f17cbf068b375390765b06e4a8a7a4e..77d87378805405915423a70b2970f46892561d8b 100644 --- a/digipus/settings.py +++ b/digipus/settings.py @@ -10,10 +10,11 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.0/ref/settings/ """ -from decouple import config +import os + import dj_database_url import django_heroku -import os +from decouple import config # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -23,76 +24,76 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = config('SECRET_KEY') +SECRET_KEY = config("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = config('DEBUG', cast=bool) +DEBUG = config("DEBUG", cast=bool) -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["*"] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'authentication.apps.AuthenticationConfig', - 'app.apps.AppConfig', - 'register.apps.RegisterConfig', - 'administration.apps.AdministrationConfig' + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "authentication.apps.AuthenticationConfig", + "app.apps.AppConfig", + "register.apps.RegisterConfig", + "administration.apps.AdministrationConfig", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'whitenoise.middleware.WhiteNoiseMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'whitenoise.middleware.WhiteNoiseMiddleware', + "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", ] -ROOT_URLCONF = 'digipus.urls' +ROOT_URLCONF = "digipus.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'templates')], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'digipus.wsgi.application' +WSGI_APPLICATION = "digipus.wsgi.application" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases -is_local = config('IS_LOCAL', cast=bool, default=False) -is_heroku = config('IS_HEROKU', cast=bool, default=False) +is_local = config("IS_LOCAL", cast=bool, default=False) +is_heroku = config("IS_HEROKU", cast=bool, default=False) if is_local: DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': config('DB_NAME'), - 'USER': config('DB_USER'), - 'PASSWORD': config('DB_PASSWORD'), - 'HOST': config('DB_HOST'), - 'PORT': '', + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": config("DB_NAME"), + "USER": config("DB_USER"), + "PASSWORD": config("DB_PASSWORD"), + "HOST": config("DB_HOST"), + "PORT": "", } } elif is_heroku: @@ -100,44 +101,29 @@ elif is_heroku: database_url = os.getenv("DATABASE_URL") DATABASES["default"] = dj_database_url.parse(database_url, conn_max_age=600) else: - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } -} + DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(BASE_DIR, "db.sqlite3"),}} -STATICFILES_DIRS = ( - os.path.join(BASE_DIR, 'staticfiles'), -) +STATICFILES_DIRS = (os.path.join(BASE_DIR, "staticfiles"),) # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators -AUTH_USER_MODEL = 'authentication.User' +AUTH_USER_MODEL = "authentication.User" AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, + {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",}, + {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",}, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, ] # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'Asia/Jakarta' +TIME_ZONE = "Asia/Jakarta" USE_I18N = True @@ -149,13 +135,16 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ -STATIC_ROOT = os.path.join(BASE_DIR, 'static') +STATIC_ROOT = os.path.join(BASE_DIR, "static") -STATIC_URL = '/static/' +STATIC_URL = "/static/" STATIC_DIRS = [ - os.path.join(BASE_DIR, 'static'), + os.path.join(BASE_DIR, "static"), ] -STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" + +MEDIA_ROOT = os.path.join(BASE_DIR, "media") +MEDIA_URL = "/media/" LOGOUT_REDIRECT_URL = "/" diff --git a/digipus/urls.py b/digipus/urls.py index 5a07adc53ba04bcde28116c99345ef522d63c346..263c49e6909847f597bf7dfe6dbea237d9295f34 100644 --- a/digipus/urls.py +++ b/digipus/urls.py @@ -13,13 +13,15 @@ Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ +from django.conf import settings +from django.conf.urls.static import static from django.contrib import admin -from django.urls import path, include +from django.urls import include, path urlpatterns = [ - path('admin/', admin.site.urls), - path('registrasi/', include('register.urls')), - path('', include("authentication.urls"), name='auth'), - path('', include("app.urls"), name='app'), - path('administration/', include('administration.urls')) -] + path("admin/", admin.site.urls), + path("registrasi/", include("register.urls")), + path("", include("authentication.urls"), name="auth"), + path("", include("app.urls"), name="app"), + path("administration/", include("administration.urls")), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/digipus/wsgi.py b/digipus/wsgi.py index 15a3819d08b5d2821e3d735ec33e85f443f09982..931c7a972044f7e9dbc49620b65efa6e516908f6 100644 --- a/digipus/wsgi.py +++ b/digipus/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'digipus.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "digipus.settings") application = get_wsgi_application() diff --git a/manage.py b/manage.py index c62e08d5ec157081241ade2c6b5ab4fe6ce280c6..ee19079d85b86ef5efc2773a3a0ef48d504f81af 100644 --- a/manage.py +++ b/manage.py @@ -5,7 +5,7 @@ import sys def main(): - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'digipus.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "digipus.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -17,5 +17,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..a70904f11f778a303dac2180fec540896169b88b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.black] +line-length = 119 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' \ No newline at end of file diff --git a/register/apps.py b/register/apps.py index 0e94c46ca879b004f5ee7daa516423f1d384e16a..f50ec16c9b50f369d4ab72b6a99781396964dd17 100644 --- a/register/apps.py +++ b/register/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class RegisterConfig(AppConfig): - name = 'register' + name = "register" diff --git a/register/forms.py b/register/forms.py index 20d04fb41bcb898c511ae01c073eb54f5b4d02f5..4c65079be1307414aee1a41fc839ae232eac2371 100644 --- a/register/forms.py +++ b/register/forms.py @@ -1,4 +1,5 @@ from django import forms + from authentication.models import User @@ -8,29 +9,28 @@ class UserForm(forms.ModelForm): class Meta: model = User - fields = ['name', 'instansi', 'nik', 'alamat', 'email', 'nomor_telpon'] + fields = ["name", "instansi", "nik", "alamat", "email", "nomor_telpon"] def __init__(self, *args, **kwargs): super(UserForm, self).__init__(*args, **kwargs) for field_name, field in self.fields.items(): - field.widget.attrs['class'] = 'input100' - if(field_name == "password1" or field_name == "password2"): - field.widget.attrs['type'] = 'password' + field.widget.attrs["class"] = "input100" + if field_name == "password1" or field_name == "password2": + field.widget.attrs["type"] = "password" - self.fields['name'].required = True - self.fields['password'].required = True - self.fields['password2'].required = True + self.fields["name"].required = True + self.fields["password"].required = True + self.fields["password2"].required = True def clean_password(self): - password = self.data.get('password') - password2 = self.data.get('password2') + password = self.data.get("password") + password2 = self.data.get("password2") if password != password2: - raise forms.ValidationError('Password tidak sama') + raise forms.ValidationError("Password tidak sama") return password def clean_email(self): email = self.cleaned_data.get("email") - if not User.objects.filter( - email=self.cleaned_data.get("email")).exists(): + if not User.objects.filter(email=self.cleaned_data.get("email")).exists(): return self.cleaned_data.get("email") - raise forms.ValidationError('Email sudah digunakan untuk mendaftar akun.') + raise forms.ValidationError("Email sudah digunakan untuk mendaftar akun.") diff --git a/register/migrations/__init__.py b/register/migrations/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/register/tests.py b/register/tests.py index 9d2339669afb1b4095d3b0c2908765c24c9758e6..d0dd34242d85448bdcf305e2822f25aef844a7fb 100644 --- a/register/tests.py +++ b/register/tests.py @@ -1,7 +1,9 @@ -from django.test import TestCase, Client +from django.test import Client, TestCase from django.urls import resolve -from register import views + from authentication.models import User +from register import views + # Create your tests here. class RegisterPageTest(TestCase): @@ -10,59 +12,107 @@ class RegisterPageTest(TestCase): def test_register_url_is_exist(self): # Positive tests - response = Client().get('/registrasi/') + response = Client().get("/registrasi/") self.assertEqual(response.status_code, 200) # Negative tests - response = Client().get('/fake/') + response = Client().get("/fake/") self.assertEqual(response.status_code, 404) - + def test_status_using_index_func(self): # Positive tests - found = resolve('/registrasi/') + found = resolve("/registrasi/") self.assertEqual(found.func.__name__, views.index.__name__) # Negative tests - found = resolve('/admin/') + found = resolve("/admin/") self.assertNotEqual(found.func, views.index) - + def test_register_title(self): - response = Client().get('/registrasi/') + response = Client().get("/registrasi/") # Positive tests - self.assertContains(response, 'Registrasi Kontributor') + self.assertContains(response, "Registrasi Kontributor") # Negative tests - self.assertNotContains(response, 'Fake Title') + self.assertNotContains(response, "Fake Title") def test_register_form_field(self): - response = Client().get('/registrasi/') + response = Client().get("/registrasi/") # Positive tests - self.assertContains(response, 'Nama') - self.assertContains(response, 'Instansi/Pekerjaan') - self.assertContains(response, 'NIK') - self.assertContains(response, 'Alamat') - self.assertContains(response, 'Email') - self.assertContains(response, 'Nomor Telepon') - self.assertContains(response, 'Kata Sandi') - self.assertContains(response, 'Ketik Ulang Kata Sandi') - + self.assertContains(response, "Nama") + self.assertContains(response, "Instansi/Pekerjaan") + self.assertContains(response, "NIK") + self.assertContains(response, "Alamat") + self.assertContains(response, "Email") + self.assertContains(response, "Nomor Telepon") + self.assertContains(response, "Kata Sandi") + self.assertContains(response, "Ketik Ulang Kata Sandi") + # Negative tests - self.assertNotContains(response, 'Jenis Kelamin') + self.assertNotContains(response, "Jenis Kelamin") def test_create_user(self): - response = self.client.post('/registrasi/', {'name': 'bob', 'instansi': 'university', 'nik': '1706074940', 'alamat': 'bekasi', 'email': 'bob@company.com', 'nomor_telpon': '087878726602', 'password': '1234', 'password2': '1234'}) + response = self.client.post( + "/registrasi/", + { + "name": "bob", + "instansi": "university", + "nik": "1706074940", + "alamat": "bekasi", + "email": "bob@company.com", + "nomor_telpon": "087878726602", + "password": "1234", + "password2": "1234", + }, + ) self.assertEqual(User.objects.all().count(), 1) def test_create_user_wrong_password_combination(self): - response = self.client.post('/registrasi/', {'name': 'bob', 'instansi': 'university', 'nik': '1706074940', 'alamat': 'bekasi', 'email': 'bob@company.com', 'nomor_telpon': '087878726602', 'password': '1234', 'password2': '12345'}) + response = self.client.post( + "/registrasi/", + { + "name": "bob", + "instansi": "university", + "nik": "1706074940", + "alamat": "bekasi", + "email": "bob@company.com", + "nomor_telpon": "087878726602", + "password": "1234", + "password2": "12345", + }, + ) self.assertEqual(User.objects.all().count(), 0) self.assertIn(b"Password tidak sama", response.content) def test_create_user_with_existing_email(self): - response = self.client.post('/registrasi/', {'name': 'bob', 'instansi': 'university', 'nik': '1706074940', 'alamat': 'bekasi', 'email': 'bob@company.com', 'nomor_telpon': '087878726602', 'password': '1234', 'password2': '1234'}) + response = self.client.post( + "/registrasi/", + { + "name": "bob", + "instansi": "university", + "nik": "1706074940", + "alamat": "bekasi", + "email": "bob@company.com", + "nomor_telpon": "087878726602", + "password": "1234", + "password2": "1234", + }, + ) self.assertEqual(User.objects.all().count(), 1) - response = self.client.post('/registrasi/', {'name': 'bob', 'instansi': 'university', 'nik': '1706074940', 'alamat': 'bekasi', 'email': 'bob@company.com', 'nomor_telpon': '087878726602', 'password': '1234', 'password2': '1234'}) + response = self.client.post( + "/registrasi/", + { + "name": "bob", + "instansi": "university", + "nik": "1706074940", + "alamat": "bekasi", + "email": "bob@company.com", + "nomor_telpon": "087878726602", + "password": "1234", + "password2": "1234", + }, + ) self.assertEqual(User.objects.all().count(), 1) self.assertIn(b"Email sudah digunakan untuk mendaftar akun.", response.content) diff --git a/register/urls.py b/register/urls.py index b51eb416df4ebef5b7687c1b731d6348f9b89292..4ebbedbcf0f2667d4b6eadc2be92363032c63963 100644 --- a/register/urls.py +++ b/register/urls.py @@ -1,8 +1,7 @@ from django.urls import path + from . import views -app_name = 'register' +app_name = "register" -urlpatterns = [ - path('', views.index.as_view()) -] \ No newline at end of file +urlpatterns = [path("", views.index.as_view())] diff --git a/register/views.py b/register/views.py index 1bb84262a6b5e4b25860724cae98e6421a03dcd1..d413b391c2ea59f9ee677262d1e9b188119b8e06 100644 --- a/register/views.py +++ b/register/views.py @@ -1,8 +1,9 @@ +from django.contrib.auth.hashers import make_password +from django.http import HttpResponseRedirect from django.shortcuts import render from django.views.generic import TemplateView + from register.forms import UserForm -from django.http import HttpResponseRedirect -from django.contrib.auth.hashers import make_password # Create your views here. @@ -19,13 +20,13 @@ class index(TemplateView): form = UserForm(request.POST) if form.is_valid(): new_user = form.save(commit=False) - new_user.password = make_password(data['password']) + new_user.password = make_password(data["password"]) new_user.is_contributor = True new_user.save() - return HttpResponseRedirect('/') + return HttpResponseRedirect("/") else: context = self.get_context_data(**kwargs) - context['form'] = form + context["form"] = form return self.render_to_response(context) def get(self, request, *args, **kwargs): diff --git a/requirements.txt b/requirements.txt index 942eabec0a47e317f48ecbc0f1044ed3815f7ebc..ed8c580002ebb4a7e2de1f3cbe26f91ad4f12b29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,35 +1,52 @@ -asgiref==3.2.5 +appdirs==1.4.3 +asgiref==3.2.7 astroid==2.3.3 atomicwrites==1.3.0 attrs==19.3.0 autopep8==1.5 +black==19.10b0 +cfgv==3.1.0 +click==7.1.1 colorama==0.4.3 coverage==5.0.4 +distlib==0.3.0 dj-database-url==0.5.0 Django==3.0.3 django-heroku==0.3.1 +filelock==3.0.12 gunicorn==20.0.4 +identify==1.4.15 importlib-metadata==1.5.2 +importlib-resources==1.4.0 isort==4.3.21 lazy-object-proxy==1.4.3 mccabe==0.6.1 more-itertools==8.2.0 +nodeenv==1.3.5 packaging==20.3 +pathspec==0.8.0 Pillow==7.1.1 pluggy==0.13.1 +pre-commit==2.3.0 psycopg2==2.8.4 py==1.8.1 pycodestyle==2.5.0 pylint==2.4.4 +pylint-django==2.0.15 +pylint-plugin-utils==0.6 pyparsing==2.4.6 +PySocks==1.7.1 pytest==5.4.1 python-decouple==3.3 pytz==2019.3 +PyYAML==5.3.1 +regex==2020.4.4 six==1.14.0 sqlparse==0.3.1 +toml==0.10.0 typed-ast==1.4.1 +virtualenv==20.0.18 wcwidth==0.1.9 whitenoise==5.0.1 wrapt==1.11.2 -zipp==3.1.0 -Pillow==7.1.1 \ No newline at end of file +zipp==3.1.0 \ No newline at end of file