mirror of
https://github.com/lemeow125/Borrowing-TrackerBackend.git
synced 2025-04-27 10:11:24 +08:00
Migrate to postgres, add memcache and vastly improve available equipment instance query time from 1min 30 seconds to 12 seconds
This commit is contained in:
parent
0af8efa793
commit
b0b1f4db86
24 changed files with 1253 additions and 292 deletions
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 5.0.1 on 2024-01-06 16:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipments', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipmentinstance',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('Available', 'Available'), ('Pending', 'Pending'), ('Broken', 'Broken'), ('Borrowed', 'Borrowed')], db_index=True, default='Available', max_length=20),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalequipmentinstance',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('Available', 'Available'), ('Pending', 'Pending'), ('Broken', 'Broken'), ('Borrowed', 'Borrowed')], db_index=True, default='Available', max_length=20),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 5.0.1 on 2024-01-06 18:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipments', '0002_alter_equipmentinstance_status_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipment',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalequipment',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100),
|
||||
),
|
||||
]
|
Binary file not shown.
|
@ -4,6 +4,7 @@ from simple_history.models import HistoricalRecords
|
|||
from django.db.models.signals import post_migrate
|
||||
from django.dispatch import receiver
|
||||
from config import settings
|
||||
from django.core.cache import cache
|
||||
import csv
|
||||
import os
|
||||
|
||||
|
@ -13,7 +14,7 @@ class Equipment(models.Model):
|
|||
('Glassware', 'Glassware'),
|
||||
('Miscellaneous', 'Miscellaneous')
|
||||
)
|
||||
name = models.CharField(max_length=40)
|
||||
name = models.CharField(max_length=100)
|
||||
category = models.CharField(
|
||||
max_length=20, choices=EQUIPMENT_CATEGORY_CHOICES, default='Miscellaneous')
|
||||
description = models.TextField(max_length=512, null=True)
|
||||
|
@ -24,6 +25,10 @@ class Equipment(models.Model):
|
|||
def __str__(self):
|
||||
return f'{self.name}'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
cache.delete('equipments')
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class EquipmentInstance(models.Model):
|
||||
EQUIPMENT_INSTANCE_STATUS_CHOICES = (
|
||||
|
@ -34,7 +39,7 @@ class EquipmentInstance(models.Model):
|
|||
)
|
||||
equipment = models.ForeignKey(Equipment, on_delete=models.CASCADE)
|
||||
status = models.CharField(
|
||||
max_length=20, choices=EQUIPMENT_INSTANCE_STATUS_CHOICES, default='Available')
|
||||
max_length=20, choices=EQUIPMENT_INSTANCE_STATUS_CHOICES, default='Available', db_index=True)
|
||||
remarks = models.TextField(max_length=512, null=True)
|
||||
date_added = models.DateTimeField(default=now, editable=False)
|
||||
last_updated = models.DateTimeField(auto_now=True, editable=False)
|
||||
|
@ -43,67 +48,77 @@ class EquipmentInstance(models.Model):
|
|||
def __str__(self):
|
||||
return f'{self.equipment.name}'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
cache.delete('equipments')
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
seed_database = False
|
||||
|
||||
|
||||
@receiver(post_migrate)
|
||||
def create_superuser(sender, **kwargs):
|
||||
if sender.name == 'equipments':
|
||||
root_path = os.path.join(settings.MEDIA_ROOT, 'equipment_records')
|
||||
csv_files = [f for f in os.listdir(root_path) if f.endswith('.csv')]
|
||||
print('Warning: Postmigration script will migrate without checking for existing item instances')
|
||||
for csv_file in csv_files:
|
||||
csv_file_path = os.path.join(root_path, csv_file)
|
||||
filename = os.path.splitext(csv_file)[0]
|
||||
print('---', 'Adding Equipments from', filename, '---')
|
||||
with open(csv_file_path, newline='') as csvfile:
|
||||
if seed_database:
|
||||
root_path = os.path.join(settings.MEDIA_ROOT, 'equipment_records')
|
||||
csv_files = [f for f in os.listdir(
|
||||
root_path) if f.endswith('.csv')]
|
||||
print(
|
||||
'Warning: Postmigration script will migrate without checking for existing item instances')
|
||||
for csv_file in csv_files:
|
||||
csv_file_path = os.path.join(root_path, csv_file)
|
||||
filename = os.path.splitext(csv_file)[0]
|
||||
print('---', 'Adding Equipments from', filename, '---')
|
||||
with open(csv_file_path, newline='') as csvfile:
|
||||
|
||||
reader = csv.reader(csvfile)
|
||||
next(reader) # Skip the header row
|
||||
for row in reader:
|
||||
if not any(row):
|
||||
continue
|
||||
reader = csv.reader(csvfile)
|
||||
next(reader) # Skip the header row
|
||||
for row in reader:
|
||||
if not any(row):
|
||||
continue
|
||||
|
||||
# Get equipment information
|
||||
category = filename.split('-')[0]
|
||||
name = row[2] + ' ' + row[1]
|
||||
# If quantity is missing, set value as 0
|
||||
if (row[3] == ''):
|
||||
available = 0
|
||||
else:
|
||||
available = int(row[3])
|
||||
# If quantity of broken instances is missing, set value as 0
|
||||
if (row[4] == ''):
|
||||
broken = 0
|
||||
else:
|
||||
available = available - int(row[4])
|
||||
broken = int(row[4])
|
||||
# Get equipment information
|
||||
category = filename.split('-')[0]
|
||||
name = row[2] + ' ' + row[1]
|
||||
# If quantity is missing, set value as 0
|
||||
if (row[3] == ''):
|
||||
available = 0
|
||||
else:
|
||||
available = int(row[3])
|
||||
# If quantity of broken instances is missing, set value as 0
|
||||
if (row[4] == ''):
|
||||
broken = 0
|
||||
else:
|
||||
available = available - int(row[4])
|
||||
broken = int(row[4])
|
||||
|
||||
def create_instances(a, b, c):
|
||||
print('Adding', a, 'number of working', c.name)
|
||||
# Add working equipments
|
||||
if (a >= 1):
|
||||
for i in range(a):
|
||||
EquipmentInstance.objects.create(
|
||||
equipment=c, status='Available')
|
||||
def create_instances(a, b, c):
|
||||
print('Adding', a, 'number of working', c.name)
|
||||
# Add working equipments
|
||||
if (a >= 1):
|
||||
for i in range(a):
|
||||
EquipmentInstance.objects.create(
|
||||
equipment=c, status='Available')
|
||||
|
||||
if (b >= 1):
|
||||
print('Adding', a, 'number of broken', c.name)
|
||||
# Add broken equipments
|
||||
for i in range(b):
|
||||
EquipmentInstance.objects.create(
|
||||
equipment=c, status='Broken')
|
||||
if (b >= 1):
|
||||
print('Adding', b, 'number of broken', c.name)
|
||||
# Add broken equipments
|
||||
for i in range(b):
|
||||
EquipmentInstance.objects.create(
|
||||
equipment=c, status='Broken')
|
||||
|
||||
EQUIPMENT = Equipment.objects.filter(
|
||||
name=name, category=category).first()
|
||||
EQUIPMENT = Equipment.objects.filter(
|
||||
name=name, category=category).first()
|
||||
|
||||
# Check if equipment exists
|
||||
if (EQUIPMENT):
|
||||
# If so, add equipment instances
|
||||
create_instances(available, broken, EQUIPMENT)
|
||||
# Check if equipment exists
|
||||
if (EQUIPMENT):
|
||||
# If so, add equipment instances
|
||||
create_instances(available, broken, EQUIPMENT)
|
||||
|
||||
# If not, create equipment
|
||||
else:
|
||||
# Create the equipment first
|
||||
EQUIPMENT = Equipment.objects.create(
|
||||
name=name, category=category)
|
||||
# Then create the instances
|
||||
create_instances(available, broken, EQUIPMENT)
|
||||
# If not, create equipment
|
||||
else:
|
||||
# Create the equipment first
|
||||
EQUIPMENT = Equipment.objects.create(
|
||||
name=name, category=category)
|
||||
# Then create the instances
|
||||
create_instances(available, broken, EQUIPMENT)
|
||||
|
|
|
@ -155,7 +155,6 @@ class EquipmentInstanceSerializer(serializers.HyperlinkedModelSerializer):
|
|||
format="%m-%d-%Y %I:%M %p", read_only=True)
|
||||
last_updated = serializers.DateTimeField(
|
||||
format="%m-%d-%Y %I:%M %p", read_only=True)
|
||||
last_updated_by = serializers.SerializerMethodField()
|
||||
status = serializers.ChoiceField(
|
||||
choices=EquipmentInstance.EQUIPMENT_INSTANCE_STATUS_CHOICES)
|
||||
|
||||
|
@ -221,19 +220,11 @@ class EquipmentInstanceSerializer(serializers.HyperlinkedModelSerializer):
|
|||
class Meta:
|
||||
model = EquipmentInstance
|
||||
fields = ('id', 'equipment', 'equipment_name', 'category', 'status', 'remarks',
|
||||
'last_updated', 'last_updated_by', 'date_added')
|
||||
read_only_fields = ('id', 'last_updated', 'equipment_name', 'category',
|
||||
'last_updated_by', 'date_added', 'equipment_name')
|
||||
'last_updated', 'date_added')
|
||||
read_only_fields = ('id', 'last_upated', 'equipment_name', 'category',
|
||||
'date_added', 'equipment_name')
|
||||
extra_kwargs = {"remarks": {"required": False, "allow_null": True}}
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_history_user(self, obj):
|
||||
return obj.history_user.username if obj.history_user else None
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_last_updated_by(self, obj):
|
||||
return obj.history.first().history_user.username if obj.history.first().history_user else None
|
||||
|
||||
|
||||
class EquipmentInstanceLogsSerializer(serializers.HyperlinkedModelSerializer):
|
||||
history_date = serializers.DateTimeField(
|
||||
|
|
|
@ -6,7 +6,7 @@ from . import serializers
|
|||
from config.settings import DEBUG
|
||||
from accounts.permissions import IsTechnician
|
||||
from transactions.models import Transaction
|
||||
|
||||
from django.core.cache import cache
|
||||
# -- Equipment Viewsets
|
||||
|
||||
|
||||
|
@ -16,6 +16,15 @@ class EquipmentViewSet(viewsets.ModelViewSet):
|
|||
serializer_class = serializers.EquipmentSerializer
|
||||
queryset = Equipment.objects.all().order_by('id')
|
||||
|
||||
def get_queryset(self):
|
||||
key = 'equipments'
|
||||
|
||||
queryset = cache.get(key)
|
||||
if not queryset:
|
||||
queryset = super().get_queryset()
|
||||
cache.set(key, queryset, timeout=60*60*24)
|
||||
return queryset
|
||||
|
||||
# For viewing all logs for all equipments
|
||||
|
||||
|
||||
|
@ -69,19 +78,28 @@ class AvailableEquipmentInstanceViewSet(generics.ListAPIView):
|
|||
This view should return a list of all the equipment instances
|
||||
that are not associated with a non-finalized transaction.
|
||||
"""
|
||||
# Get all non-finalized transactions
|
||||
non_finalized_transactions = Transaction.objects.filter(
|
||||
~Q(transaction_status__in=['Finalized', 'Rejected', 'Cancelled']))
|
||||
key = 'available_equipment_instances'
|
||||
|
||||
# Get all equipment instances associated with non-finalized transactions
|
||||
non_finalized_equipments = EquipmentInstance.objects.filter(
|
||||
transaction__in=non_finalized_transactions)
|
||||
queryset = cache.get(key)
|
||||
if not queryset:
|
||||
# Get all non-finalized transactions
|
||||
non_finalized_transactions = Transaction.objects.filter(
|
||||
~Q(transaction_status__in=[
|
||||
'Finalized', 'Rejected', 'Cancelled'])
|
||||
).prefetch_related('equipments')
|
||||
|
||||
# Get all equipment instances which are not associated with non-finalized transactions
|
||||
queryset = EquipmentInstance.objects.exclude(
|
||||
id__in=non_finalized_equipments.values_list('id', flat=True)).order_by('id')
|
||||
# Get all equipment instances associated with non-finalized transactions
|
||||
non_finalized_equipments = EquipmentInstance.objects.filter(
|
||||
transaction__in=non_finalized_transactions
|
||||
).prefetch_related('equipment')
|
||||
|
||||
return queryset
|
||||
# Get all equipment instances which are not associated with non-finalized transactions
|
||||
queryset = EquipmentInstance.objects.exclude(
|
||||
id__in=non_finalized_equipments.values_list('id', flat=True)
|
||||
)
|
||||
cache.set(key, list(queryset), timeout=60*60*24)
|
||||
|
||||
return queryset.prefetch_related('equipment')
|
||||
|
||||
# For viewing all equipment instance logs
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue