Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

library management system created #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
/venv
# Created by https://www.toptal.com/developers/gitignore/api/django
# Edit at https://www.toptal.com/developers/gitignore?templates=django

### Django ###
*.log
*.pot
*.pyc
__pycache__/
local_settings.py
db.sqlite3
db.sqlite3-journal
media

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
6 changes: 6 additions & 0 deletions file.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
title,author,publisher,available_quantity
Book 1,Author 1,Publisher A,10
Book 2,Author 2,Publisher B,15
Book 3,Author 3,Publisher A,8
Book 4,Author 4,Publisher C,20
Book 5,Author 5,Publisher B,12
33 changes: 31 additions & 2 deletions library/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,23 @@
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

import os
from pathlib import Path
import environ
import certifi

env = environ.Env()
environ.Env.read_env()

# Previous settings ...
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = env('EMAIL_HOST')
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
os.environ['SSL_CERT_FILE'] = certifi.where()


# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
Expand All @@ -37,6 +53,10 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'users',
'library_management',
'rest_framework',
'django_crontab',
]

MIDDLEWARE = [
Expand All @@ -54,7 +74,7 @@
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'DIRS': ['templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
Expand All @@ -80,7 +100,6 @@
}
}


# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators

Expand All @@ -99,6 +118,7 @@
},
]

AUTH_USER_MODEL = 'users.User'

# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
Expand All @@ -121,3 +141,12 @@
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

CRONJOBS = [
('* * * * *', 'library_management.cron.send_return_reminders',
'>> /Users/rohansaeed/Desktop/library-management/library_management/debug7.log')
]


MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
4 changes: 3 additions & 1 deletion library/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('users.urls')),
path('lib/', include('library_management.urls'))
]
8 changes: 8 additions & 0 deletions library_management/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.contrib import admin

from .models import Book, BookIssue, NewBookTicket
# Register your models here.

admin.site.register(Book)
admin.site.register(BookIssue)
admin.site.register(NewBookTicket)
6 changes: 6 additions & 0 deletions library_management/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class LibraryManagementConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'library_management'
18 changes: 18 additions & 0 deletions library_management/cron.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.core.mail import send_mail
from django.utils import timezone
from .models import BookIssue


def send_return_reminders():
actual_return_date = timezone.now() + timezone.timedelta(days=15)
overdue_books = BookIssue.objects.filter(
return_date__lt=actual_return_date, returned=True)
for book in overdue_books:
user = book.user
subject = 'Return Reminder'
message = f"Dear {user.username}, please remember to return the book '{book.name}' as soon as possible."
from_email = '[email protected]'
recipient_list = [user.email]

send_mail(subject, message, from_email,
recipient_list, fail_silently=False)
File renamed without changes.
Empty file.
41 changes: 41 additions & 0 deletions library_management/management/commands/my_books.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# myapp/management/commands/import_books_from_csv.py
import csv
from django.core.management.base import BaseCommand

from library_management.models import Book


class Command(BaseCommand):
help = 'Import books from a CSV file'

def add_arguments(self, parser):
parser.add_argument('file_path', type=str, help='Path to the CSV file')

def handle(self, *args, **kwargs):
file_path = kwargs['file_path']
try:
with open(file_path, 'r') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
title = row['title']
author = row['author']
publisher = row['publisher']
available_quantity = int(row['available_quantity'])

book, created = Book.objects.get_or_create(
name=title,
author=author,
publisher=publisher,
defaults={'quantity': available_quantity}
)

if not created:
book.quantity = available_quantity
book.save()

self.stdout.write(self.style.SUCCESS(
f'Successfully imported book: {title}'))

except FileNotFoundError:
self.stdout.write(self.style.ERROR(
'File not found. Please check the file path.'))
49 changes: 49 additions & 0 deletions library_management/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Generated by Django 4.2.6 on 2023-11-06 05:45

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Book',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('author', models.CharField(max_length=100)),
('publisher', models.CharField(max_length=100)),
('image', models.ImageField(blank=True, null=True, upload_to='books/')),
('quantity', models.PositiveIntegerField(default=0)),
],
),
migrations.CreateModel(
name='NewBookTicket',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('Pending', 'Pending'), ('Approved', 'Approved'), ('Rejected', 'Rejected')], default='Pending', max_length=20)),
('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='library_management.book')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='BookIssue',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('issued_date', models.DateField(auto_now=True)),
('return_date', models.DateField()),
('returned', models.BooleanField(default=False)),
('status', models.CharField(choices=[('Issued', 'Issued'), ('Pending', 'Pending'), ('Rejected', 'Rejected')], default='Pending', max_length=20)),
('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='library_management.book')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
18 changes: 18 additions & 0 deletions library_management/migrations/0002_alter_bookissue_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.6 on 2023-11-07 05:21

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('library_management', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='bookissue',
name='status',
field=models.CharField(choices=[('pending', 'Pending'), ('issued', 'Issued'), ('rejected', 'Rejected')], default='pending', max_length=9),
),
]
Empty file.
70 changes: 70 additions & 0 deletions library_management/models.py
Copy link
Collaborator

@Danyal-Faheem Danyal-Faheem Nov 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use of choices but we can utilize classes for choices as well. For example like this:

class MyModel(models.Model): class Month(models.IntegerChoices): JAN = 1, "JANUARY" FEB = 2, "FEBRUARY" MAR = 3, "MAR" # (...) month = models.PositiveSmallIntegerField( choices=Month.choices, default=Month.JAN )

You can refer to the 2nd answer in this [link](https://stackoverflow.com/questions/18676156/how-to-properly-use-the-choices-field-option-in-django) for more info

Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from django.conf import settings
from django.db import models
from users.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.mail import send_mail


class Book(models.Model):
name = models.CharField(max_length=100)
author = models.CharField(max_length=100)
publisher = models.CharField(max_length=100)
image = models.ImageField(upload_to='books/', null=True, blank=True)
quantity = models.PositiveIntegerField(default=0)


class BookIssue(models.Model):
# REQUEST_STATUS_CHOICES = (
# ('Issued', 'Issued'),
# ('Pending', 'Pending'),
# ('Rejected', 'Rejected'),
# )
user = models.ForeignKey(User, on_delete=models.CASCADE)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
issued_date = models.DateField(auto_now=True)
return_date = models.DateField()
returned = models.BooleanField(default=False)
Comment on lines +26 to +27
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have a return_date here when we have a separate table for returned books.


class Status(models.TextChoices):
"""
Status of each request, changed to depict the current status
Utilized TextChoices here as I required some comparisons with keys using request data
"""
PENDING = "pending", "Pending"
ISSUED = "issued", "Issued"
REJECTED = "rejected", "Rejected"
status = models.CharField(choices=Status.choices,
max_length=9, default=Status.PENDING)

# status = models.CharField(
# max_length=20, choices=REQUEST_STATUS_CHOICES, default='Pending')


class NewBookTicket(models.Model):
BOOK_TICKET_STATUS_CHOICES = (
('Pending', 'Pending'),
('Approved', 'Approved'),
('Rejected', 'Rejected'),
)
user = models.ForeignKey(User, on_delete=models.CASCADE)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
status = models.CharField(
max_length=20, choices=BOOK_TICKET_STATUS_CHOICES, default='Pending')

def __str__(self):
return f'{self.user} - {self.book}'


@receiver(post_save, sender=NewBookTicket)
def call_car_api(sender, instance, **kwargs):
subject = 'Ticket Received'
message = f"Dear {instance.user.username}, we have got your request regarding '{instance.book.name}'. We will soon inform you about availability ."
from_email = '[email protected]'
recipient_list = [instance.user.email]

send_mail(subject, message, from_email,
recipient_list, fail_silently=False)

print('Ticket object created')
print(sender, instance, kwargs)
8 changes: 8 additions & 0 deletions library_management/permissions.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work on using inbuilt permission checks.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from rest_framework import permissions


class IsStaffEditorPermission(permissions.DjangoModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
return request.user and request.user.is_authenticated
return request.user and request.user.is_authenticated and (request.user.is_librarian or request.user.is_staff)
Loading