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",
),
]
This diff is collapsed.
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 %}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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