From 1bf515ae76435e88f8a200edefcdec9b5a9111d9 Mon Sep 17 00:00:00 2001 From: Irwan Cahyono Date: Thu, 26 Feb 2026 11:06:14 +0700 Subject: [PATCH] pertamax --- .gitignore | 140 ++++++++++++++++ microsite_api/.gitignore | 142 ++++++++++++++++ microsite_api/location/__init__.py | 0 microsite_api/location/admin.py | 3 + microsite_api/location/apps.py | 5 + microsite_api/location/migrations/__init__.py | 0 microsite_api/location/models.py | 40 +++++ microsite_api/location/serializers.py | 35 ++++ microsite_api/location/tests.py | 3 + microsite_api/location/urls.py | 16 ++ microsite_api/location/views.py | 107 ++++++++++++ microsite_api/manage.py | 22 +++ microsite_api/microsite_api/__init__.py | 0 microsite_api/microsite_api/asgi.py | 16 ++ microsite_api/microsite_api/pagination.py | 23 +++ microsite_api/microsite_api/settings.py | 153 ++++++++++++++++++ microsite_api/microsite_api/urls.py | 30 ++++ microsite_api/microsite_api/wsgi.py | 16 ++ microsite_api/user_profile/__init__.py | 0 microsite_api/user_profile/admin.py | 3 + microsite_api/user_profile/apps.py | 5 + .../user_profile/migrations/__init__.py | 0 microsite_api/user_profile/models.py | 3 + microsite_api/user_profile/permissions.py | 13 ++ microsite_api/user_profile/serializers.py | 72 +++++++++ microsite_api/user_profile/tests.py | 3 + microsite_api/user_profile/urls.py | 11 ++ microsite_api/user_profile/views.py | 41 +++++ requirements.txt | 24 +++ 29 files changed, 926 insertions(+) create mode 100644 .gitignore create mode 100644 microsite_api/.gitignore create mode 100644 microsite_api/location/__init__.py create mode 100644 microsite_api/location/admin.py create mode 100644 microsite_api/location/apps.py create mode 100644 microsite_api/location/migrations/__init__.py create mode 100644 microsite_api/location/models.py create mode 100644 microsite_api/location/serializers.py create mode 100644 microsite_api/location/tests.py create mode 100644 microsite_api/location/urls.py create mode 100644 microsite_api/location/views.py create mode 100755 microsite_api/manage.py create mode 100644 microsite_api/microsite_api/__init__.py create mode 100644 microsite_api/microsite_api/asgi.py create mode 100644 microsite_api/microsite_api/pagination.py create mode 100644 microsite_api/microsite_api/settings.py create mode 100644 microsite_api/microsite_api/urls.py create mode 100644 microsite_api/microsite_api/wsgi.py create mode 100644 microsite_api/user_profile/__init__.py create mode 100644 microsite_api/user_profile/admin.py create mode 100644 microsite_api/user_profile/apps.py create mode 100644 microsite_api/user_profile/migrations/__init__.py create mode 100644 microsite_api/user_profile/models.py create mode 100644 microsite_api/user_profile/permissions.py create mode 100644 microsite_api/user_profile/serializers.py create mode 100644 microsite_api/user_profile/tests.py create mode 100644 microsite_api/user_profile/urls.py create mode 100644 microsite_api/user_profile/views.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4189e56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,140 @@ +# ========================================== +# Python cache and bytecode files +# ========================================== +__pycache__/ +*.py[cod] +*$py.class +**/__pycache__/ +**/*.pyc +**/*.pyo +**/*.pyd + +# ========================================== +# Virtual environments +# ========================================== +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# ========================================== +# Dependency files +# ========================================== +Pipfile.lock +poetry.lock + +# ========================================== +# Distribution / build +# ========================================== +build/ +dist/ +*.egg-info/ +.eggs/ + +# ========================================== +# Installer logs +# ========================================== +pip-log.txt +pip-delete-this-directory.txt + +# ========================================== +# Unit test / coverage +# ========================================== +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +.mypy_cache/ +.dmypy.json +dmypy.json +.pyre/ + +# ========================================== +# Django +# ========================================== +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +media/ +staticfiles/ + +# Migrations (optional, keep *.py if you want to version control) +**/migrations/*.pyc +**/migrations/__pycache__/ + +# ========================================== +# IDEs and editors +# ========================================== +.idea/ +.vscode/ + +# ========================================== +# OS-specific files +# ========================================== +.DS_Store +Thumbs.db +ehthumbs.db +Desktop.ini + +# ========================================== +# Environment variable files +# ========================================== +*.env +*.env.* + +# ========================================== +# Profiles +# ========================================== +*.prof + +# ========================================== +# Backup and swap files +# ========================================== +*~ +*.bak +*.swp +*.swo + +# ========================================== +# Docker (optional) +# ========================================== +#**/Dockerfile +#**/docker-compose*.yml + +# ========================================== +# Celery (if used) +# ========================================== +celerybeat-schedule +celerybeat.pid + +# ========================================== +# Sphinx docs +# ========================================== +docs/_build/ + +# ========================================== +# PyBuilder +# ========================================== +target/ + +# ========================================== +# Jupyter Notebook +# ========================================== +.ipynb_checkpoints/ + +# ========================================== +# pyenv +# ========================================== +.python-version diff --git a/microsite_api/.gitignore b/microsite_api/.gitignore new file mode 100644 index 0000000..b351ca9 --- /dev/null +++ b/microsite_api/.gitignore @@ -0,0 +1,142 @@ +# ========================================== +# Python cache and bytecode files +# ========================================== +__pycache__/ +*.py[cod] +*$py.class +**/__pycache__/ +**/*.pyc +**/*.pyo +**/*.pyd + +# ========================================== +# Virtual environments +# ========================================== +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# ========================================== +# Dependency files +# ========================================== +Pipfile.lock +poetry.lock + +# ========================================== +# Distribution / build +# ========================================== +build/ +dist/ +*.egg-info/ +.eggs/ + +# ========================================== +# Installer logs +# ========================================== +pip-log.txt +pip-delete-this-directory.txt + +# ========================================== +# Unit test / coverage +# ========================================== +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +.mypy_cache/ +.dmypy.json +dmypy.json +.pyre/ + +# ========================================== +# Django +# ========================================== +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +media/ +staticfiles/ + +# Migrations (optional, keep *.py if you want to version control) +# **/migrations/*.py +# **/migrations/__init__.py +**/migrations/*.pyc +**/migrations/__pycache__/ + +# ========================================== +# IDEs and editors +# ========================================== +.idea/ +.vscode/ + +# ========================================== +# OS-specific files +# ========================================== +.DS_Store +Thumbs.db +ehthumbs.db +Desktop.ini + +# ========================================== +# Environment variable files +# ========================================== +*.env +*.env.* + +# ========================================== +# Profiles +# ========================================== +*.prof + +# ========================================== +# Backup and swap files +# ========================================== +*~ +*.bak +*.swp +*.swo + +# ========================================== +# Docker (optional) +# ========================================== +# **/Dockerfile +# **/docker-compose*.yml + +# ========================================== +# Celery (if used) +# ========================================== +celerybeat-schedule +celerybeat.pid + +# ========================================== +# Sphinx docs +# ========================================== +docs/_build/ + +# ========================================== +# PyBuilder +# ========================================== +target/ + +# ========================================== +# Jupyter Notebook +# ========================================== +.ipynb_checkpoints/ + +# ========================================== +# pyenv +# ========================================== +.python-version diff --git a/microsite_api/location/__init__.py b/microsite_api/location/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/microsite_api/location/admin.py b/microsite_api/location/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/microsite_api/location/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/microsite_api/location/apps.py b/microsite_api/location/apps.py new file mode 100644 index 0000000..445dfe3 --- /dev/null +++ b/microsite_api/location/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class LocationConfig(AppConfig): + name = 'location' diff --git a/microsite_api/location/migrations/__init__.py b/microsite_api/location/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/microsite_api/location/models.py b/microsite_api/location/models.py new file mode 100644 index 0000000..a0330ee --- /dev/null +++ b/microsite_api/location/models.py @@ -0,0 +1,40 @@ +from django.db import models +from django.contrib.auth.models import User +from django_softdelete.models import SoftDeleteModel + +class Province(SoftDeleteModel): + code = models.CharField(max_length=2, null=False, unique=True) + name = models.CharField(max_length=255, null=False) + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.name + +class RegencyCity(SoftDeleteModel): + code = models.CharField(max_length=4, null=False, unique=True) + name = models.CharField(max_length=255, null=False) + province = models.ForeignKey(Province, on_delete=models.CASCADE, null=False) + + def __str__(self): + return self.name + + class Meta: + unique_together = ('code', 'province') + +class Location(SoftDeleteModel): + name = models.CharField(max_length=255, null=False) + + path = models.TextField(null=True, blank=True) + + totem_latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) + totem_longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) + totem_code = models.CharField(max_length=50, null=True, blank=True) + + province = models.ForeignKey(Province, on_delete=models.SET_NULL, null=True) + regency_city = models.ForeignKey(RegencyCity, on_delete=models.SET_NULL, null=True) + active = models.BooleanField(default=True) + + def __str__(self): + return f"{self.user.username} - {self.province.name if self.province else 'No Province'} - {self.regency_city.name if self.regency_city else 'No Regency/City'}" \ No newline at end of file diff --git a/microsite_api/location/serializers.py b/microsite_api/location/serializers.py new file mode 100644 index 0000000..0f7259a --- /dev/null +++ b/microsite_api/location/serializers.py @@ -0,0 +1,35 @@ +from rest_framework import serializers +from location import models + +class ProvinceSerializer(serializers.ModelSerializer): + + class Meta: + model = models.Province + fields = ['id', 'code', 'name'] + +class RegencyCitySerializer(serializers.ModelSerializer): + + class Meta: + model = models.RegencyCity + fields = ['id', 'code', 'name', 'province'] + +class RegencyCityDetailSerializer(serializers.ModelSerializer): + province = ProvinceSerializer(read_only=True) + + class Meta: + model = models.RegencyCity + fields = ['id', 'code', 'name', 'province'] + +class LocationSerializer(serializers.ModelSerializer): + + class Meta: + model = models.Location + fields = ['id', 'name', 'path', 'totem_latitude', 'totem_longitude', 'totem_code', 'province', 'regency_city', 'active'] + +class LocationDetailSerializer(serializers.ModelSerializer): + province = ProvinceSerializer(read_only=True) + regency_city = RegencyCitySerializer(read_only=True) + + class Meta: + model = models.Location + fields = ['id', 'name', 'path', 'totem_latitude', 'totem_longitude', 'totem_code', 'province', 'regency_city', 'active'] \ No newline at end of file diff --git a/microsite_api/location/tests.py b/microsite_api/location/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/microsite_api/location/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/microsite_api/location/urls.py b/microsite_api/location/urls.py new file mode 100644 index 0000000..b21334b --- /dev/null +++ b/microsite_api/location/urls.py @@ -0,0 +1,16 @@ +from django.urls import path +from rest_framework.urlpatterns import format_suffix_patterns +from location import views + +urlpatterns = [ + path('provinces/', views.ProvinceList.as_view()), + path('provinces//', views.ProvinceDetail.as_view()), + + path('regencies_cities/', views.RegencyCityList.as_view()), + path('regencies_cities//', views.RegencyCityDetail.as_view()), + + path('locations/', views.LocationList.as_view()), + path('locations//', views.LocationDetail.as_view()), +] + +urlpatterns = format_suffix_patterns(urlpatterns) \ No newline at end of file diff --git a/microsite_api/location/views.py b/microsite_api/location/views.py new file mode 100644 index 0000000..ee8f26b --- /dev/null +++ b/microsite_api/location/views.py @@ -0,0 +1,107 @@ +from django.shortcuts import render +from rest_framework import generics, filters, permissions +from django_filters.rest_framework import DjangoFilterBackend + +from location import models, serializers + +# PROVINCES +class ProvinceList(generics.ListCreateAPIView): + queryset = models.Province.objects.all() + serializer_class = serializers.ProvinceSerializer + filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend] + search_fields = ['name', 'code'] + filterset_fields = ['name', 'code'] + ordering_fields = '__all__' + + def get_permissions(self): + if self.request.method == 'POST': + return [permissions.IsAdminUser()] + return [permissions.IsAuthenticated()] + +class ProvinceDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = models.Province.objects.all() + serializer_class = serializers.ProvinceSerializer + + def get_permissions(self): + if self.request.method == 'PUT' or self.request.method == 'DELETE': + return [permissions.IsAdminUser()] + return [permissions.IsAuthenticated()] + +# REGENCY/CITY + +class RegencyCityList(generics.ListCreateAPIView): + queryset = models.RegencyCity.objects.all() + serializer_class = serializers.RegencyCitySerializer + filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend] + search_fields = ['name', 'code'] + filterset_fields = ['name', 'code', 'province'] + ordering_fields = '__all__' + + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.RegencyCityDetailSerializer + + return serializer_class + + def get_permissions(self): + if self.request.method == 'POST': + return [permissions.IsAdminUser()] + return [permissions.IsAuthenticated()] + +class RegencyCityDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = models.RegencyCity.objects.all() + serializer_class = serializers.RegencyCitySerializer + + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.RegencyCityDetailSerializer + + return serializer_class + + def get_permissions(self): + if self.request.method == 'PUT' or self.request.method == 'DELETE': + return [permissions.IsAdminUser()] + return [permissions.IsAuthenticated()] + +# LOCATION +class LocationList(generics.ListCreateAPIView): + queryset = models.Location.objects.all() + serializer_class = serializers.LocationSerializer + filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend] + search_fields = ['name', 'path'] + filterset_fields = ['name', 'path', 'totem_code', 'province', 'regency_city', 'active'] + ordering_fields = '__all__' + + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.LocationDetailSerializer + + return serializer_class + + def get_permissions(self): + if self.request.method == 'POST': + return [permissions.IsAdminUser()] + return [permissions.IsAuthenticated()] + +class LocationDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = models.Location.objects.all() + serializer_class = serializers.LocationSerializer + + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.LocationDetailSerializer + + return serializer_class + + def get_permissions(self): + if self.request.method == 'PUT' or self.request.method == 'DELETE': + return [permissions.IsAdminUser()] + return [permissions.IsAuthenticated()] \ No newline at end of file diff --git a/microsite_api/manage.py b/microsite_api/manage.py new file mode 100755 index 0000000..5cbd3b0 --- /dev/null +++ b/microsite_api/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', 'microsite_api.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/microsite_api/microsite_api/__init__.py b/microsite_api/microsite_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/microsite_api/microsite_api/asgi.py b/microsite_api/microsite_api/asgi.py new file mode 100644 index 0000000..bf707bf --- /dev/null +++ b/microsite_api/microsite_api/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for microsite_api 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.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'microsite_api.settings') + +application = get_asgi_application() diff --git a/microsite_api/microsite_api/pagination.py b/microsite_api/microsite_api/pagination.py new file mode 100644 index 0000000..64b224f --- /dev/null +++ b/microsite_api/microsite_api/pagination.py @@ -0,0 +1,23 @@ +from rest_framework.pagination import PageNumberPagination +from rest_framework.response import Response + + +DEFAULT_PAGE = 1 +DEFAULT_PAGE_SIZE = 10 + +class CustomPagination(PageNumberPagination): + page = DEFAULT_PAGE + page_size = DEFAULT_PAGE_SIZE + page_size_query_param = 'size' + + # def get_paginated_response(self, data): + # return Response({ + # 'links': { + # 'next': self.get_next_link(), + # 'previous': self.get_previous_link() + # }, + # 'total': self.page.paginator.count, + # 'page': int(self.request.GET.get('page', DEFAULT_PAGE)), # can not set default = self.page + # 'page_size': int(self.request.GET.get('page_size', self.page_size)), + # 'results': data + # }) \ No newline at end of file diff --git a/microsite_api/microsite_api/settings.py b/microsite_api/microsite_api/settings.py new file mode 100644 index 0000000..03e2012 --- /dev/null +++ b/microsite_api/microsite_api/settings.py @@ -0,0 +1,153 @@ +""" +Django settings for microsite_api project. + +Generated by 'django-admin startproject' using Django 5.2.7. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.2/ref/settings/ +""" + +from datetime import timedelta +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.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-%3kiu2*x+3k27ownszb6e=h=i_v*m^5f-eudpf0t+$5kgx+#82' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'rest_framework', + 'rest_framework_simplejwt', + 'django_filters', + + 'user_profile', + 'location', +] + +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 = 'microsite_api.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'microsite_api.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'pert_microsite', + 'USER': 'postgres', + 'PASSWORD': 'asalada123', + 'HOST': '127.0.0.1', # Use '127.0.0.1' for local + 'PORT': '5432', # Default is 5432 + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.2/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.2/topics/i18n/ + +LANGUAGE_CODE = 'id-ID' + +TIME_ZONE = 'Asia/Jakarta' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.2/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +REST_FRAMEWORK = { + 'DEFAULT_PAGINATION_CLASS': + 'microsite_api.pagination.CustomPagination', + 'DEFAULT_FILTER_BACKENDS': [ + 'django_filters.rest_framework.DjangoFilterBackend' + ], + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + ], +} + +SIMPLE_JWT = { + 'AUTH_HEADER_TYPES': ('Bearer', 'JWT', 'Token'), +} \ No newline at end of file diff --git a/microsite_api/microsite_api/urls.py b/microsite_api/microsite_api/urls.py new file mode 100644 index 0000000..1b994eb --- /dev/null +++ b/microsite_api/microsite_api/urls.py @@ -0,0 +1,30 @@ +""" +URL configuration for microsite_api project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.2/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 include, path +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView + +urlpatterns = [ + path('admin/', admin.site.urls), + + path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'), + + path('profile/', include('user_profile.urls')), + path('location/', include('location.urls')), +] diff --git a/microsite_api/microsite_api/wsgi.py b/microsite_api/microsite_api/wsgi.py new file mode 100644 index 0000000..862e340 --- /dev/null +++ b/microsite_api/microsite_api/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for microsite_api 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.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'microsite_api.settings') + +application = get_wsgi_application() diff --git a/microsite_api/user_profile/__init__.py b/microsite_api/user_profile/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/microsite_api/user_profile/admin.py b/microsite_api/user_profile/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/microsite_api/user_profile/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/microsite_api/user_profile/apps.py b/microsite_api/user_profile/apps.py new file mode 100644 index 0000000..91ad550 --- /dev/null +++ b/microsite_api/user_profile/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class UserProfileConfig(AppConfig): + name = 'user_profile' diff --git a/microsite_api/user_profile/migrations/__init__.py b/microsite_api/user_profile/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/microsite_api/user_profile/models.py b/microsite_api/user_profile/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/microsite_api/user_profile/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/microsite_api/user_profile/permissions.py b/microsite_api/user_profile/permissions.py new file mode 100644 index 0000000..bb582dd --- /dev/null +++ b/microsite_api/user_profile/permissions.py @@ -0,0 +1,13 @@ +from rest_framework.permissions import BasePermission, SAFE_METHODS + +class TargetIsStaff(BasePermission): + message = "Hanya bisa memodifikasi user staff." + + def has_permission(self, request, view): + if request.method in SAFE_METHODS: + return bool(request.user and request.user.is_authenticated) + + return bool(request.user and request.user.is_staff) + + def has_object_permission(self, request, view, obj): + return bool(request.user.is_staff and obj.is_staff) \ No newline at end of file diff --git a/microsite_api/user_profile/serializers.py b/microsite_api/user_profile/serializers.py new file mode 100644 index 0000000..7b1f1af --- /dev/null +++ b/microsite_api/user_profile/serializers.py @@ -0,0 +1,72 @@ +from django.contrib.auth import get_user_model +from rest_framework import serializers + +User = get_user_model() + +from django.contrib.auth import get_user_model +from rest_framework import serializers + +User = get_user_model() + +class UserCreateSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = [ + 'id', + 'username', + 'email', + 'password', + 'first_name', + 'last_name', + ] + extra_kwargs = { + 'password': {'write_only': True} + } + + def create(self, validated_data): + password = validated_data.pop('password') + user = User(**validated_data) + user.set_password(password) + user.is_staff = True + user.save() + return user + +class UserRetrieveSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = [ + 'id', + 'username', + 'email', + 'first_name', + 'last_name', + 'is_active', + 'is_staff', + 'is_superuser', + 'date_joined', + 'last_login', + ] + read_only_fields = fields + +class UserUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = [ + 'id', + 'username', + 'email', + 'first_name', + 'last_name', + ] + +class UserUpdatePasswordSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = [ + 'id', + 'password', + ] + + extra_kwargs = { + 'password': {'write_only': True} + } \ No newline at end of file diff --git a/microsite_api/user_profile/tests.py b/microsite_api/user_profile/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/microsite_api/user_profile/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/microsite_api/user_profile/urls.py b/microsite_api/user_profile/urls.py new file mode 100644 index 0000000..8ebbf37 --- /dev/null +++ b/microsite_api/user_profile/urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from rest_framework.urlpatterns import format_suffix_patterns +from user_profile import views + +urlpatterns = [ + path('users/', views.UserList.as_view()), + path('users//', views.UserDetail.as_view()), + path('users//password/', views.UserPasswordUpdate.as_view()), +] + +urlpatterns = format_suffix_patterns(urlpatterns) \ No newline at end of file diff --git a/microsite_api/user_profile/views.py b/microsite_api/user_profile/views.py new file mode 100644 index 0000000..3448e9a --- /dev/null +++ b/microsite_api/user_profile/views.py @@ -0,0 +1,41 @@ +from rest_framework import filters, generics, permissions +from django.contrib.auth import get_user_model +from user_profile.permissions import TargetIsStaff +from user_profile import serializers +from django_filters.rest_framework import DjangoFilterBackend + +User = get_user_model() + +class UserList(generics.ListCreateAPIView): + queryset = User.objects.all() + serializer_class = serializers.UserCreateSerializer + permission_classes = [TargetIsStaff] + filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend] + search_fields = ['username', 'email', 'first_name', 'last_name'] + filterset_fields = ['username', 'email', 'first_name', 'last_name'] + ordering_fields = '__all__' + + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.UserRetrieveSerializer + + return serializer_class + +class UserDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = User.objects.all() + serializer_class = serializers.UserUpdateSerializer + permission_classes = [TargetIsStaff] + def get_serializer_class(self): + serializer_class = self.serializer_class + + if self.request.method == 'GET': + serializer_class = serializers.UserRetrieveSerializer + + return serializer_class + +class UserPasswordUpdate(generics.UpdateAPIView): + queryset = User.objects.all() + serializer_class = serializers.UserUpdatePasswordSerializer + permission_classes = [TargetIsStaff] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..dca1414 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,24 @@ +asgiref==3.11.1 +certifi==2026.2.25 +cffi==2.0.0 +charset-normalizer==3.4.4 +coverage==4.0.3 +cryptography==46.0.5 +defusedxml==0.7.1 +Django==6.0.2 +django-filter==25.2 +django-soft-delete==1.0.23 +djangorestframework==3.16.1 +djangorestframework_simplejwt==5.5.1 +idna==3.11 +oauthlib==3.3.1 +psycopg2-binary==2.9.11 +pycparser==3.0 +PyJWT==2.11.0 +python3-openid==3.2.0 +requests==2.32.5 +requests-oauthlib==2.0.0 +social-auth-app-django==5.7.0 +social-auth-core==4.8.5 +sqlparse==0.5.5 +urllib3==2.6.3