diff --git a/PKPL_Individu_2306240124_NairaShafiqaAfiany/settings.py b/PKPL_Individu_2306240124_NairaShafiqaAfiany/settings.py index 18cf220a9e0d7cff697f773ad80fa31984d6ce64..ee4f0f1fd07bb28aad53659ae1c57f5f4be41ef9 100644 --- a/PKPL_Individu_2306240124_NairaShafiqaAfiany/settings.py +++ b/PKPL_Individu_2306240124_NairaShafiqaAfiany/settings.py @@ -37,6 +37,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'main' ] MIDDLEWARE = [ @@ -121,3 +122,4 @@ STATIC_URL = 'static/' # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +AUTH_USER_MODEL = 'main.User' diff --git a/PKPL_Individu_2306240124_NairaShafiqaAfiany/urls.py b/PKPL_Individu_2306240124_NairaShafiqaAfiany/urls.py index ddd50969980ce513ff1e4bbdc61909d920c4f34d..f6c93c5d7f151a02efe865474b402c7e24f8718a 100644 --- a/PKPL_Individu_2306240124_NairaShafiqaAfiany/urls.py +++ b/PKPL_Individu_2306240124_NairaShafiqaAfiany/urls.py @@ -15,8 +15,10 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), + path('', include('main.urls')), + ] diff --git a/main/__init__.py b/main/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/main/admin.py b/main/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e --- /dev/null +++ b/main/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/main/apps.py b/main/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..167f04426e4e30b75dadbbe56ff974baff39b71a --- /dev/null +++ b/main/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MainConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'main' diff --git a/main/forms.py b/main/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..ff84b7fc1c5dc88d0e0d50fc5bddddd6f90a0073 --- /dev/null +++ b/main/forms.py @@ -0,0 +1,19 @@ +from django import forms +from .models import User, Transportasi + +class UserForm(forms.ModelForm): + class Meta: + model = User + fields = ['username', 'password', 'birthdate', 'phone_number', 'email', 'blog_url', 'bio'] + widgets = { + 'password': forms.PasswordInput(), + 'birthdate': forms.DateInput(attrs={'type': 'date'}), + } + +class TransportasiForm(forms.ModelForm): + class Meta: + model = Transportasi + fields = ['lokasi_duduk', 'nomor_rangka'] + widgets = { + 'lokasi_duduk': forms.Select(), # ✅ Supaya bisa pilih dropdown + } diff --git a/main/migrations/0001_initial.py b/main/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..9d8d8f31ca8997f1c56341aa0f51a8cea9b56942 --- /dev/null +++ b/main/migrations/0001_initial.py @@ -0,0 +1,48 @@ +# Generated by Django 5.1.7 on 2025-03-16 07:24 + +import django.contrib.auth.models +import django.utils.timezone +import main.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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=150, 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')), + ('username', models.CharField(max_length=255, unique=True, validators=[main.models.validate_username])), + ('password', models.CharField(max_length=255, validators=[main.models.validate_password])), + ('birthdate', models.DateField(validators=[main.models.validate_birthdate])), + ('phone_number', models.CharField(max_length=15, validators=[main.models.validate_phone])), + ('email', models.EmailField(max_length=254, unique=True)), + ('blog_url', models.URLField()), + ('bio', models.TextField(blank=True, max_length=1000)), + ('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', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/main/migrations/0002_transportasi.py b/main/migrations/0002_transportasi.py new file mode 100644 index 0000000000000000000000000000000000000000..e1780bce5a14f570455f2e5bf0d363e53702e9f0 --- /dev/null +++ b/main/migrations/0002_transportasi.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.7 on 2025-03-16 07:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Transportasi', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('lokasi_duduk', models.CharField(choices=[('Jendela', 'Jendela'), ('Tengah', 'Tengah'), ('Gang', 'Gang')], max_length=10, verbose_name='Lokasi Duduk')), + ('nomor_rangka', models.CharField(max_length=15, unique=True, verbose_name='Nomor Rangka Kendaraan')), + ], + ), + ] diff --git a/main/migrations/__init__.py b/main/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/main/models.py b/main/models.py new file mode 100644 index 0000000000000000000000000000000000000000..0539da6e010b392a8928be21950a8dc6b3dd33ea --- /dev/null +++ b/main/models.py @@ -0,0 +1,63 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models +import re +from django.core.exceptions import ValidationError +from datetime import date, timedelta + +def validate_username(value): + if not re.match(r'^[a-zA-Z0-9._-]+$', value): + raise ValidationError("Username hanya boleh berisi huruf, angka, '.', '_', atau '-'.") + +def validate_password(value): + if len(value) < 8 or not re.search(r'[\W_]', value): + raise ValidationError("Password harus minimal 8 karakter dan mengandung karakter spesial.") + +def validate_birthdate(value): + min_birthdate = date.today() - timedelta(days=12*365) + if value > min_birthdate: + raise ValidationError("Usia minimal adalah 12 tahun.") + +def validate_phone(value): + if not re.match(r'^62[0-9]{6,13}$', value): + raise ValidationError("Nomor HP harus dalam format 62xxxxxxxx dan panjangnya 8-15 angka.") + +def validate_nomor_rangka(value): + if not re.match(r'^[A-Za-z]{5}[A-Za-z0-9]{10}$', value): + raise ValidationError("Nomor rangka harus diawali 5 huruf, lalu 10 karakter alfanumerik.") + +# MODEL USER +class User(AbstractUser): + username = models.CharField(max_length=255, unique=True, validators=[validate_username]) + password = models.CharField(max_length=255, validators=[validate_password]) + birthdate = models.DateField(validators=[validate_birthdate]) + phone_number = models.CharField(max_length=15, validators=[validate_phone]) + email = models.EmailField(unique=True) + blog_url = models.URLField() + bio = models.TextField(max_length=1000, blank=True) + + def __str__(self): + return self.username + +# MODEL TRANSPORTASI +class Transportasi(models.Model): + LOKASI_DUDUK_CHOICES = [ + ('Jendela', 'Jendela'), + ('Tengah', 'Tengah'), + ('Gang', 'Gang'), + ] + + lokasi_duduk = models.CharField( + max_length=10, + choices=LOKASI_DUDUK_CHOICES, + verbose_name="Lokasi Duduk" + ) + + nomor_rangka = models.CharField( + max_length=15, + unique=True, + validators=[validate_nomor_rangka], + verbose_name="Nomor Rangka Kendaraan" + ) + + def __str__(self): + return f"{self.nomor_rangka} - {self.lokasi_duduk}" diff --git a/main/templates/main.html b/main/templates/main.html new file mode 100644 index 0000000000000000000000000000000000000000..12a45f89cb4f2e695ef6956cc051c9d1dd6a93d1 --- /dev/null +++ b/main/templates/main.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html lang="id"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Form User & Transportasi</title> +</head> +<body> + <h2><strong>Form User</strong></h2> + <form method="post"> + {% csrf_token %} + + {{ user_form.as_p }} + {{ transportasi_form.as_p }} + + <button type="submit">Submit</button> + </form> +</body> +</html> diff --git a/main/tests.py b/main/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/main/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/main/urls.py b/main/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..c0dd158c56f23897713f54e43c7002a5f78e658f --- /dev/null +++ b/main/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from .views import combined_form_view + +urlpatterns = [ + path('', combined_form_view, name='home'), +] diff --git a/main/views.py b/main/views.py new file mode 100644 index 0000000000000000000000000000000000000000..e81de2f55ccaf3f7f9f994bbff39448a5ec9463e --- /dev/null +++ b/main/views.py @@ -0,0 +1,25 @@ +from django.shortcuts import render, redirect +from .forms import UserForm, TransportasiForm +from django.contrib import messages + +def combined_form_view(request): + if request.method == "POST": + user_form = UserForm(request.POST) + transportasi_form = TransportasiForm(request.POST) + + if user_form.is_valid() and transportasi_form.is_valid(): + user_form.save() + transportasi_form.save() + messages.success(request, "Data berhasil disimpan!") + return redirect('home') + + else: + messages.error(request, "Periksa kembali data yang dimasukkan.") + else: + user_form = UserForm() + transportasi_form = TransportasiForm() + + return render(request, "main.html", { + "user_form": user_form, + "transportasi_form": transportasi_form + })