Commit bf3f39b7 authored by Ilham Maulana's avatar Ilham Maulana

new project

parent 3e735293
JWT_SECRET= #SECURE RANDOM SECREET
### Database
DB_HOST=""
DB_NAME=""
DB_USER=""
DB_PASSWORD=""
\ No newline at end of file
# Django #
*.log
*.pot
*.pyc
**/__pycache__/**
*.sqlite3
media
uploads
# 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
# 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/
*.whl
*.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
# Librarian App
The Librarian App is a library management tool designed to streamline book lending and member management processes. It offers functionalities for librarians to:
* Manage book entries (add, edit, delete)
* Manage member information (add, edit, delete)
* Manage librarian accounts (add, edit, delete with access control)
* Record book loans and returns
* Track librarian login history
* Generate reports on:
* Near outstanding book loans
* Overdue book loans
* Librarian login activity
This app provides a centralized platform for librarians to effectively manage library resources and user interactions.
## API
prefix: `/api/v1`
postman:
### Auth
| Endpoint | Description | HTTP |
|----|----|----|
| `/auth/login` | Login for librarian or member | POST |
| `/auth/logout` | Logout for librarian or member | GET |
| `/auth/registration` | Register new librarian or member | POST |
| `/auth/password/reset` | Reset password | POST |
| `/auth/password/reset/confirm` | Confirm reset password | POST |
### Books
| Endpoint | Description | HTTP |
|----|----|----|
| `/books` | book lists | GET |
| `/books/{{ book.id }}` | book details | GET, POST, PUT |
| `/books?search={{ book.title }}` | search book lists, filtered by name | GET |
| `/books?year={{ year }}` | filter book lists by year | GET |
| `/books?category={{ category }}` | filter book lists by category | GET |
### Categories
| Endpoint | Description | HTTP |
|----|----|----|
| `/categories` | category lists | GET |
| `/categories/{{ book.id }}` | category details | GET, POST, PUT |
| `/categories?search={{ book.title }}` | search category lists, filtered by name | GET |
| `/categories?year={{ year }}` | filter category lists by year | GET |
| `/categories?category={{ category }}` | filter category lists by category | GET |
### Members
| Endpoint | Description | HTTP |
|----|----|----|
| `/members` | member lists | GET |
| `/members/{{ book.id }}` | member details | GET, POST, PUT |
| `/members?search={{ book.title }}` | search member lists, filtered by name | GET |
| `/members?year={{ year }}` | filter member lists by year | GET |
| `/members?category={{ category }}` | filter member lists by category | GET |
### Librarians
| Endpoint | Description | HTTP |
|----|----|----|
| `/librarians` | librarian lists | GET |
| `/librarians/{{ book.id }}` | librarian details | GET, POST, PUT |
| `/librarians?search={{ book.title }}` | search librarian lists, filtered by name | GET |
| `/librarians?year={{ year }}` | filter librarian lists by year | GET |
| `/librarians?category={{ category }}` | filter librarian lists by category | GET |
### Book Loans
| Endpoint | Description | HTTP |
|----|----|----|
| `/book-loans` | book loan lists | GET |
| `/book-loans/{{ book.id }}` | book loan details | GET, POST, PUT |
| `/book-loans?search={{ book.title }}` | search book loan lists, filtered by name | GET |
| `/book-loans?year={{ year }}` | filter book loan lists by year | GET |
| `/book-loans?category={{ category }}` | filter book loan lists by category | GET |
## Start Development
1. Clone this project
```
git clone https://github.com/impfundev/library-app.git
cd library-app
```
2. Create virtual environments
```
python -m venv library_app_env
# linux
source library_app_env/bin/activate
# windows
library_app_env\Scripts\Activate.ps1
```
3. Install Depedencies
```
pip -r install requirements.txt
```
4. Configure secure variable `.env`
```
cp .env.example .env
```
in `.env`
```
JWT_SECRET= #SECURE RANDOM SECREET
### Database
PGHOST=""
PGDATABASE=""
PGUSER=""
PGPASSWORD=""
```
5. Make migration:
```
python manage.py makemigrations
python manage.py migrate
```
6. Run Development Server
```
python manage.py runserver
```
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'
from datetime import datetime
from django.contrib.auth import get_user_model
from rest_framework import serializers
from users.models import Role
from books.models import Book, Category
from members.models import Members
from book_loans.models import BookLoans
from librarians.models import Librarians
class RoleSerializer(serializers.ModelSerializer):
class Meta:
model = Role
fields = "__all__"
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = [
"username",
"email",
"password",
"first_name",
"last_name",
"is_staff",
]
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = "__all__"
class BookSerializer(serializers.ModelSerializer):
category_detail = CategorySerializer(source="category", read_only=True)
class Meta:
model = Book
fields = "__all__"
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = Members
fields = "__all__"
class LibrarianSerializer(serializers.ModelSerializer):
class Meta:
model = Librarians
fields = "__all__"
class BookLoanSerializer(serializers.ModelSerializer):
book_detail = BookSerializer(source="book", read_only=True)
member_detail = MemberSerializer(source="member", read_only=True)
librarian_detail = LibrarianSerializer(source="librarian", read_only=True)
class Meta:
model = BookLoans
fields = "__all__"
class MemberLoanSerializer(BookLoanSerializer):
is_overdue = serializers.BooleanField(read_only=True)
def to_representation(self, instance):
data = super().to_representation(instance)
data["is_overdue"] = instance.due_date.date() < datetime.now().date()
return data
class Meta:
model = BookLoans
fields = ["book", "loan_date", "due_date", "is_overdue"]
from django.urls import include, path, reverse
from rest_framework.test import APITestCase, URLPatternsTestCase
class AccountTests(APITestCase, URLPatternsTestCase):
urlpatterns = [
path("api/", include("api.urls")),
]
def test_create_account(self):
"""
Ensure can create a new account object.
"""
url = reverse("users")
response = self.client.get(url, format="json")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 1)
from django.urls import path, include
from rest_framework import routers
from dj_rest_auth.views import LogoutView
from api.views import (
UserViewSet,
BookViewSet,
CategoryViewSet,
MemberViewSet,
LibrarianViewSet,
BookLoanViewSet,
LoginAsLibrarian,
LogoutAsLibrarian,
ChangePasswordAsLibrarian,
LoginAsMember,
LogoutAsMember,
ChangePasswordAsMember,
OverduedBookLoanViewSet,
UpComingBookLoanViewSet,
MemberLoanViewSet,
LoginUserView,
)
router = routers.DefaultRouter()
router.register(r"users", UserViewSet, basename="users")
router.register(r"books", BookViewSet, basename="books")
router.register(r"categories", CategoryViewSet, basename="categories")
router.register(r"members", MemberViewSet, basename="members")
router.register(r"librarians", LibrarianViewSet, basename="librarians")
router.register(r"book-loans", BookLoanViewSet, basename="book_loans")
router.register(
r"overdued-loans", OverduedBookLoanViewSet, basename="book_loans_overdued"
)
router.register(
r"upcoming-loans", UpComingBookLoanViewSet, basename="book_loans_upcoming"
)
# extend endpoint member
router_member = routers.DefaultRouter()
router_member.register(r"loans", MemberLoanViewSet, basename="members_loans")
urlpatterns = [
path("", include(router.urls)),
path("login/librarian/", LoginAsLibrarian.as_view(), name="login_librarian"),
path(
"logout/librarian/<int:pk>/",
LogoutAsLibrarian.as_view(),
name="logout_librarian",
),
path(
"librarians/<int:pk>/change_password/",
ChangePasswordAsLibrarian.as_view(),
name="change_pw_librarian",
),
path("login/member/", LoginAsMember.as_view(), name="login_member"),
path(
"logout/member/<int:pk>/",
LogoutAsMember.as_view(),
name="logout_member",
),
path(
"members/<int:pk>/change_password/",
ChangePasswordAsMember.as_view(),
name="change_pw_member",
),
# extended
path(
"members/<int:member_id>/",
include(router_member.urls),
name="member_loans",
),
# auth beta
path(
"auth/beta/login/",
LoginUserView.as_view(),
name="auth_login_beta",
),
path(
"auth/beta/logout/",
LogoutView.as_view(),
name="auth_login_beta",
),
]
import jwt
from django.conf import settings
from datetime import datetime, timedelta
from rest_framework import views, viewsets, status
from rest_framework.response import Response
from rest_framework.filters import SearchFilter
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
from authentications.utils import Hasher
from api.serializers import (
get_user_model,
UserSerializer,
Book,
BookSerializer,
Category,
CategorySerializer,
Members,
MemberSerializer,
Librarians,
LibrarianSerializer,
BookLoans,
BookLoanSerializer,
MemberLoanSerializer,
)
from librarians.models import LoginHistory
from users.models import User
from dj_rest_auth.views import LoginView
class LoginUserView(LoginView):
def get_response(self):
if self.user.is_staff:
return Response(
{"message": "Login as librarian success"},
status=status.HTTP_200_OK,
)
else:
return Response(
{"message": "Login as member success"},
status=status.HTTP_200_OK,
)
class RegisterUserView(views.APIView):
def pos(self, request):
data = request.data
is_username = User.objects.filter(username=data.username)
if is_username.exists():
return Response(
{
"message": "Register failed: Username is already used, please used another username"
},
status=status.HTTP_400_BAD_REQUEST,
)
is_email = User.objects.filter(email=data.email)
if is_email.exists():
return Response(
{
"messagge": "Register failed: Email is already used, please used another email"
},
status=status.HTTP_400_BAD_REQUEST,
)
User.objects.create(data)
user = User.objects.get(username=data.username)
return Response(
{"messagge": "Register Success"},
status=status.HTTP_400_BAD_REQUEST,
)
class UserViewSet(viewsets.ModelViewSet):
# permission_classes = [IsAuthenticated]
queryset = get_user_model().objects.all().order_by("id")
serializer_class = UserSerializer
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all().order_by("created_at")
serializer_class = BookSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ["published_year", "category__name"]
search_fields = ["title"]
def update(self, request, pk):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all().order_by("created_at")
serializer_class = CategorySerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ["created_at", "updated_at"]
search_fields = ["name", "description"]
def update(self, request, pk):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
class MemberViewSet(viewsets.ModelViewSet):
queryset = Members.objects.all().order_by("created_at")
serializer_class = MemberSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ["created_at", "updated_at"]
search_fields = ["name", "email"]
class LibrarianViewSet(viewsets.ModelViewSet):
queryset = Librarians.objects.all().order_by("created_at")
serializer_class = LibrarianSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ["name", "email"]
search_fields = ["name", "email"]
class BookLoanViewSet(viewsets.ModelViewSet):
queryset = BookLoans.objects.all().order_by("created_at")
serializer_class = BookLoanSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ["loan_date", "due_date", "return_date", "member__id"]
search_fields = ["book__title", "member__name"]
def update(self, request, pk):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
class OverduedBookLoanViewSet(BookLoanViewSet):
now = datetime.now()
queryset = (
BookLoans.objects.all()
.filter(due_date__lte=now, return_date=None)
.order_by("created_at")
)
class UpComingBookLoanViewSet(BookLoanViewSet):
now = datetime.now()
due_date_treshold = now.today() + timedelta(days=3)
queryset = (
BookLoans.objects.all()
.filter(due_date__lte=due_date_treshold, return_date=None)
.filter(due_date__gte=now.today())
.order_by("created_at")
)
class MemberLoanViewSet(BookLoanViewSet):
queryset = BookLoans.objects.all()
serializer_class = MemberLoanSerializer
def get_queryset(self):
member_id = self.kwargs.get("member_id")
return BookLoans.objects.filter(member__id=member_id).order_by("created_at")
class LoginAsLibrarian(views.APIView):
def post(self, request):
data = request.data
librarians = Librarians.objects.all()
librarian = librarians.filter(email=data.get("email"))
is_email_exists = librarian.exists()
if not is_email_exists:
return Response(
{
"message": "Invalid Email, please enter valid email or sign up firts!"
},
status=status.HTTP_401_UNAUTHORIZED,
)
is_password_verified = Hasher.verify(
data.get("password"), librarian[0].password
)
if not is_password_verified:
return Response(
{"message": "Invalid Password, please enter valid password!"},
status=status.HTTP_401_UNAUTHORIZED,
)
expiration_time = datetime.now() + timedelta(hours=2)
payload = {
"exp": expiration_time.timestamp(),
"librarian_id": librarian[0].id,
"name": librarian[0].name,
"email": librarian[0].email,
}
token = jwt.encode(payload, settings.JWT_SECRET, algorithm="HS256")
LoginHistory.objects.create(librarian_id=librarian[0].id)
key = "auth_session_" + str(librarian[0].uuid)
request.session[key] = token
return Response({"message": "Login success!"}, status=status.HTTP_200_OK)
class LogoutAsLibrarian(views.APIView):
def get(self, request, pk):
librarian = Librarians.objects.get(pk=pk)
key = "auth_session_" + str(librarian.uuid)
if request.session[key] is None:
return Response(
{"message": "Logout failed, invalid key!"},
status=status.HTTP_400_BAD_REQUEST,
)
del request.session[key]
return Response({"message": "Logout success!"}, status=status.HTTP_200_OK)
class ChangePasswordAsLibrarian(views.APIView):
def post(self, request, pk):
data = request.data
librarians = Librarians.objects.all()
librarian = librarians.filter(pk=pk, email=data.get("email"))
is_email_exists = librarian.exists()
new_password = data.get("new_password")
if request.data.email is None or request.data.password is None:
return Response(
{"message": "Email or Password is required fields, cannot be empty"}
)
if not is_email_exists:
return Response(
{"message": "Invalid Email, please enter valid email!"},
status=status.HTTP_400_BAD_REQUEST,
)
is_old_password_verified = Hasher.verify(
data.get("password"), librarian[0].password
)
if not is_old_password_verified:
return Response(
{"message": "Invalid Old Password, please enter valid password!"},
status=status.HTTP_400_BAD_REQUEST,
)
if new_password is None:
return Response(
{"message": "Request failed, new_password is required field!"},
status=status.HTTP_400_BAD_REQUEST,
)
hashed_password = Hasher.encode(new_password)
librarian.update(password=hashed_password)
return Response(
{"message": "Change password success!"}, status=status.HTTP_200_OK
)
class LoginAsMember(views.APIView):
def post(self, request):
data = request.data
members = Members.objects.all()
member = members.filter(email=data.get("email"))
is_email_exists = member.exists()
if not is_email_exists:
return Response(
{
"message": "Invalid Email, please enter valid email or sign up firts!"
},
status=status.HTTP_401_UNAUTHORIZED,
)
is_password_verified = Hasher.verify(data.get("password"), member[0].password)
if not is_password_verified:
return Response(
{"message": "Invalid Password, please enter valid password!"},
status=status.HTTP_401_UNAUTHORIZED,
)
expiration_time = datetime.now() + timedelta(hours=2)
payload = {
"exp": expiration_time.timestamp(),
"librarian_id": member[0].id,
"name": member[0].name,
"email": member[0].email,
}
token = jwt.encode(payload, settings.JWT_SECRET, algorithm="HS256")
LoginHistory.objects.create(librarian_id=member[0].id)
key = "auth_session_" + member[0].account_number
request.session[key] = token
return Response({"message": "Login success!"}, status=status.HTTP_200_OK)
class LogoutAsMember(views.APIView):
def get(self, request, pk):
member = Members.objects.get(pk=pk)
key = "auth_session_" + member.account_number
if request.session[key] is None:
return Response(
{"message": "Logout failed, invalid key!"},
status=status.HTTP_400_BAD_REQUEST,
)
del request.session[key]
return Response({"message": "Logout success!"}, status=status.HTTP_200_OK)
class ChangePasswordAsMember(views.APIView):
def post(self, request, pk):
data = request.data
members = Members.objects.all()
member = members.filter(pk=pk, email=data.get("email"))
is_email_exists = member.exists()
new_password = data.get("new_password")
if not is_email_exists:
return Response(
{"message": "Invalid Email, please enter valid email!"},
status=status.HTTP_400_BAD_REQUEST,
)
is_old_password_verified = Hasher.verify(
data.get("password"), member[0].password
)
if not is_old_password_verified:
return Response(
{"message": "Invalid Old Password, please enter valid password!"},
status=status.HTTP_400_BAD_REQUEST,
)
if new_password is None:
return Response(
{"message": "Request failed, new_password is required field!"},
status=status.HTTP_400_BAD_REQUEST,
)
hashed_password = Hasher.encode(new_password)
member.update(password=hashed_password)
return Response(
{"message": "Change password success!"}, status=status.HTTP_200_OK
)
from django.apps import AppConfig
class AuthenticationsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'authentications'
import jwt
from django.shortcuts import get_object_or_404
from librarians.models import Librarians
from django.conf import settings
def get_auth_session(request):
auth_session = request.session.get("auth_session", None)
if auth_session:
decoded = jwt.decode(auth_session, settings.JWT_SECRET, algorithms=["HS256"])
user_id = decoded["librarian_id"]
user_verified = get_object_or_404(Librarians, id=user_id)
return {"user": user_verified}
return {"user": None}
from django import forms
from librarians.models import Librarians
class LoginForm(forms.ModelForm):
class Meta:
model = Librarians
fields = ["email", "password"]
widgets = {
"email": forms.EmailInput(
attrs={
"placeholder": "Email",
"class": "form-control",
}
),
"password": forms.PasswordInput(
attrs={
"placeholder": "Password",
"class": "form-control",
}
),
}
class SignUpForm(forms.ModelForm):
class Meta:
model = Librarians
fields = ["name", "email", "password"]
widgets = {
"name": forms.TextInput(
attrs={
"placeholder": "Name",
"class": "form-control",
}
),
"email": forms.EmailInput(
attrs={
"placeholder": "Email",
"class": "form-control",
}
),
"password": forms.PasswordInput(
attrs={
"placeholder": "Password",
"class": "form-control",
}
),
}
class ForgotPassword(forms.Form):
email = forms.EmailField(
widget=forms.TextInput(
attrs={
"placeholder": "Email",
"class": "form-control",
}
)
)
old_password = forms.CharField(
widget=forms.PasswordInput(
attrs={
"placeholder": "Old Password",
"class": "form-control",
}
)
)
new_password = forms.CharField(
widget=forms.PasswordInput(
attrs={
"placeholder": "New Password",
"class": "form-control",
}
)
)
import jwt
from django.conf import settings
from datetime import datetime, timedelta
from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponseRedirect
import jwt.utils
class AuthMiddleware(MiddlewareMixin):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
auth_session = request.session.get("auth_session", None)
if auth_session is not None:
try:
payload = jwt.decode(
auth_session, settings.JWT_SECRET, algorithms=["HS256"]
)
# refresh token 5 minutes before expired
expired_time = datetime.fromtimestamp(payload["exp"])
near_expired = expired_time - timedelta(minutes=5)
if datetime.now() >= near_expired:
payload["exp"] = payload["exp"] + timedelta(hours=2).total_seconds()
new_token = jwt.encode(
payload, settings.JWT_SECRET, algorithm="HS256"
)
request.session["auth_session"] = new_token
return response
except jwt.ExpiredSignatureError:
del request.session["auth_session"]
return HttpResponseRedirect("/auth/login")
if auth_session is None and request.path.startswith("/dashboard/"):
return HttpResponseRedirect("/auth/login")
elif auth_session is not None and request.path.startswith("/auth/"):
return HttpResponseRedirect("/dashboard/")
else:
return response
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Django Library</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
/>
</head>
<body>
{% block content %}{% endblock content %}
<script
src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"
integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy"
crossorigin="anonymous"
></script>
</body>
</html>
{% extends "base.html" %} {% block content %}
<main
style="min-height: 100vh"
class="w-100 h-100 bg-body-secondary d-flex justify-content-center align-items-center"
>
<form
action="/auth/forgot-password/"
method="POST"
class="card w-25 p-4 rounded-4 shadow"
>
<h1 class="h3 text-center mb-4">Forgot Password</h1>
{% csrf_token %} {% for field in form %}
<div class="form-outline form-white mb-3">{{ field }}</div>
{% endfor %}
<button
type="submit"
id="submit-login"
class="btn btn-primary my-2 rounded-5"
>
Save Changes
</button>
<div class="d-flex flex-column align-items-center">
{% if message %}
<p class="alert alert-success small" role="alert">
<i class="bi bi-check2-circle"></i> {{ message }}
</p>
{% endif %} {% if error_message %}
<p class="alert alert-warning small" role="alert">
<i class="bi bi-exclamation-circle"></i> {{ error_message }}
</p>
{% endif %}
<p class="my-2">
Back to <a href="/auth/login">Login</a> or
<a href="/auth/sign-up">Sign Up</a>
</p>
</div>
</form>
</main>
{% endblock content %}
{% extends "base.html" %} {% block content %}
<main
style="min-height: 100vh"
class="w-100 h-100 bg-body-secondary d-flex justify-content-center align-items-center"
>
<form
action="/auth/login/"
method="POST"
class="card w-25 p-4 rounded-4 shadow"
>
<h1 class="h3 text-center mb-4">Login</h1>
{% csrf_token %} {% for field in form %}
<div class="form-outline form-white mb-3">{{ field }}</div>
{% endfor %}
<button
type="submit"
id="submit-login"
class="btn btn-primary my-2 rounded-5"
>
Login
</button>
<div class="d-flex flex-column align-items-center">
{% if error_message %}
<p class="alert alert-warning small" role="alert">
<i class="bi bi-exclamation-circle"></i> {{ error_message }}
</p>
{% endif %}
<p class="my-2">
Don't have an account? <a href="/auth/sign-up">Sign Up</a>
</p>
<a href="/auth/forgot-password/" class="my-2">Forgot password</a>
</div>
</form>
</main>
{% endblock content %}
{% extends "base.html" %} {% block content %}
<main
style="min-height: 100vh"
class="w-100 h-100 bg-body-secondary d-flex justify-content-center align-items-center"
>
<form
action="/auth/sign-up/"
method="POST"
class="card w-25 p-4 rounded-4 shadow"
>
<h1 class="h3 text-center mb-4">Sign Up</h1>
{% csrf_token %} {% for field in form %}
<div class="form-outline form-white mb-3">{{ field }}</div>
{% endfor %}
<button
type="submit"
id="submit-sign-up"
class="btn btn-primary my-2 rounded-5"
>
Login
</button>
<div class="d-flex flex-column align-items-center">
{% if error_message %}
<p class="alert alert-warning small" role="alert">
<i class="bi bi-exclamation-circle"></i> {{ error_message }}
</p>
{% endif %}
<p class="my-2">
Already have an account? <a href="/auth/login">Login</a>
</p>
</div>
</form>
</main>
{% endblock content %}
from django.urls import path
from authentications.views import AuthView
urlpatterns = [
path("login/", AuthView.login, name="login"),
path("sign-up/", AuthView.sign_up, name="sign_up"),
path("logout/", AuthView.logout, name="logout"),
path("forgot-password/", AuthView.forgot_password, name="forgot_password"),
]
import jwt
import bcrypt
from django.conf import settings
def create_auth_session(request, payload):
token = jwt.encode(payload, settings.JWT_SECRET, algorithm="HS256")
request.session["auth_session"] = token
class Hasher:
def encode(password: str):
hashed_password = bcrypt.hashpw(
password.encode("utf-8"), bcrypt.gensalt(rounds=8)
)
return hashed_password
def verify(password: str, encoded: str):
hashed_password = encoded[2:].replace("'", "").encode("utf-8")
password_encode = password.encode("utf-8")
is_verified = bcrypt.checkpw(
password=password_encode, hashed_password=hashed_password
)
return is_verified
from datetime import timedelta, datetime
from django.views.generic import TemplateView
from django.http import HttpResponseRedirect
from django.shortcuts import render
from authentications.forms import LoginForm, SignUpForm, ForgotPassword
from librarians.models import Librarians, LoginHistory
from authentications.utils import create_auth_session, Hasher
class AuthView(TemplateView):
def login(request):
librarian = Librarians.objects.all()
context = {"form": LoginForm()}
if request.method == "POST":
form = LoginForm(request.POST)
if form.is_valid():
account = librarian.filter(email=form.data["email"])
password = form.data["password"]
if account.exists():
librarian = librarian.get(email=form.data["email"])
verified = Hasher.verify(
password=password, encoded=librarian.password
)
if not verified:
context["error_message"] = (
"Password invalid, please enter valid data or Sign Up first"
)
else:
expiration_time = datetime.now() + timedelta(hours=2)
payload = {
"exp": expiration_time.timestamp(),
"librarian_id": librarian.id,
"name": librarian.name,
"email": librarian.email,
}
create_auth_session(request, payload)
LoginHistory.objects.create(librarian_id=librarian.id)
return HttpResponseRedirect("/dashboard/")
else:
context["error_message"] = (
"Email invalid, please enter valid data or Sign Up first"
)
else:
form = LoginForm()
return render(request, "login.html", context)
def sign_up(request):
librarian = Librarians.objects.all()
context = {"form": SignUpForm()}
if request.method == "POST":
form = SignUpForm(request.POST)
if form.is_valid():
is_email = librarian.filter(email=form.data["email"])
if is_email.exists():
context["error_message"] = (
"Email was already exist, please use different email"
)
else:
password = form.data["password"]
hashed_password = Hasher.encode(password=password)
librarian.create(
name=form.data["name"],
email=form.data["email"],
password=hashed_password,
)
new_librarian = librarian.get(
name=form.data["name"],
email=form.data["email"],
)
expiration_time = datetime.now() + timedelta(minutes=30)
payload = {
"exp": expiration_time.timestamp(),
"librarian_id": new_librarian.id,
"name": new_librarian.name,
"email": new_librarian.email,
}
create_auth_session(request, payload)
LoginHistory.objects.create(librarian_id=new_librarian.id)
return HttpResponseRedirect("/dashboard/")
else:
form = SignUpForm()
return render(request, "sign_up.html", context)
def logout(request):
del request.session["auth_session"]
return HttpResponseRedirect("/auth/login")
def forgot_password(request):
librarian = Librarians.objects.all()
context = {"form": ForgotPassword()}
if request.method == "POST":
form = ForgotPassword(request.POST)
if form.is_valid:
account = librarian.filter(
email=form.data["email"], password=form.data["old_password"]
)
if account.exists():
if form.data["old_password"] == form.data["new_password"]:
context["error_message"] = (
"Old and New password cannot be same!"
)
else:
try:
librarian.update(password=form.data["new_password"])
context["message"] = (
"Change password success, now lets try login!"
)
except:
context["error_message"] = (
"Change password failed, please try again later."
)
else:
context["error_message"] = (
"Account with email and old password not found, please enter valid data!"
)
return render(request, "forgot-password.html", context)
from django.contrib import admin
from book_loans.models import BookLoans
admin.site.register(BookLoans)
from django.apps import AppConfig
class BookLoansConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'book_loans'
from django import forms
from book_loans.models import BookLoans
class BookLoanForm(forms.ModelForm):
class Meta:
model = BookLoans
fields = [
"book",
"member",
"librarian",
"loan_date",
"due_date",
"return_date",
"notes",
]
widgets = {
"book": forms.Select(
attrs={
"class": "form-control",
}
),
"member": forms.Select(
attrs={
"class": "form-control",
}
),
"librarian": forms.Select(
attrs={
"class": "form-control",
}
),
"loan_date": forms.DateTimeInput(
attrs={
"type": "datetime-local",
"class": "form-control",
}
),
"due_date": forms.DateTimeInput(
attrs={
"type": "datetime-local",
"class": "form-control",
}
),
"return_date": forms.DateTimeInput(
attrs={
"type": "datetime-local",
"class": "form-control",
}
),
"notes": forms.Textarea(
attrs={
"placeholder": "Note",
"class": "form-control",
}
),
}
# Generated by Django 4.2 on 2024-06-26 07:38
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('members', '0001_initial'),
('books', '0001_initial'),
('librarians', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='BookLoans',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('notes', models.TextField(blank=True, null=True)),
('loan_date', models.DateTimeField()),
('due_date', models.DateTimeField()),
('return_date', models.DateTimeField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='books.book')),
('issued_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='librarians.librarians')),
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='members.members')),
],
options={
'db_table': 'book_loans',
},
),
]
# Generated by Django 4.2 on 2024-06-26 07:41
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('members', '0001_initial'),
('books', '0001_initial'),
('book_loans', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='bookloans',
name='book',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='books.book'),
),
migrations.AlterField(
model_name='bookloans',
name='member',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='members.members'),
),
]
# Generated by Django 4.2 on 2024-06-26 07:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('book_loans', '0002_alter_bookloans_book_alter_bookloans_member'),
]
operations = [
migrations.RenameField(
model_name='bookloans',
old_name='issued_by',
new_name='librarians',
),
]
# Generated by Django 4.2 on 2024-07-03 05:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('book_loans', '0003_rename_issued_by_bookloans_librarians'),
]
operations = [
migrations.AlterField(
model_name='bookloans',
name='return_date',
field=models.DateTimeField(blank=True, null=True),
),
]
# Generated by Django 5.0.6 on 2024-07-05 02:14
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('book_loans', '0004_alter_bookloans_return_date'),
('books', '0002_book_stock'),
('librarians', '0003_alter_loginhistory_table'),
('members', '0006_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='bookloans',
name='book',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='books.book'),
),
migrations.AlterField(
model_name='bookloans',
name='librarians',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='librarians.librarians'),
),
migrations.AlterField(
model_name='bookloans',
name='member',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='members.members'),
),
]
# Generated by Django 5.0.6 on 2024-07-09 07:15
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('book_loans', '0005_alter_bookloans_book_alter_bookloans_librarians_and_more'),
]
operations = [
migrations.RenameField(
model_name='bookloans',
old_name='librarians',
new_name='librarian',
),
]
from django.db import models
from books.models import Book
from members.models import Members
from librarians.models import Librarians
class BookLoans(models.Model):
book = models.ForeignKey(to=Book, on_delete=models.CASCADE, null=True)
member = models.ForeignKey(to=Members, on_delete=models.CASCADE, null=True)
librarian = models.ForeignKey(to=Librarians, on_delete=models.CASCADE, null=True)
notes = models.TextField(blank=True, null=True)
loan_date = models.DateTimeField()
due_date = models.DateTimeField()
return_date = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "book_loans"
<table class="table table-hover">
<thead>
<tr class="table-primary">
<th scope="col">Book</th>
<th scope="col">Member</th>
<th scope="col">Librarian</th>
<th scope="col">Loan Date</th>
<th scope="col">Due Date</th>
<th scope="col">Return Date</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% if object_list %} {% for loan in object_list %}
<tr>
<td>{{ loan.book.title }}</td>
<td>{{ loan.member.name }}</td>
<td>{{ loan.librarian.name }}</td>
<td>{{ loan.loan_date }}</td>
<td>{{ loan.due_date }}</td>
<td>{{ loan.return_date }}</td>
<td class="d-flex gap-2 py-4">
<a class="btn btn-success" href="/dashboard/book-loans/{{ loan.id }}/">
<i class="bi bi-pencil-square"></i>
</a>
<a
class="btn btn-danger"
href="/dashboard/book-loans/{{ loan.id }}/delete/"
>
<i class="bi bi-trash3-fill"></i>
</a>
</td>
</tr>
{% endfor %} {% else %}
<tr class="w-100">
<td></td>
<td></td>
<td></td>
<td>
<p>Data Empty</p>
</td>
<td></td>
<td></td>
<td></td>
</tr>
{% endif %}
</tbody>
</table>
{% include "pagination.html" %}
{% extends "layout.html" %} {% block dashboard %}
<div class="w-100 p-4">
<div class="d-flex gap-2 mb-4">
{% include "order_form.html" %}
<a type="button" class="btn btn-primary" href="/dashboard/book-loans/add/">
<i class="bi bi-plus-circle"></i> Add Book Loan
</a>
{% include "search_form.html" %}
</div>
{% include "book_loan_table_data.html" %}
</div>
{% endblock dashboard %}
from django.urls import path
from book_loans.views import (
BookLoanListView,
BookLoanCreateView,
BookLoanUpdateView,
BookLoanDeleteView,
)
urlpatterns = [
path("", BookLoanListView.as_view(), name="book_loan_lists"),
path("add/", BookLoanCreateView.as_view(), name="add_book_loan"),
path("<int:pk>/", BookLoanUpdateView.as_view(), name="update_book_loan"),
path("<int:pk>/delete/", BookLoanDeleteView.as_view(), name="delete_book_loan"),
]
from django.db.models import Q
from django.views import generic
from book_loans.models import BookLoans
from book_loans.forms import BookLoanForm
class BookLoanListView(generic.ListView):
model = BookLoans
template_name = "loans.html"
paginate_by = 5
def get_queryset(self):
queryset = super().get_queryset()
keyword = self.request.GET.get("q")
order = self.request.GET.get("o")
if keyword:
queryset = queryset.filter(
Q(book__title__icontains=keyword)
| Q(member__name__icontains=keyword)
| Q(librarian__name__icontains=keyword)
).order_by("-created_at")
if order:
if order == "new":
queryset = queryset.order_by("-created_at")
elif order == "old":
queryset = queryset.order_by("created_at")
return queryset.order_by("-updated_at")
class BookLoanCreateView(generic.edit.CreateView):
model = BookLoans
form_class = BookLoanForm
success_url = "/dashboard/book-loans/"
template_name = "form/create_form.html"
class BookLoanUpdateView(generic.edit.UpdateView):
model = BookLoans
form_class = BookLoanForm
success_url = "/dashboard/book-loans"
template_name = "form/update_form.html"
class BookLoanDeleteView(generic.edit.DeleteView):
model = BookLoans
success_url = "/dashboard/book-loans"
template_name = "form/delete_form.html"
from django.contrib import admin
from books.models import Book
admin.site.register(Book)
from django.apps import AppConfig
class BooksConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'books'
from django import forms
from books.models import Book
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = [
"cover_image",
"title",
"stock",
"category",
"description",
"published_year",
]
widgets = {
"cover_image": forms.FileInput(
attrs={
"placeholder": "Cover Image",
"class": "form-control",
}
),
"title": forms.TextInput(
attrs={
"placeholder": "Title",
"class": "form-control",
}
),
"stock": forms.TextInput(
attrs={
"type": "number",
"placeholder": "Stock",
"class": "form-control",
}
),
"category": forms.Select(
attrs={
"class": "form-control",
}
),
"description": forms.Textarea(
attrs={
"placeholder": "Description",
"class": "form-control",
}
),
"published_year": forms.TextInput(
attrs={
"type": "number",
"class": "form-control",
}
),
}
# Generated by Django 4.2 on 2024-06-26 07:28
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Book',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('description', models.CharField(max_length=255)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'book',
},
),
]
# Generated by Django 4.2 on 2024-07-03 08:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='book',
name='stock',
field=models.BigIntegerField(blank=True, null=True),
),
]
# Generated by Django 5.0.6 on 2024-07-09 12:03
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0002_book_stock'),
('categories', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='book',
name='category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='categories.category'),
),
]
# Generated by Django 5.0.6 on 2024-07-10 04:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0003_book_category'),
]
operations = [
migrations.AddField(
model_name='book',
name='cover_image',
field=models.ImageField(blank=True, null=True, upload_to='uploads'),
),
]
# Generated by Django 5.0.6 on 2024-07-10 05:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0004_book_cover_image'),
]
operations = [
migrations.AlterField(
model_name='book',
name='cover_image',
field=models.ImageField(blank=True, null=True, upload_to=''),
),
]
# Generated by Django 5.0.6 on 2024-07-10 05:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0005_alter_book_cover_image'),
]
operations = [
migrations.AlterField(
model_name='book',
name='cover_image',
field=models.ImageField(blank=True, null=True, upload_to='uploads'),
),
]
# Generated by Django 5.0.6 on 2024-07-10 05:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0006_alter_book_cover_image'),
]
operations = [
migrations.AlterField(
model_name='book',
name='cover_image',
field=models.ImageField(blank=True, null=True, upload_to='media/uploads'),
),
]
# Generated by Django 5.0.6 on 2024-07-10 05:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0007_alter_book_cover_image'),
]
operations = [
migrations.AlterField(
model_name='book',
name='cover_image',
field=models.ImageField(blank=True, null=True, upload_to='uploads'),
),
]
# Generated by Django 5.0.6 on 2024-07-10 06:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0008_alter_book_cover_image'),
]
operations = [
migrations.AlterField(
model_name='book',
name='description',
field=models.CharField(blank=True, max_length=255, null=True),
),
]
# Generated by Django 5.0.6 on 2024-07-10 07:32
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0009_alter_book_description'),
]
operations = [
migrations.AddField(
model_name='book',
name='published_year',
field=models.PositiveIntegerField(blank=True, help_text='Use the following format: <YYYY>', null=True, validators=[django.core.validators.MinValueValidator(1900), django.core.validators.MaxValueValidator(2024)]),
),
]
# Generated by Django 5.0.6 on 2024-07-10 10:30
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0010_book_published_year'),
]
operations = [
migrations.AlterField(
model_name='book',
name='published_year',
field=models.PositiveIntegerField(blank=True, help_text='E.g: 2024', null=True, validators=[django.core.validators.MinValueValidator(1900), django.core.validators.MaxValueValidator(2024)]),
),
]
from datetime import datetime
from django.db import models
from categories.models import Category
from django.core.validators import MinValueValidator, MaxValueValidator
class Book(models.Model):
title = models.CharField(max_length=100)
description = models.CharField(max_length=255, blank=True, null=True)
stock = models.BigIntegerField(blank=True, null=True)
category = models.ForeignKey(
to=Category, on_delete=models.CASCADE, blank=True, null=True
)
cover_image = models.ImageField(upload_to="uploads", blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published_year = models.PositiveIntegerField(
validators=[MinValueValidator(1900), MaxValueValidator(datetime.now().year)],
help_text="E.g: 2024",
blank=True,
null=True,
)
def __str__(self):
return self.title
class Meta:
db_table = "book"
{% extends "layout.html" %} {% block dashboard %}
<div style="max-width: 80vw" class="w-100 p-4">
<div class="d-flex justify-content-between pb-4">
<div class="d-flex gap-2 pb-4">
<a class="btn btn-success" href="/dashboard/books/{{ book.id }}/update">
<i class="bi bi-pencil-square"></i> Edit
</a>
<a class="btn btn-danger" href="/dashboard/books/{{ book.id }}/delete/">
<i class="bi bi-trash3-fill"></i> Delete
</a>
</div>
</div>
{% if book.cover_image %}
<div class="d-flex gap-5">
<img
class="object-fit-contain"
height="360"
src="{{ book.cover_image.url }}"
alt="{{ book.title }}"
/>
<div class="col">
<h1 class="h2 row">{{ book.title }}</h1>
<p class="h5 row">{{ book.description }}</p>
<p class="h5 row">Stock: {{ book.stock }}</p>
<p class="row badge text-bg-secondary">{{ book.category.name }}</p>
<time datetime="{{ book.created_at }}" class="row fs-6"
>Created at: {{ book.created_at }}</time
>
<time datetime="{{ book.updated_at }}" class="row fs-6"
>Updated at: {{ book.updated_at }}</time
>
</div>
</div>
{% endif %}
</div>
{% endblock dashboard %}
<table class="table table-hover">
<thead>
<tr class="table-primary">
<th scope="col">Title</th>
<th scope="col">Category</th>
<th scope="col">Stock</th>
<th scope="col">Description</th>
<th scope="col">Year</th>
<th scope="col">Created At</th>
<th scope="col">Updated At</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% if object_list %} {% for book in object_list %}
<tr>
<td><a href="/dashboard/books/{{ book.id }}/">{{ book.title }}</a></td>
<td>{{ book.category.name }}</td>
<td>{{ book.stock }}</td>
<td>{{ book.description }}</td>
<td>{{ book.published_year }}</td>
<td>{{ book.created_at }}</td>
<td>{{ book.updated_at }}</td>
<td>
<div class="d-flex gap-2">
<a
class="btn btn-success"
href="/dashboard/books/{{ book.id }}/update"
>
<i class="bi bi-pencil-square"></i>
</a>
<a
class="btn btn-danger"
href="/dashboard/books/{{ book.id }}/delete/"
>
<i class="bi bi-trash3-fill"></i>
</a>
</div>
</td>
</tr>
{% endfor %} {% else %}
<tr class="w-100">
<td></td>
<td></td>
<td></td>
<td>
<p>Data Empty</p>
</td>
<td></td>
<td></td>
<td></td>
</tr>
{% endif %}
</tbody>
</table>
{% include "pagination.html" %}
{% extends "layout.html" %} {% block dashboard %}
<div style="max-width: 80vw" class="w-100 p-4">
<div class="d-flex gap-2 pb-4">
{% include "order_form.html" %}
<a type="button" class="btn btn-primary" href="/dashboard/books/add">
<i class="bi bi-plus-circle"></i> Add Book
</a>
{% include "search_form.html" %}
</div>
{% include "book_table_data.html" %}
</div>
{% endblock dashboard %}
from django.urls import path
from books.views import (
BookListView,
BookDetailView,
BookCreateView,
BookUpdateView,
BookDeleteView,
)
urlpatterns = [
path("", BookListView.as_view(), name="book_list"),
path("add/", BookCreateView.as_view(), name="book_add"),
path("<int:pk>/", BookDetailView.as_view(), name="book_detail"),
path("<int:pk>/update/", BookUpdateView.as_view(), name="book_update"),
path("<int:pk>/delete/", BookDeleteView.as_view(), name="book_delete"),
]
from django.db.models import Q
from django.views import generic
from books.models import Book
from books.forms import BookForm
class BookListView(generic.ListView):
model = Book
template_name = "books.html"
paginate_by = 5
def get_queryset(self):
queryset = super().get_queryset()
keyword = self.request.GET.get("q")
order = self.request.GET.get("o")
if keyword:
queryset = queryset.filter(
Q(title__icontains=keyword)
| Q(category__name__icontains=keyword)
| Q(description__icontains=keyword)
| Q(published_year__icontains=keyword)
).order_by("-created_at")
if order:
if order == "new":
queryset = queryset.order_by("-created_at")
elif order == "old":
queryset = queryset.order_by("created_at")
return queryset
class BookDetailView(generic.DeleteView):
model = Book
template_name = "book_detail.html"
context_object_name = "book"
class BookCreateView(generic.edit.CreateView):
model = Book
form_class = BookForm
success_url = "/dashboard/books/"
template_name = "form/create_form.html"
class BookUpdateView(generic.edit.UpdateView):
model = Book
form_class = BookForm
success_url = "/dashboard/books"
template_name = "form/update_form.html"
class BookDeleteView(generic.edit.DeleteView):
model = Book
success_url = "/dashboard/books"
template_name = "form/delete_form.html"
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class CategoriesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'categories'
from django import forms
from categories.models import Category
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = ["name", "description"]
widgets = {
"name": forms.TextInput(
attrs={
"placeholder": "Title",
"class": "form-control",
}
),
"description": forms.Textarea(
attrs={
"placeholder": "Description",
"class": "form-control",
}
),
}
# Generated by Django 5.0.6 on 2024-07-09 11:54
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('description', models.CharField(blank=True, max_length=255, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'categories',
},
),
]
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=255, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Meta:
db_table = "categories"
{% extends "layout.html" %} {% block dashboard %}
<div style="max-width: 80vw" class="w-100 p-4">
<div class="d-flex gap-2 pb-4">
{% include "order_form.html" %}
<a type="button" class="btn btn-primary" href="/dashboard/categories/add">
<i class="bi bi-plus-circle"></i> Add Category
</a>
{% include "search_form.html" %}
</div>
{% include "categories_table_data.html" %}
</div>
{% endblock dashboard %}
<table class="table table-hover">
<thead>
<tr class="table-primary">
<th scope="col">Name</th>
<th scope="col">Description</th>
<th scope="col">Created At</th>
<th scope="col">Updated At</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% if object_list %} {% for category in object_list %}
<tr>
<td>{{ category.name }}</td>
<td>{{ category.description }}</td>
<td>{{ category.created_at }}</td>
<td>{{ category.updated_at }}</td>
<td class="d-flex gap-2">
<a
class="btn btn-success"
href="/dashboard/categories/{{ category.id }}/"
>
<i class="bi bi-pencil-square"></i>
</a>
<a
class="btn btn-danger"
href="/dashboard/categories/{{ category.id }}/delete/"
>
<i class="bi bi-trash3-fill"></i>
</a>
</td>
</tr>
{% endfor %} {% else %}
<tr class="w-100">
<td></td>
<td></td>
<td>
<p>Data Empty</p>
</td>
<td></td>
<td></td>
</tr>
{% endif %}
</tbody>
</table>
{% include "pagination.html" %}
from django.urls import path
from categories.views import (
CategoryListView,
CategoryCreateView,
CategoryUpdateView,
CategoryDeleteView,
)
urlpatterns = [
path("", CategoryListView.as_view(), name="category_list"),
path("add/", CategoryCreateView.as_view(), name="category_update"),
path("<int:pk>/", CategoryUpdateView.as_view(), name="category_update"),
path("<int:pk>/delete/", CategoryDeleteView.as_view(), name="category_delete"),
]
from django.db.models import Q
from django.views import generic
from categories.models import Category
from categories.forms import CategoryForm
class CategoryListView(generic.ListView):
model = Category
template_name = "categories.html"
paginate_by = 5
def get_queryset(self):
queryset = super().get_queryset()
keyword = self.request.GET.get("q")
order = self.request.GET.get("o")
if keyword:
queryset = queryset.filter(
Q(name__icontains=keyword) | Q(description__icontains=keyword)
).order_by("-created_at")
if order:
if order == "new":
queryset = queryset.order_by("-created_at")
elif order == "old":
queryset = queryset.order_by("created_at")
return queryset.order_by("-updated_at")
class CategoryCreateView(generic.edit.CreateView):
model = Category
form_class = CategoryForm
success_url = "/dashboard/categories/"
template_name = "form/create_form.html"
class CategoryUpdateView(generic.edit.UpdateView):
model = Category
form_class = CategoryForm
success_url = "/dashboard/categories/"
template_name = "form/update_form.html"
class CategoryDeleteView(generic.edit.DeleteView):
model = Category
success_url = "/dashboard/categories/"
template_name = "form/delete_form.html"
"""
ASGI config for library_django 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.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_asgi_application()
"""
Django settings for library_django project.
Generated by 'django-admin startproject' using Django 5.0.6.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
import os
from pathlib import Path
from dotenv import load_dotenv
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# .env
load_dotenv()
JWT_SECRET = os.getenv("JWT_SECRET", default="")
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = JWT_SECRET
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
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",
# 3rd party
"rest_framework",
"rest_framework.authtoken",
"django_filters",
"allauth",
"allauth.account",
"allauth.socialaccount",
"dj_rest_auth",
"dj_rest_auth.registration",
# local
"api.apps.ApiConfig",
"users.apps.UsersConfig",
"books.apps.BooksConfig",
"categories.apps.CategoriesConfig",
"members.apps.MembersConfig",
"book_loans.apps.BookLoansConfig",
"librarians.apps.LibrariansConfig",
"dashboards.apps.DashboardsConfig",
"authentications.apps.AuthenticationsConfig",
]
# 3rd party config
# DJANGO REST FRAMEWORK CONFIG
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
}
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",
# 3rd party
"allauth.account.middleware.AccountMiddleware",
# local
"authentications.middleware.AuthMiddleware",
]
ROOT_URLCONF = "config.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"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",
"django.template.context_processors.media",
# local
"authentications.context_processors.get_auth_session",
],
},
},
]
WSGI_APPLICATION = "config.wsgi.application"
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": os.getenv("DB_NAME"),
"USER": os.getenv("DB_USER"),
"PASSWORD": os.getenv("DB_PASSWORD"),
"HOST": os.getenv("DB_HOST"),
"PORT": os.getenv("DB_PORT"),
}
}
# Password validation
# https://docs.djangoproject.com/en/5.0/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.0/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "Asia/Jakarta"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}
# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
"""
URL configuration for library_django project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.0/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.conf import settings
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
from dashboards.views import HomePage
from django.conf.urls.static import static
urlpatterns = [
path("", HomePage.as_view(), name="homepage"),
path("dashboard/", include("dashboards.urls")),
path("admin/", admin.site.urls),
path("auth/", include("authentications.urls")),
# API
path("api/v1/", include("api.urls")),
path("api/v1/auth/", include("dj_rest_auth.urls")),
path(
"api/v1/auth/registration/",
include("dj_rest_auth.registration.urls"),
name="register",
),
path("api-auth/", include("rest_framework.urls")),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
"""
WSGI config for library_django 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.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class DashboardsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'dashboards'
from django.db import models
{% extends "layout.html" %} {% block dashboard %}
<div style="max-width: 80vw" class="w-100 p-4 d-flex flex-column gap-4">
<h1 class="h2">Reports</h1>
<div class="row card">{% include "dashboard/summary.html" %}</div>
<div class="row card">{% include "dashboard/overdue_loan.html" %}</div>
<div class="row card">{% include "dashboard/near_overdue_loan.html" %}</div>
<div class="row card">{% include "dashboard/login_history.html" %}</div>
</div>
{% endblock dashboard %}
<div class="card-header">
<h2 class="h4">Librarian Login History</h2>
</div>
<div class="card-body">
<table class="table table-striped">
<thead>
<tr class="table-primary">
<th scope="col">Name</th>
<th scope="col">Login At</th>
</tr>
</thead>
<tbody>
{% for histori in login_histories %}
<tr>
<td>{{ histori.librarian.name }}</td>
<td>{{ histori.login_at }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-header d-flex justify-content-between">
<h2 class="h4">Near Outstanding Book Loan</h2>
<a href="/dashboard/upcoming-loans/" class="btn btn-primary"
>See All Upcoming Loans</a
>
</div>
<div class="card-body">
<table class="table table-striped">
<thead>
<tr class="table-primary">
<th scope="col">Book Title</th>
<th scope="col">Due Date</th>
<th scope="col">Loan Date</th>
</tr>
</thead>
<tbody>
{% if upcoming_loans %} {% for loan in upcoming_loans %}
<tr>
<td>{{ loan.book.title }}</td>
<td>{{ loan.due_date }}</td>
<td>{{ loan.loan_date }}</td>
</tr>
{% endfor %} {% else %}
<tr>
<td>No Data</td>
<td></td>
<td></td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<div class="card-header d-flex justify-content-between">
<h2 class="h4">Overdued Book Loan</h2>
<a href="/dashboard/overdued-loans/" class="btn btn-primary"
>See All Overdued Loans</a
>
</div>
<div class="card-body">
<table class="table table-striped">
<thead>
<tr class="table-primary">
<th scope="col">Book Title</th>
<th scope="col">Due Date</th>
<th scope="col">Loan Date</th>
</tr>
</thead>
<tbody>
{% if overdue_loans %} {% for loan in overdue_loans %}
<tr>
<td>{{ loan.book.title }}</td>
<td>{{ loan.due_date }}</td>
<td>{{ loan.loan_date }}</td>
</tr>
{% endfor %} {% else %}
<tr>
<td>No Data</td>
<td></td>
<td></td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<div class="card-header">
<h3 class="h4">At a Glance</h3>
</div>
<div class="card-body row container text-center">
<div class="col">
<div class="card h-100 d-flex flex-column">
<div class="card-body row align-content-center">
<h5 class="card-title"><i class="bi bi-book-half"></i> Total Book</h5>
<p class="h1">{{ total_book }}</p>
</div>
<div class="card-footer">
<a href="/dashboard/books/" class="w-100 btn btn-primary"
>Explore Book</a
>
</div>
</div>
</div>
<div class="col">
<div class="card h-100 d-flex flex-column">
<div class="card-body row align-content-center">
<h5 class="card-title">
<i class="bi bi-book-half"></i> Total Category
</h5>
<p class="h1">{{ total_category }}</p>
</div>
<div class="card-footer">
<a href="/dashboard/books/" class="w-100 btn btn-primary"
>Explore Categories</a
>
</div>
</div>
</div>
<div class="col">
<div class="card h-100">
<div class="card-body row align-content-center">
<h5 class="card-title">
<i class="bi bi-person-vcard"></i> Total Member
</h5>
<p class="h1">{{ total_member }}</p>
</div>
<div class="card-footer">
<a href="/dashboard/members/" class="w-100 btn btn-primary"
>Go to Member</a
>
</div>
</div>
</div>
<div class="col">
<div class="card h-100">
<div class="card-body row align-content-center">
<h5 class="card-title">
<i class="bi bi-calendar-week"></i> Book Loan
</h5>
<div class="d-flex justify-content-center gap-2">
<div class="card">
<div class="card-header">Total</div>
<p class="h1">{{ total_book_loans }}</p>
</div>
<div class="card">
<div class="card-header">Upcoming</div>
<p class="h1">{{ total_upcoming }}</p>
</div>
<div class="card">
<div class="card-header">Overdue</div>
<p class="h1">{{ total_overdue }}</p>
</div>
</div>
</div>
<div class="card-footer">
<a href="/dashboard/book-loans/" class="w-100 btn btn-primary"
>Go to Book Loans</a
>
</div>
</div>
</div>
</div>
{% extends "layout.html" %} {% block dashboard %}
<div style="max-width: 80vw" class="w-100 p-4">
<div class="d-flex flex-column gap-2 mb-4">
<h1 class="h3">Add Data</h1>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="d-flex flex-column gap-1">{{ form }}</div>
<div class="d-flex gap-2 my-3">
<a href="javascript:window.history.back()" class="btn btn-secondary"
>Cancel</a
>
<button class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
{% endblock dashboard %}
{% extends "layout.html" %} {% block dashboard %}
<div style="max-width: 80vw" class="w-100 p-4">
<div class="d-flex flex-column gap-2 mb-4">
<h1 class="h3">Are you sure want to delete this data
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="d-flex flex-column gap-1">Once data is deleted, it cannot be restored.</div>
<div class="d-flex gap-2 my-3">
<a href="javascript:window.history.back()" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-danger">Continue</button>
</div>
</form>
</div>
</div>
{% endblock dashboard %}
{% extends "layout.html" %} {% block dashboard %}
<div style="max-width: 80vw" class="w-100 p-4">
<div class="d-flex flex-column gap-2 mb-4">
<h1 class="h3">Update Data</h1>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="d-flex flex-column gap-1">{{ form }}</div>
<div class="d-flex gap-2 my-3">
<a href="javascript:window.history.back()" class="btn btn-secondary"
>Cancel</a
>
<button class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
{% endblock dashboard %}
{% extends "base.html" %} {% block content %}
<main
style="min-height: 100vh"
class="w-100 h-100 bg-body-secondary d-flex flex-column justify-content-center align-items-center"
>
<h2 class="h3">Welcome to</h2>
<h1 class="h1">Django Library Management System</h1>
<section
class="d-flex flex-column justify-content-center align-items-center my-3 w-100"
>
<h4 class="h4">Let's get started</h4>
<div class="d-flex gap-2 my-2">
<a href="/auth/login" class="btn btn-outline-primary">Login</a>
<a href="/auth/sign-up" class="btn btn-primary">Sign Up</a>
</div>
</section>
</main>
{% endblock content %}
{% extends "base.html" %} {% block content %}
<main
style="min-height: 100vh"
class="w-100 h-100 bg-body-secondary d-flex justify-content-end"
>
<div
class="w-100 p-4 d-flex flex-column gap-4 bg-dark"
style="max-width: 20vw"
>
{% include "profile.html" %}
<div class="d-flex flex-column gap-2">
<a
href="/dashboard"
class="btn {% if request.path == '/dashboard/' %}btn-primary{% else %}btn-outline-primary border-white text-white{% endif %} text-start w-100"
><i class="bi bi-columns-gap"></i> Dashboard</a
>
<a
href="/dashboard/books"
class="btn {% if request.path == '/dashboard/books/' %}btn-primary{% else %}btn-outline-primary border-white text-white{% endif %} text-start w-100"
><i class="bi bi-book-half"></i> Books</a
>
<a
href="/dashboard/categories"
class="btn {% if request.path == '/dashboard/categories/' %}btn-primary{% else %}btn-outline-primary border-white text-white{% endif %} text-start w-100"
><i class="bi bi-tags-fill"></i> Categories</a
>
<a
href="/dashboard/members"
class="btn {% if request.path == '/dashboard/members/' %}btn-primary{% else %}btn-outline-primary border-white text-white{% endif %} text-start w-100"
><i class="bi bi-person-vcard"></i> Members</a
>
<a
href="/dashboard/librarians"
class="btn {% if request.path == '/dashboard/librarians/' %}btn-primary{% else %}btn-outline-primary border-white text-white{% endif %} text-start w-100"
><i class="bi bi-person-fill-lock"></i> Librarian</a
>
<a
href="/dashboard/book-loans"
class="btn {% if request.path == '/dashboard/book-loans/' %}btn-primary{% else %}btn-outline-primary border-white text-white{% endif %} text-start w-100"
><i class="bi bi-calendar-week"></i> Book Loans</a
>
<a
href="/dashboard/upcoming-loans/"
class="btn {% if request.path == '/dashboard/upcoming-loans/' %}btn-primary{% else %}btn-outline-primary border-white text-white{% endif %} text-start w-100"
><i class="bi bi-calendar2-event"></i> Upcoming Loans</a
>
<a
href="/dashboard/overdued-loans/"
class="btn {% if request.path == '/dashboard/overdued-loans/' %}btn-primary{% else %}btn-outline-primary border-white text-white{% endif %} text-start w-100"
><i class="bi bi-calendar2-x"></i> Overdued Loans</a
>
</div>
</div>
<div style="max-height: 100vh" class="w-100 overflow-y-auto">
{% block dashboard %}{% endblock dashboard %}
</div>
</main>
{% endblock content %}
<div class="dropdown">
<button
type="button"
class="btn btn-secondary dropdown-toggle"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i class="bi bi-arrow-down-up"></i> Order By
</button>
<ul class="dropdown-menu shadow">
<li>
<form action="" method="get" class="d-flex gap-2">
<input name="o" value="new" hidden />
<button type="submit" class="dropdown-item" href="#">Newest</button>
</form>
</li>
<li>
<form action="" method="get" class="d-flex gap-2">
<input name="o" value="old" hidden />
<button type="submit" class="dropdown-item" href="#">Oldest</button>
</form>
</li>
</ul>
</div>
{% if is_paginated %}
<nav aria-label="Page navigation example">
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}"
>Previous</a
>
</li>
{% else %}
<li class="page-item">
<a class="page-link disabled" href="#">Previous</a>
</li>
{% endif %} {% for index in page_obj.paginator.page_range %}
<li class="page-item {% if page_obj.number == index %}active{% endif %}">
<a class="page-link" href="?page={{ index }}">{{ index }}</a>
</li>
{% endfor %} {% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}"
>Next</a
>
</li>
{% else %}
<li class="page-item">
<a class="page-link disabled" href="#">Next</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
<div class="d-flex flex-column gap-2">
<button
class="w-100 btn btn-primary text-start d-flex justify-content-center gap-2"
type="button"
data-bs-toggle="dropdown"
>
<i class="bi bi-person-circle"></i>
{% if user %} <span class="text-truncate">{{ user.name }}</span> {% endif %}
</button>
<a
class="w-100 btn btn-outline-primary border-white text-white text-start"
href="/dashboard/librarians/{{ user.id }}/"
><i class="bi bi-person-fill-gear"></i> profile</a
>
<a
class="w-100 btn btn-outline-primary border-white text-white text-start"
href="/auth/logout/{{ user.id }}/"
><i class="bi bi-box-arrow-left"></i> logout</a
>
</div>
<hr class="border-white" />
<form action="" method="get" class="input-group w-50">
<input
type="text"
name="q"
class="form-control"
placeholder="Search..."
aria-label="Search..."
/>
<button class="input-group-text btn btn-primary">Search</button>
</form>
from django.urls import path, include
from dashboards.views import DashboardView, UpcomingLoanView, OverduedLoanView
urlpatterns = [
path("", DashboardView.as_view(), name="dashboard"),
path("books/", include("books.urls")),
path("categories/", include("categories.urls")),
path("members/", include("members.urls")),
path("book-loans/", include("book_loans.urls")),
path("librarians/", include("librarians.urls")),
path("upcoming-loans/", UpcomingLoanView.as_view(), name="upcoming_loans"),
path("overdued-loans/", OverduedLoanView.as_view(), name="overdued_loans"),
]
from django.db.models import Q
from django.views.generic import ListView, TemplateView
from datetime import datetime, timedelta
from librarians.models import LoginHistory
from members.models import Members
from book_loans.models import Book, BookLoans
from categories.models import Category
class OverduedLoanView(ListView):
model = BookLoans
template_name = "loans.html"
paginate_by = 5
def get_queryset(self):
queryset = super().get_queryset()
keyword = self.request.GET.get("q")
order = self.request.GET.get("o")
now = datetime.now()
queryset = queryset.filter(due_date__lte=now, return_date=None).order_by(
"-updated_at"
)
if keyword:
queryset = queryset.filter(
Q(book__title__icontains=keyword)
| Q(member__name__icontains=keyword)
| Q(librarian__name__icontains=keyword)
).order_by("-created_at")
if order:
if order == "new":
queryset = queryset.order_by("-created_at")
elif order == "old":
queryset = queryset.order_by("created_at")
return queryset
class UpcomingLoanView(ListView):
model = BookLoans
template_name = "loans.html"
paginate_by = 5
def get_queryset(self):
queryset = super().get_queryset()
keyword = self.request.GET.get("q")
order = self.request.GET.get("o")
now = datetime.now()
due_date_treshold = now.today() + timedelta(days=3)
queryset = (
queryset.filter(due_date__lte=due_date_treshold, return_date=None)
.filter(due_date__gte=now.today())
.order_by("-updated_at")
)
if keyword:
queryset = queryset.filter(
Q(book__title__icontains=keyword)
| Q(member__name__icontains=keyword)
| Q(librarian__name__icontains=keyword)
).order_by("-created_at")
if order:
if order == "new":
queryset = queryset.order_by("-created_at")
elif order == "old":
queryset = queryset.order_by("created_at")
return queryset
class HomePage(TemplateView):
template_name = "homepage.html"
class DashboardView(TemplateView):
template_name = "dashboard/index.html"
login_history = LoginHistory.objects.order_by("-login_at")[:10]
book_loans = BookLoans.objects.all()
total_book = Book.objects.count()
total_category = Category.objects.count()
total_member = Members.objects.count()
total_book_loans = book_loans.count()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
now = datetime.now()
overdue_loans = self.book_loans.filter(
due_date__lte=now, return_date=None
).order_by("-created_at")[:10]
due_date_treshold = now.today() + timedelta(days=3)
upcoming_loans = (
self.book_loans.filter(due_date__lte=due_date_treshold, return_date=None)
.filter(due_date__gte=now.today())
.order_by("-created_at")[:10]
)
context["login_histories"] = self.login_history
context["total_book"] = self.total_book
context["total_category"] = self.total_category
context["total_member"] = self.total_member
context["total_book_loans"] = self.total_book_loans
context["total_overdue"] = overdue_loans.count()
context["total_upcoming"] = upcoming_loans.count()
context["overdue_loans"] = overdue_loans
context["upcoming_loans"] = upcoming_loans
return context
from django.contrib import admin
from librarians.models import Librarians
admin.site.register(Librarians)
from django.apps import AppConfig
class LibrariansConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'librarians'
from django import forms
from librarians.models import Librarians
class LibrarianForm(forms.ModelForm):
class Meta:
model = Librarians
fields = ["name", "email", "password"]
widgets = {
"name": forms.TextInput(
attrs={
"placeholder": "Name",
"class": "form-control",
}
),
"email": forms.EmailInput(
attrs={
"placeholder": "Email",
"class": "form-control",
}
),
"password": forms.PasswordInput(
attrs={
"placeholder": "Password",
"class": "form-control",
}
),
}
def __init__(self, *args, **kwargs):
super(LibrarianForm, self).__init__(*args, **kwargs)
self.fields["password"].required = False
# Generated by Django 4.2 on 2024-06-26 07:38
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Librarians',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('email', models.EmailField(max_length=254)),
('password', models.CharField(max_length=255)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'librarians',
},
),
]
# Generated by Django 4.2 on 2024-06-26 08:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('librarians', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='LoginHistory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('login_at', models.DateTimeField(auto_now_add=True)),
('librarian', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='librarians.librarians')),
],
),
]
# Generated by Django 4.2 on 2024-06-26 08:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('librarians', '0002_loginhistory'),
]
operations = [
migrations.AlterModelTable(
name='loginhistory',
table='librarians_login_histories',
),
]
# Generated by Django 5.0.6 on 2024-07-10 10:30
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0003_alter_loginhistory_table'),
]
operations = [
migrations.AddField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('c55123c4-cc13-4ce9-910a-559e026448d8')),
),
]
# Generated by Django 5.0.6 on 2024-07-10 10:34
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0004_librarians_uuid'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('96d31e5e-a0f3-46a0-acbc-9c2d5376e4dc')),
),
]
# Generated by Django 5.0.6 on 2024-07-10 10:36
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0005_alter_librarians_uuid'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('43cbe37c-ca84-4e71-bc98-2bd215fd0e8b')),
),
]
# Generated by Django 5.0.6 on 2024-07-10 10:39
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0006_alter_librarians_uuid'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='email',
field=models.EmailField(max_length=254, unique=True),
),
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('a55a91a5-93c4-496e-bf36-c672850c59b6')),
),
]
# Generated by Django 5.0.6 on 2024-07-10 10:46
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0007_alter_librarians_email_alter_librarians_uuid'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='email',
field=models.EmailField(max_length=254),
),
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('2fcb5fbd-6516-4aa1-8115-78c5ae78343c')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 02:28
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0008_alter_librarians_email_alter_librarians_uuid'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('ccb160bd-804f-4527-bcbd-333de301dfc2')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 02:45
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0009_alter_librarians_uuid'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveField(
model_name='librarians',
name='email',
),
migrations.RemoveField(
model_name='librarians',
name='name',
),
migrations.RemoveField(
model_name='librarians',
name='password',
),
migrations.RemoveField(
model_name='librarians',
name='uuid',
),
migrations.AddField(
model_name='librarians',
name='picture',
field=models.ImageField(blank=True, null=True, upload_to='uploads'),
),
migrations.AddField(
model_name='librarians',
name='user',
field=models.OneToOneField(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:10
import datetime
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0010_remove_librarians_email_remove_librarians_name_and_more'),
]
operations = [
migrations.RemoveField(
model_name='librarians',
name='picture',
),
migrations.RemoveField(
model_name='librarians',
name='user',
),
migrations.AddField(
model_name='librarians',
name='email',
field=models.EmailField(default=datetime.datetime(2024, 7, 12, 3, 9, 56, 642818, tzinfo=datetime.timezone.utc), max_length=254),
preserve_default=False,
),
migrations.AddField(
model_name='librarians',
name='name',
field=models.CharField(default=datetime.datetime(2024, 7, 12, 3, 10, 0, 628769, tzinfo=datetime.timezone.utc), max_length=50),
preserve_default=False,
),
migrations.AddField(
model_name='librarians',
name='password',
field=models.CharField(default=datetime.datetime(2024, 7, 12, 3, 10, 3, 855783, tzinfo=datetime.timezone.utc), max_length=255),
preserve_default=False,
),
migrations.AddField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('54ab208e-edb8-459a-8bb6-3aa3bc7b5a3c')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:29
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0011_remove_librarians_picture_remove_librarians_user_and_more'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('6ef57b61-22f2-4a91-ade4-2b1b19a10cf9')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:43
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0012_alter_librarians_uuid'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('ff71097d-c72c-4989-9905-fa63f177b313')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:46
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0013_alter_librarians_uuid'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('e9657dfd-1678-4fc2-8492-0ceebf8f5642')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:48
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0014_alter_librarians_uuid'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('856f10d7-725c-46fc-91bc-fa4ae15171f7')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:51
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0015_alter_librarians_uuid'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('0d98bb0f-7a0c-4bd5-af24-c8c24cc3a0f2')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:57
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0016_alter_librarians_uuid'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('6fed0670-22bc-4ae9-9cd5-d1e1f37aa012')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 04:07
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('librarians', '0017_alter_librarians_uuid'),
]
operations = [
migrations.AlterField(
model_name='librarians',
name='uuid',
field=models.UUIDField(default=uuid.UUID('e8bae497-c18d-4c63-bd04-317ee34edab7')),
),
]
import uuid
from django.db import models
class Librarians(models.Model):
uuid = models.UUIDField(default=uuid.uuid4())
name = models.CharField(max_length=50)
email = models.EmailField()
password = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Meta:
db_table = "librarians"
class LoginHistory(models.Model):
librarian = models.ForeignKey(to=Librarians, on_delete=models.CASCADE)
login_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "librarians_login_histories"
{% extends "layout.html" %} {% block dashboard %}
<div style="max-width: 80vw" class="w-100 p-4">
<div class="d-flex gap-2 mb-4">
{% include "order_form.html" %}
<a type="button" class="btn btn-primary" href="/dashboard/librarians/add/">
<i class="bi bi-plus-circle"></i> Add Librarian
</a>
{% include "search_form.html" %}
</div>
{% include "librarians_table_data.html" %}
</div>
{% endblock dashboard %}
<table class="table table-hover">
<thead>
<tr class="table-primary">
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Created At</th>
<th scope="col">Updated At</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% if object_list %} {% for librarian in object_list %}
<tr>
<td>{{ librarian.name }}</td>
<td>{{ librarian.email }}</td>
<td>{{ librarian.created_at }}</td>
<td>{{ librarian.updated_at }}</td>
<td class="d-flex gap-2">
<a
class="btn btn-success"
href="/dashboard/librarians/{{ librarian.id }}/"
>
<i class="bi bi-pencil-square"></i>
</a>
<a
class="btn btn-danger"
href="/dashboard/librarians/{{ librarian.id }}/delete/"
>
<i class="bi bi-trash3-fill"></i>
</a>
</td>
</tr>
{% endfor %} {% else %}
<tr class="w-100">
<td></td>
<td></td>
<td>
<p>Data Empty</p>
</td>
<td></td>
<td></td>
</tr>
{% endif %}
</tbody>
</table>
{% include "pagination.html" %}
from django.urls import path
from librarians.views import (
LibrarianListView,
LibrarianCreateView,
LibrarianUpdateView,
LibrarianDeleteView,
)
urlpatterns = [
path("", LibrarianListView.as_view(), name="librarian_lists"),
path("add/", LibrarianCreateView.as_view(), name="create_librarian"),
path("<int:pk>/", LibrarianUpdateView.as_view(), name="update_librarian"),
path("<int:pk>/delete/", LibrarianDeleteView.as_view(), name="delete_librarian"),
]
from django.db.models import Q
from django.views import generic
from librarians.models import Librarians
from librarians.forms import LibrarianForm
from authentications.utils import Hasher
class LibrarianListView(generic.ListView):
model = Librarians
template_name = "librarians.html"
paginate_by = 5
def get_queryset(self):
queryset = super().get_queryset()
keyword = self.request.GET.get("q")
order = self.request.GET.get("o")
if keyword:
queryset = queryset.filter(
Q(name__icontains=keyword) | Q(email__icontains=keyword)
).order_by("-created_at")
if order:
if order == "new":
queryset = queryset.order_by("-created_at")
elif order == "old":
queryset = queryset.order_by("created_at")
return queryset.order_by("-updated_at")
class LibrarianCreateView(generic.FormView):
model = Librarians
form_class = LibrarianForm
success_url = "/dashboard/librarians/"
template_name = "form/create_form.html"
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
formData = form.cleaned_data.copy()
formData["password"] = Hasher.encode(formData["password"])
self.model.objects.create(**formData)
return super().post(request, *args, **kwargs)
class LibrarianUpdateView(generic.FormView):
model = Librarians
form_class = LibrarianForm
success_url = "/dashboard/librarians"
template_name = "form/update_form.html"
def get(self, request, *args, **kwargs):
librarian = self.model.objects.get(pk=kwargs["pk"])
self.initial = {
"name": librarian.name,
"email": librarian.email,
"password": librarian.password,
}
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
librarian = self.model.objects.get(pk=kwargs["pk"])
form = self.get_form()
if form.is_valid():
formData = form.cleaned_data.copy()
new_password = form.cleaned_data.get("password")
if new_password:
formData["password"] = Hasher.encode(formData["password"])
else:
formData["password"] = librarian.password
self.model.objects.filter(pk=kwargs["pk"]).update(**formData)
return super().post(request, *args, **kwargs)
return self.form_invalid(form)
class LibrarianDeleteView(generic.edit.DeleteView):
model = Librarians
success_url = "/dashboard/librarians"
template_name = "form/delete_form.html"
#!/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", "config.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()
from django.contrib import admin
from members.models import Members
admin.site.register(Members)
from django.apps import AppConfig
class MembersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'members'
from django import forms
from members.models import Members
class MemberForm(forms.ModelForm):
class Meta:
model = Members
fields = ["name", "email", "password"]
widgets = {
"name": forms.TextInput(
attrs={
"placeholder": "Name",
"class": "form-control",
}
),
"email": forms.EmailInput(
attrs={
"placeholder": "Email",
"class": "form-control",
}
),
"password": forms.PasswordInput(
attrs={
"placeholder": "Password",
"class": "form-control",
}
),
}
def __init__(self, *args, **kwargs):
super(MemberForm, self).__init__(*args, **kwargs)
self.fields["password"].required = False
# Generated by Django 4.2 on 2024-06-26 07:38
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Members',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('email', models.EmailField(max_length=254)),
('password', models.CharField(max_length=255)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'members',
},
),
]
# Generated by Django 4.2 on 2024-07-03 05:31
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('members', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='members',
name='uniq_code',
field=models.UUIDField(default=uuid.UUID('7159cb38-38d8-4a14-b38d-aa99818f5dc3'), editable=False),
),
]
# Generated by Django 4.2 on 2024-07-03 05:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0002_members_uniq_code'),
]
operations = [
migrations.RemoveField(
model_name='members',
name='uniq_code',
),
migrations.AddField(
model_name='members',
name='account_number',
field=models.CharField(default='93080345', editable=False, max_length=8),
),
]
# Generated by Django 4.2 on 2024-07-03 05:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0003_remove_members_uniq_code_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='025950513486265', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-04 03:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0004_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='919767233748919', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-05 02:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0005_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='810779514016329', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-09 07:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0006_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='987260160756796', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 02:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0007_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='190415015691257', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 04:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0008_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='022569062908676', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 05:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0009_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='955979555055185', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 05:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0010_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='574296497943013', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 05:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0011_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='232120498178845', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 05:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0012_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='733604449314241', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 06:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0013_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='468552874976336', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 07:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0014_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='350636101419308', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 07:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0015_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='473840282244567', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 07:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0016_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='610483236868486', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 07:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0017_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='034413731472460', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 07:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0018_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='999881495313697', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 10:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0019_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='067221253098248', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 10:34
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0020_alter_members_account_number'),
]
operations = [
migrations.AddField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('1aab9168-dc1d-4b7b-9ad1-d7df196cb4c7')),
),
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='236487127558267', editable=False, max_length=15),
),
]
# Generated by Django 5.0.6 on 2024-07-10 10:36
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0021_members_uuid_alter_members_account_number'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='403329495459524', editable=False, max_length=15),
),
migrations.AlterField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('eeb97fca-fbb6-4e95-89ff-2286e2664f3e')),
),
]
# Generated by Django 5.0.6 on 2024-07-10 10:39
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0022_alter_members_account_number_alter_members_uuid'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='826711401788064', editable=False, max_length=15),
),
migrations.AlterField(
model_name='members',
name='email',
field=models.EmailField(max_length=254, unique=True),
),
migrations.AlterField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('3fa3cac4-2778-4c3e-b53d-8f8fb90cec94')),
),
]
# Generated by Django 5.0.6 on 2024-07-10 10:46
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0023_alter_members_account_number_alter_members_email_and_more'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='993484768474901', editable=False, max_length=15),
),
migrations.AlterField(
model_name='members',
name='email',
field=models.EmailField(max_length=254),
),
migrations.AlterField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('17686b22-8447-413b-946d-beeef40d6e31')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 02:28
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0024_alter_members_account_number_alter_members_email_and_more'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='745450367912390', editable=False, max_length=15),
),
migrations.AlterField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('a84677d7-26d5-4656-b702-b3910c01c46b')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 02:45
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0025_alter_members_account_number_alter_members_uuid'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveField(
model_name='members',
name='account_number',
),
migrations.RemoveField(
model_name='members',
name='email',
),
migrations.RemoveField(
model_name='members',
name='name',
),
migrations.RemoveField(
model_name='members',
name='password',
),
migrations.RemoveField(
model_name='members',
name='uuid',
),
migrations.AddField(
model_name='members',
name='picture',
field=models.ImageField(blank=True, null=True, upload_to='uploads'),
),
migrations.AddField(
model_name='members',
name='user',
field=models.OneToOneField(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:10
import datetime
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0026_remove_members_account_number_remove_members_email_and_more'),
]
operations = [
migrations.RemoveField(
model_name='members',
name='picture',
),
migrations.RemoveField(
model_name='members',
name='user',
),
migrations.AddField(
model_name='members',
name='account_number',
field=models.CharField(default='867934046970059', editable=False, max_length=15),
),
migrations.AddField(
model_name='members',
name='email',
field=models.EmailField(default=datetime.datetime(2024, 7, 12, 3, 10, 8, 751856, tzinfo=datetime.timezone.utc), max_length=254),
preserve_default=False,
),
migrations.AddField(
model_name='members',
name='name',
field=models.CharField(default=datetime.datetime(2024, 7, 12, 3, 10, 12, 339105, tzinfo=datetime.timezone.utc), max_length=50),
preserve_default=False,
),
migrations.AddField(
model_name='members',
name='password',
field=models.CharField(default=datetime.datetime(2024, 7, 12, 3, 10, 18, 879028, tzinfo=datetime.timezone.utc), max_length=255),
preserve_default=False,
),
migrations.AddField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('d369404f-1c7c-401d-835b-e5d6d097d77a')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:29
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0027_remove_members_picture_remove_members_user_and_more'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='247978923232139', editable=False, max_length=15),
),
migrations.AlterField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('b90c94f9-5360-4e11-a73f-4cab8e3c78da')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:43
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0028_alter_members_account_number_alter_members_uuid'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='504849108428186', editable=False, max_length=15),
),
migrations.AlterField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('6167adb1-0907-43b9-88a3-27bd835df412')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:46
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0029_alter_members_account_number_alter_members_uuid'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='098731120345666', editable=False, max_length=15),
),
migrations.AlterField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('ed0efa99-bdac-4207-9369-8fe5450f7abf')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:48
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0030_alter_members_account_number_alter_members_uuid'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='612077411752323', editable=False, max_length=15),
),
migrations.AlterField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('a7218f2f-cf61-4ea8-9872-2bdc9d001d84')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:51
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0031_alter_members_account_number_alter_members_uuid'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='323395721046290', editable=False, max_length=15),
),
migrations.AlterField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('2c948d79-5532-47ef-9349-c8cd119baa78')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:57
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0032_alter_members_account_number_alter_members_uuid'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='309018897746558', editable=False, max_length=15),
),
migrations.AlterField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('f6cb8ac0-24c2-4b74-9f4c-a740041c4811')),
),
]
# Generated by Django 5.0.6 on 2024-07-12 04:07
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0033_alter_members_account_number_alter_members_uuid'),
]
operations = [
migrations.AlterField(
model_name='members',
name='account_number',
field=models.CharField(default='300839628181539', editable=False, max_length=15),
),
migrations.AlterField(
model_name='members',
name='uuid',
field=models.UUIDField(default=uuid.UUID('4e447ee1-6541-4f94-92a8-74526f928ff4')),
),
]
import uuid
from django.db import models
from members.utils import generate_unique_number
random_number = generate_unique_number(15)
class Members(models.Model):
uuid = models.UUIDField(default=uuid.uuid4())
account_number = models.CharField(
default=random_number, editable=False, max_length=15
)
name = models.CharField(max_length=50)
email = models.EmailField()
password = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Meta:
db_table = "members"
{% extends "layout.html" %} {% block dashboard %}
<div style="max-width: 80vw" class="w-100 p-4">
<div class="d-flex gap-2 mb-4">
{% include "order_form.html" %}
<a type="button" class="btn btn-primary" href="/dashboard/members/add/">
<i class="bi bi-plus-circle"></i> Add Member
</a>
{% include "search_form.html" %}
</div>
{% include "members_table_data.html" %}
</div>
{% endblock dashboard %}
<table class="table table-hover">
<thead>
<tr class="table-primary">
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Created At</th>
<th scope="col">Updated At</th>
<th scope="col">Account Number</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% if object_list %} {% for member in object_list %}
<tr class="text-truncate">
<td>{{ member.name }}</td>
<td>{{ member.email }}</td>
<td>{{ member.created_at }}</td>
<td>{{ member.updated_at }}</td>
<td>{{ member.account_number }}</td>
<td class="d-flex gap-2">
<a class="btn btn-success" href="/dashboard/members/{{ member.id }}/">
<i class="bi bi-pencil-square"></i>
</a>
<a
class="btn btn-danger"
href="/dashboard/members/{{ member.id }}/delete/"
>
<i class="bi bi-trash3-fill"></i>
</a>
</td>
</tr>
{% endfor %} {% else %}
<tr class="w-100">
<td></td>
<td></td>
<td></td>
<td>
<p>Data Empty</p>
</td>
<td></td>
<td></td>
</tr>
{% endif %}
</tbody>
</table>
{% include "pagination.html" %}
from django.urls import path
from members.views import (
MemberListView,
MemberUpdateView,
MemberCreateView,
MemberDeleteView,
)
urlpatterns = [
path("", MemberListView.as_view(), name="member_lists"),
path("add/", MemberCreateView.as_view(), name="add_member"),
path("<int:pk>/", MemberUpdateView.as_view(), name="update_member"),
path("<int:pk>/delete/", MemberDeleteView.as_view(), name="delete_member"),
]
import random
import string
def generate_unique_number(digit: int):
while True:
number = "".join(random.choices(string.digits, k=digit))
if not is_number_used(number):
return number
def is_number_used(number):
return False
from django.db.models import Q
from django.views import generic
from members.models import Members
from members.forms import MemberForm
from authentications.utils import Hasher
class MemberListView(generic.ListView):
model = Members
template_name = "members.html"
paginate_by = 5
def get_queryset(self):
queryset = super().get_queryset()
keyword = self.request.GET.get("q")
order = self.request.GET.get("o")
if keyword:
queryset = queryset.filter(
Q(name__icontains=keyword) | Q(email__icontains=keyword)
).order_by("-created_at")
if order:
if order == "new":
queryset = queryset.order_by("-created_at")
elif order == "old":
queryset = queryset.order_by("created_at")
return queryset.order_by("-updated_at")
class MemberCreateView(generic.FormView):
model = Members
form_class = MemberForm
success_url = "/dashboard/members/"
template_name = "form/create_form.html"
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
formData = form.cleaned_data.copy()
formData["password"] = Hasher.encode(formData["password"])
self.model.objects.create(**formData)
return super().post(request, *args, **kwargs)
class MemberUpdateView(generic.FormView):
model = Members
form_class = MemberForm
success_url = "/dashboard/members"
template_name = "form/update_form.html"
def get(self, request, *args, **kwargs):
member = self.model.objects.get(pk=kwargs["pk"])
self.initial = {
"name": member.name,
"email": member.email,
"password": member.password,
}
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
member = self.model.objects.get(pk=kwargs["pk"])
form = self.get_form()
if form.is_valid():
formData = form.cleaned_data.copy()
new_password = form.cleaned_data.get("password")
if new_password:
formData["password"] = Hasher.encode(formData["password"])
else:
formData["password"] = member.password
self.model.objects.filter(pk=kwargs["pk"]).update(**formData)
return super().post(request, *args, **kwargs)
return self.form_invalid(form)
class MemberDeleteView(generic.edit.DeleteView):
model = Members
success_url = "/dashboard/members"
template_name = "form/delete_form.html"
Binary files a/requirements.txt and /dev/null differ
from django.apps import AppConfig
class TestsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'tests'
from django.test import TestCase
from books.models import Book
class BookModelTestCase(TestCase):
def setUp(self):
self.title = "Test Book"
self.description = "It's just Book created for testing!"
Book.objects.create(title=self.title, description=self.description)
def test_get_books(self):
"""Should get all books"""
books = Book.objects.all()
self.assertIsNotNone(books)
def test_get_book_by_field(self):
"""Should get book by field"""
book = Book.objects.get(title=self.title)
self.assertEqual(book.title, self.title)
def test_create_new_book(self):
"""Should create new book"""
title = "New book"
description = "This is new book, created just for testing!"
Book.objects.create(title=title, description=description)
new_book = Book.objects.get(title=title)
self.assertEqual(new_book.title, title)
self.assertEqual(new_book.description, description)
def test_update_book(self):
"""Should update book fields"""
new_title = "This is updated title!"
Book.objects.filter(title=self.title).update(title=new_title)
updated_book = Book.objects.get(title=new_title)
self.assertEqual(updated_book.title, new_title)
def test_delete_book(self):
book = Book.objects.get(title=self.title)
book.delete()
self.assertRaises(Book.DoesNotExist, Book.objects.get, title=book.title)
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'users'
# Generated by Django 5.0.6 on 2024-07-12 03:51
import django.contrib.auth.models
import django.db.models.deletion
from django.conf import settings
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=[
('user_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
('role', models.CharField(choices=[('1', 'librarian'), ('2', 'member')], max_length=50)),
('picture', models.ImageField(blank=True, null=True, upload_to='uploads')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
bases=('auth.user',),
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]
# Generated by Django 5.0.6 on 2024-07-12 03:57
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Role',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(choices=[('1', 'librarian'), ('2', 'member')], max_length=50)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.DeleteModel(
name='User',
),
]
# Generated by Django 5.0.6 on 2024-07-12 04:07
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('users', '0002_role_delete_user'),
]
operations = [
migrations.RenameField(
model_name='role',
old_name='role',
new_name='name',
),
]
from django.contrib.auth.models import User
from django.db import models
ROLE_CHOICES = (
("1", "librarian"),
("2", "member"),
)
class Role(models.Model):
name = models.CharField(choices=ROLE_CHOICES, max_length=50)
def __str__(self):
return self.name
from django.test import TestCase
# Create your tests here.
from django.shortcuts import render
# Create your views here.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment