diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2671b2e8dbf968a875261a07f7a10750e89fbc0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,138 @@ +# Django +*.log +*.pot +*.pyc +__pycache__ +db.sqlite3 +media + +# Backup files +*.bak + +# If you are using PyCharm +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml +.DS_Store + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# File-based project format +*.iws + +# IntelliJ +out/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Python +*.py[cod] +*$py.class + +# Distribution / packaging +.Python build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache +*.sublime-workspace +*.sublime-project + +# sftp configuration file +sftp-config.json + +# Package control specific files Package +Control.last-run +Control.ca-list +Control.ca-bundle +Control.system-ca-bundle +GitHub.sublime-settings + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history \ No newline at end of file diff --git a/README.md b/README.md index 33ed86c7fd0200a834dea72101547718fbf668f9..aafb4410424e30c1b1cb08f364a9c3fcfb23197c 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -Mohamad Rafli Hidayat \ No newline at end of file +Mohamad Rafli Hidayat - 2306245831 +PKPL - C +Weekly Individual Assignment 6 \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100644 index 0000000000000000000000000000000000000000..5da474c603fd2f0c4546611c8d0b73b83f14945b --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wia6.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..c458349acf236d6e68bfd00fdd066dc462185b04 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +django +gunicorn +whitenoise +psycopg2-binary +requests +urllib3 \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000000000000000000000000000000000000..fdb2096344cacba8e640fb09a1a0e08d5f7aab1b --- /dev/null +++ b/templates/base.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + {% block meta %} {% endblock meta %} + <script src="https://cdn.tailwindcss.com"></script> + </head> + <body> + {% block content %} {% endblock content %} + </body> +</html> \ No newline at end of file diff --git a/wia6/__init__.py b/wia6/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/wia6/asgi.py b/wia6/asgi.py new file mode 100644 index 0000000000000000000000000000000000000000..dd72ec41839d3ec07f8e81db45c846cb76b91b27 --- /dev/null +++ b/wia6/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for wia6 project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wia6.settings') + +application = get_asgi_application() diff --git a/wia6/settings.py b/wia6/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..2b301b4d88639cd6708d7b61000d5aed81db46ee --- /dev/null +++ b/wia6/settings.py @@ -0,0 +1,126 @@ +""" +Django settings for wia6 project. + +Generated by 'django-admin startproject' using Django 5.1.7. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +import os +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-8mh6_hwahmof$w3lfth6y-5f=3&o@+snf7__e4-61bqp!cq9uy' + +# SECURITY WARNING: don't run with debug turned on in production! +PRODUCTION = os.getenv("PRODUCTION", False) +DEBUG = not PRODUCTION + +ALLOWED_HOSTS = ["localhost", "127.0.0.1"] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'wia6app', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + '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', +] + +ROOT_URLCONF = 'wia6.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [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 = 'wia6.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators + +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', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/wia6/urls.py b/wia6/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..2bb4d7d541ec77f568c0dddcb1efaf3aaac0b974 --- /dev/null +++ b/wia6/urls.py @@ -0,0 +1,23 @@ +""" +URL configuration for wia6 project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +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.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('wia6app.urls')), +] diff --git a/wia6/wsgi.py b/wia6/wsgi.py new file mode 100644 index 0000000000000000000000000000000000000000..c2dd0d7a3a618f7d08989173af54e285e9d2cb1b --- /dev/null +++ b/wia6/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for wia6 project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wia6.settings') + +application = get_wsgi_application() diff --git a/wia6app/__init__.py b/wia6app/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/wia6app/admin.py b/wia6app/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e --- /dev/null +++ b/wia6app/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/wia6app/apps.py b/wia6app/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..995bb7526d89993e18ad10faa716751791bf4553 --- /dev/null +++ b/wia6app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class Wia6AppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'wia6app' diff --git a/wia6app/forms.py b/wia6app/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..5173535d6ec08e86543144abf9e1cf1424349e96 --- /dev/null +++ b/wia6app/forms.py @@ -0,0 +1,125 @@ +from django import forms +import re +from datetime import date + +class InfoForm(forms.Form): + # Validasi Model User + nama = forms.CharField( + max_length=255, + error_messages={ + 'required': 'Nama wajib diisi.', + } + ) + def clean_nama(self): + nama = self.cleaned_data['nama'] + if not re.match(r'^[a-zA-Z0-9._-]+$', nama): + raise forms.ValidationError("Nama hanya boleh berisi huruf, angka, titik (.), underscore (_), dan tanda hubung (-).") + return nama + + password = forms.CharField( + max_length=255, + error_messages={ + 'required': 'Password wajib diisi.', + } + ) + def clean_password(self): + password = self.cleaned_data['password'] + if len(password) < 8: + raise forms.ValidationError("Password harus minimal 8 karakter.") + if not re.match(r'^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$', password): + raise forms.ValidationError("Password harus mengandung huruf, angka, dan karakter spesial (@$!%*#?&).") + return password + + tanggal_lahir = forms.DateField( + input_formats=['%Y-%m-%d'], + error_messages={ + 'required': 'Tanggal lahir wajib diisi.', + 'invalid': 'Format tanggal lahir tidak valid. Gunakan format YYYY-MM-DD.', + } + ) + def clean_tanggal_lahir(self): + tanggal_lahir = self.cleaned_data.get('tanggal_lahir') + today = date.today() + usia = today.year - tanggal_lahir.year - ((today.month, today.day) < (tanggal_lahir.month, tanggal_lahir.day)) + if usia < 12: + raise forms.ValidationError("Usia pengguna harus minimal 12 tahun.") + + return tanggal_lahir + + kode_negara = forms.CharField( + max_length=3, + label="Kode Negara", + error_messages={ + 'required': 'Kode Negara wajib diisi.', + 'max_length': 'Kode Negara tidak boleh lebih dari 3 digit angka.', + } + ) + nomor_hp = forms.CharField( + max_length=12, + error_messages={ + 'required': 'Nomor HP wajib diisi.', + 'max_length': 'Nomor HP tidak boleh lebih dari 12 digit angka.', + } + ) + def clean_kode_negara(self): + kode_negara = self.cleaned_data['kode_negara'] + if not re.match(r'^\d{1,3}$', kode_negara): + raise forms.ValidationError("Kode negara harus berupa angka dengan panjang 1-3 digit.") + return kode_negara + def clean_nomor_hp(self): + nomor_hp = self.cleaned_data['nomor_hp'] + if not re.match(r'^\d{7,12}$', nomor_hp): + raise forms.ValidationError("Nomor HP harus berupa angka dengan panjang 7-12 digit.") + panjang_total = len(nomor_hp) + if panjang_total < 7: + raise forms.ValidationError("Panjang nomor HP harus antara 7-12 digit angka.") + return nomor_hp + + email = forms.EmailField( + error_messages={ + 'required': 'Email wajib diisi.', + } + ) + + url_blog = forms.URLField( + error_messages={ + 'required': 'URL wajib diisi.', + } + ) + + deskripsi_diri = forms.CharField( + widget=forms.Textarea, + error_messages={ + 'required': 'Deskripsi diri wajib diisi.', + } + ) + def clean_deskripsi_diri(self): + deskripsi_diri = self.cleaned_data['deskripsi_diri'] + panjang_total = len(deskripsi_diri) + if panjang_total < 5 or panjang_total > 100: + raise forms.ValidationError("Deskripsi diri harus minimal 5 karakter dan maksimal 100 karakter.") + return deskripsi_diri + + # Validasi Model Khusus dari Makanan & Minuman + nik = forms.CharField( + max_length=16, + error_messages={ + 'required': 'NIK wajib diisi.', + 'max_length': 'NIK tidak boleh lebih dari 16 digit angka.', + } + ) + def clean_nik(self): + nik = self.cleaned_data['nik'] + if not re.match(r'^\d{16}$', nik): + raise forms.ValidationError("NIK harus berupa 16 digit angka.") + return nik + + poin = forms.IntegerField( + min_value=0, + max_value=1000000, + error_messages={ + 'required': 'Poin wajib diisi.', + 'min_value': 'Poin tidak boleh kurang dari 0.', + 'max_value': 'Poin tidak boleh lebih dari 1.000.000.', + } + ) \ No newline at end of file diff --git a/wia6app/migrations/0001_initial.py b/wia6app/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..68a037716ae27a7e5ea3bc9ee6e1c8cb8949e7d7 --- /dev/null +++ b/wia6app/migrations/0001_initial.py @@ -0,0 +1,40 @@ +# Generated by Django 5.1.7 on 2025-03-14 08:20 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='FoodPreference', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nik', models.CharField(max_length=16)), + ('jumlah_poin', models.IntegerField()), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='UserInfo', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nama', models.CharField(max_length=255)), + ('password', models.CharField(max_length=255)), + ('tanggal_lahir', models.DateField()), + ('nomor_hp', models.CharField(max_length=15)), + ('email', models.EmailField(max_length=254)), + ('url_blog', models.URLField()), + ('deskripsi_diri', models.TextField()), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/wia6app/migrations/0002_remove_userinfo_user_info_delete_foodpreference_and_more.py b/wia6app/migrations/0002_remove_userinfo_user_info_delete_foodpreference_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..8d3c3b610afd0a953b70d9e2613c44525746bf72 --- /dev/null +++ b/wia6app/migrations/0002_remove_userinfo_user_info_delete_foodpreference_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 5.1.7 on 2025-03-14 14:31 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wia6app', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.RemoveField( + model_name='userinfo', + name='user', + ), + migrations.CreateModel( + name='Info', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nama', models.CharField(max_length=255)), + ('password', models.CharField(max_length=255)), + ('tanggal_lahir', models.DateField()), + ('nomor_hp', models.CharField(max_length=15)), + ('email', models.EmailField(max_length=254)), + ('url_blog', models.URLField()), + ('deskripsi_diri', models.TextField()), + ('nik', models.CharField(max_length=16)), + ('jumlah_poin', models.IntegerField()), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.DeleteModel( + name='FoodPreference', + ), + migrations.DeleteModel( + name='UserInfo', + ), + ] diff --git a/wia6app/migrations/__init__.py b/wia6app/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/wia6app/models.py b/wia6app/models.py new file mode 100644 index 0000000000000000000000000000000000000000..a3147cabe27b563ca6abbbba524784f3b54be79d --- /dev/null +++ b/wia6app/models.py @@ -0,0 +1,17 @@ +from django.db import models +from django.contrib.auth.models import User + +class Info(models.Model): + # Model User + user = models.OneToOneField(User, on_delete=models.CASCADE) + nama = models.CharField(max_length=255) + password = models.CharField(max_length=255) + tanggal_lahir = models.DateField() + nomor_hp = models.CharField(max_length=15) + email = models.EmailField() + url_blog = models.URLField() + deskripsi_diri = models.TextField() + + # Model Makanan dan Minuman no 4 & 5 + nik = models.CharField(max_length=16) + jumlah_poin = models.IntegerField() \ No newline at end of file diff --git a/wia6app/templates/user_form.html b/wia6app/templates/user_form.html new file mode 100644 index 0000000000000000000000000000000000000000..a36b15cb3e19bf39e495e7cf415488dc43606e99 --- /dev/null +++ b/wia6app/templates/user_form.html @@ -0,0 +1,128 @@ +{% extends 'base.html' %} + +{% block content %} +<div class="bg-gray-100 flex items-center justify-center min-h-screen"> + <div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-md"> + <h1 class="text-2xl font-bold mb-6 text-center">Form</h1> + <form method="post" class="space-y-4"> + {% csrf_token %} + + <div> + <label class="block text-sm font-medium text-gray-700">Nama</label> + <input type="text" name="nama" value="{{ form.nama.value|default_if_none:'' }}" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="Username"> + {% if form.nama.errors %} + <p class="text-red-500 text-sm">{{ form.nama.errors.0 }}</p> + {% endif %} + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Password</label> + <div class="relative"> + <input type="password" name="password" id="password" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"> + <!-- Tombol Mata --> + <button type="button" onclick="togglePasswordVisibility()" class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"> + <svg id="eye-icon" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> + </svg> + </button> + </div> + {% if form.password.errors %} + <p class="text-red-500 text-sm">{{ form.password.errors.0 }}</p> + {% endif %} + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Tanggal Lahir</label> + <input type="date" name="tanggal_lahir" value="{{ form.tanggal_lahir.value|default_if_none:'' }}" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="Tanggal Lahir"> + {% if form.tanggal_lahir.errors %} + <p class="text-red-500 text-sm">{{ form.tanggal_lahir.errors.0 }}</p> + {% endif %} + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Nomor HP</label> + <div class="flex gap-2"> + <div class="w-1/5"> + <input type="text" name="kode_negara" value="{{ form.kode_negara.value|default_if_none:'' }}" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="Kode"> + </div> + <div class="w-4/5"> + <input type="text" name="nomor_hp" value="{{ form.nomor_hp.value|default_if_none:'' }}" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="Nomor Telepon"> + </div> + </div> + {% if form.kode_negara.errors %} + <p class="text-red-500 text-sm">{{ form.kode_negara.errors.0 }}</p> + {% endif %} + {% if form.nomor_hp.errors %} + <p class="text-red-500 text-sm">{{ form.nomor_hp.errors.0 }}</p> + {% endif %} + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Email</label> + <input type="email" name="email" value="{{ form.email.value|default_if_none:'' }}" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="Email"> + {% if form.email.errors %} + <p class="text-red-500 text-sm">{{ form.email.errors.0 }}</p> + {% endif %} + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">URL Blog</label> + <input type="url" name="url_blog" value="{{ form.url_blog.value|default_if_none:'' }}" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="URL"> + {% if form.url_blog.errors %} + <p class="text-red-500 text-sm">{{ form.url_blog.errors.0 }}</p> + {% endif %} + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Deskripsi Diri</label> + <textarea name="deskripsi_diri" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="Deskripsi Diri">{{ form.deskripsi_diri.value|default_if_none:'' }}</textarea> + {% if form.deskripsi_diri.errors %} + <p class="text-red-500 text-sm">{{ form.deskripsi_diri.errors.0 }}</p> + {% endif %} + </div> + + <!-- Data Khusus dari Makanan & Minuman --> + <div> + <label class="block text-sm font-medium text-gray-700">NIK</label> + <input type="text" name="nik" value="{{ form.nik.value|default_if_none:'' }}" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="NIK"> + {% if form.nik.errors %} + <p class="text-red-500 text-sm">{{ form.nik.errors.0 }}</p> + {% endif %} + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Poin</label> + <input type="number" name="poin" value="{{ form.poin.value|default_if_none:'' }}" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="Poin"> + {% if form.poin.errors %} + <p class="text-red-500 text-sm">{{ form.poin.errors.0 }}</p> + {% endif %} + </div> + + <button type="submit" class="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600">Submit</button> + </form> + </div> +</div> + +<script> + // Fungsi untuk menampilkan/menyembunyikan password + function togglePasswordVisibility() { + const passwordField = document.getElementById('password'); + const eyeIcon = document.getElementById('eye-icon'); + // Menampilkan password + if (passwordField.type === 'password') { + passwordField.type = 'text'; + eyeIcon.innerHTML = ` + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" /> + `; + // Menyembunyikan password + } else { + passwordField.type = 'password'; + eyeIcon.innerHTML = ` + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> + `; + } + } +</script> +{% endblock content %} \ No newline at end of file diff --git a/wia6app/templates/user_info_display.html b/wia6app/templates/user_info_display.html new file mode 100644 index 0000000000000000000000000000000000000000..027018f67f96d9b910ef94c5db12219d5b1308ce --- /dev/null +++ b/wia6app/templates/user_info_display.html @@ -0,0 +1,92 @@ +{% extends 'base.html' %} + +{% block content %} +<div class="bg-gray-100 flex items-center justify-center min-h-screen"> + <div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-md"> + <h1 class="text-2xl font-bold mb-6 text-center">Display Info</h1> + <div class="space-y-4"> + + <div> + <label class="block text-sm font-medium text-gray-700">Nama</label> + <p class="mt-1">{{ form_data.nama }}</p> + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Password</label> + <div class="relative"> + <!-- Tombol Mata --> + <input type="password" id="password-display" value="{{ form_data.password }}" class="mt-1" readonly> + <button type="button" onclick="togglePasswordVisibility('password-display')" class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"> + <svg id="eye-icon-display" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> + </svg> + </button> + </div> + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Tanggal Lahir</label> + <p class="mt-1">{{ form_data.tanggal_lahir }}</p> + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Nomor HP</label> + <p class="mt-1">{{ form_data.nomor_hp_lengkap }}</p> + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Email</label> + <p class="mt-1">{{ form_data.email }}</p> + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">URL Blog</label> + <p class="mt-1">{{ form_data.url_blog }}</p> + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Deskripsi Diri</label> + <p class="mt-1">{{ form_data.deskripsi_diri }}</p> + </div> + + <!-- Data Khusus dari Makanan & Minuman --> + <div> + <label class="block text-sm font-medium text-gray-700">NIK</label> + <p class="mt-1">{{ form_data.nik }}</p> + </div> + + <div> + <label class="block text-sm font-medium text-gray-700">Poin</label> + <p class="mt-1">{{ form_data.poin }}</p> + </div> + + </div> + <div class="mt-6"> + <a href="{% url 'user_form' %}" class="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 block text-center">Kembali</a> + </div> + </div> +</div> + +<script> + // Fungsi untuk menampilkan/menyembunyikan password + function togglePasswordVisibility(fieldId) { + const passwordField = document.getElementById(fieldId); + const eyeIcon = document.getElementById('eye-icon-display'); + // Menampilkan password + if (passwordField.type === 'password') { + passwordField.type = 'text'; + eyeIcon.innerHTML = ` + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" /> + `; + // Menyembunyikan password + } else { + passwordField.type = 'password'; + eyeIcon.innerHTML = ` + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> + `; + } + } +</script> +{% endblock content %} \ No newline at end of file diff --git a/wia6app/tests.py b/wia6app/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/wia6app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/wia6app/urls.py b/wia6app/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..bf81330b84ee11e14e28e1c6985aee062488452a --- /dev/null +++ b/wia6app/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('user-form/', views.user_form, name='user_form'), + path('user-info-display/', views.user_info_display, name='user_info_display'), +] \ No newline at end of file diff --git a/wia6app/views.py b/wia6app/views.py new file mode 100644 index 0000000000000000000000000000000000000000..a06557865424e3cd341b513df8a6a16da5694103 --- /dev/null +++ b/wia6app/views.py @@ -0,0 +1,34 @@ +from django.shortcuts import render, redirect +from .forms import InfoForm +from datetime import date + +# Membuat view untuk halaman form +def user_form(request): + if request.method == 'POST': + form = InfoForm(request.POST) + if form.is_valid(): + # Ambil data yang sudah divalidasi + form_data = form.cleaned_data + # Konversi date ke string + if 'tanggal_lahir' in form_data: + form_data['tanggal_lahir'] = form_data['tanggal_lahir'].isoformat() + # Gabungkan kode negara dan nomor telepon + nomor_hp_lengkap = f"+{form_data['kode_negara']} {form_data['nomor_hp']}" + form_data['nomor_hp_lengkap'] = nomor_hp_lengkap # Tambahkan ke data form + # Simpan data ke session + request.session['form_data'] = form_data + return redirect('user_info_display') # Redirect ke halaman display + else: + # Buat form baru + form = InfoForm() + return render(request, 'user_form.html', {'form': form}) + +# Membuat view untuk halaman display +def user_info_display(request): + # Ambil data dari session + form_data = request.session.get('form_data', {}) + # Konversi string tanggal_lahir kembali ke date + if 'tanggal_lahir' in form_data: + form_data['tanggal_lahir'] = date.fromisoformat(form_data['tanggal_lahir']) + # Tampilkan data + return render(request, 'user_info_display.html', {'form_data': form_data})