Added all required functionality for equipments and equipment handling

This commit is contained in:
Keannu Bernasol 2023-11-12 21:45:39 +08:00
parent 2d0e602ba1
commit 7609152767
17 changed files with 485 additions and 3 deletions

View file

@ -14,6 +14,7 @@ drf-spectacular = {version = "*", extras = ["sidecar"]}
django-extra-fields = "*"
pillow = "*"
psycopg2 = "*"
django-simple-history = "*"
[dev-packages]

10
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "7ca25164f3d5c5501004130b514ab73101cc9d6937aa0af8356bf65a3cb354fe"
"sha256": "ebe508cba3dbd2fa271c39d69ab71cb95f5630e21875aea04ad9f6893ced1877"
},
"pipfile-spec": 6,
"requires": {
@ -254,6 +254,14 @@
"index": "pypi",
"version": "==3.0.2"
},
"django-simple-history": {
"hashes": [
"sha256:19bd1a87e1e2eba34dfd43eab1fcf2da5752221f343232f2372b2121c7e3b97d",
"sha256:992dcca3cddc0b67b470fc91f77292e2d2a6010d37c9eac3536e9d80e8754032"
],
"index": "pypi",
"version": "==3.4.0"
},
"django-templated-mail": {
"hashes": [
"sha256:8db807effebb42a532622e2d142dfd453dafcd0d7794c4c3332acb90656315f9",

View file

@ -1,5 +1,6 @@
from django.urls import path, include
urlpatterns = [
path('accounts/', include('accounts.urls'))
path('accounts/', include('accounts.urls')),
path('equipments/', include('equipments.urls'))
]

View file

@ -64,11 +64,13 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_simplejwt',
'simple_history',
'djoser',
'corsheaders',
'drf_spectacular',
'drf_spectacular_sidecar',
'accounts'
'accounts',
'equipments',
]
MIDDLEWARE = [

Binary file not shown.

View file

View file

@ -0,0 +1,5 @@
from django.contrib import admin
from .models import Equipment, EquipmentInstance
admin.site.register(Equipment)
admin.site.register(EquipmentInstance)

View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class EquipmentsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'equipments'

View file

@ -0,0 +1,66 @@
# Generated by Django 4.2.7 on 2023-11-12 12:07
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import simple_history.models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Equipment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=40)),
('description', models.TextField(max_length=512)),
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('last_updated', models.DateTimeField(auto_now=True)),
('last_changed_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='HistoricalEquipmentInstance',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('status', models.CharField(choices=[('Working', 'Working'), ('Broken', 'Broken'), ('Under Maintenance', 'Under Maintenance'), ('Decomissioned', 'Decomissioned ')], default='PENDING', max_length=20)),
('remarks', models.TextField(max_length=512)),
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('last_updated', models.DateTimeField(blank=True, editable=False)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('equipment', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='equipments.equipment')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('last_changed_by', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical equipment instance',
'verbose_name_plural': 'historical equipment instances',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='EquipmentInstance',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('Working', 'Working'), ('Broken', 'Broken'), ('Under Maintenance', 'Under Maintenance'), ('Decomissioned', 'Decomissioned ')], default='PENDING', max_length=20)),
('remarks', models.TextField(max_length=512)),
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('last_updated', models.DateTimeField(auto_now=True)),
('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipments.equipment')),
('last_changed_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -0,0 +1,25 @@
# Generated by Django 4.2.7 on 2023-11-12 12:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('equipments', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='equipment',
name='last_changed_by',
),
migrations.RemoveField(
model_name='equipmentinstance',
name='last_changed_by',
),
migrations.RemoveField(
model_name='historicalequipmentinstance',
name='last_changed_by',
),
]

View file

@ -0,0 +1,40 @@
# Generated by Django 4.2.7 on 2023-11-12 12:27
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import simple_history.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('equipments', '0002_remove_equipment_last_changed_by_and_more'),
]
operations = [
migrations.CreateModel(
name='HistoricalEquipment',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('name', models.CharField(max_length=40)),
('description', models.TextField(max_length=512)),
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
('last_updated', models.DateTimeField(blank=True, editable=False)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical equipment',
'verbose_name_plural': 'historical equipments',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]

View file

@ -0,0 +1,50 @@
from django.db import models
from django.utils.timezone import now
from simple_history.models import HistoricalRecords
from django.db.models.signals import post_migrate
from django.dispatch import receiver
# Create your models here.
class Equipment(models.Model):
name = models.CharField(max_length=40)
description = models.TextField(max_length=512)
date_added = models.DateTimeField(default=now, editable=False)
last_updated = models.DateTimeField(auto_now=True, editable=False)
history = HistoricalRecords()
def __str__(self):
return f'{self.name} ID:{self.id}'
class EquipmentInstance(models.Model):
STATUS_CHOICES = (
('Working', 'Working'),
('Broken', 'Broken'),
('Under Maintenance', 'Under Maintenance'),
('Decomissioned', 'Decomissioned '),
)
equipment = models.ForeignKey(Equipment, on_delete=models.CASCADE)
status = models.CharField(
max_length=20, choices=STATUS_CHOICES, default='PENDING')
remarks = models.TextField(max_length=512)
date_added = models.DateTimeField(default=now, editable=False)
last_updated = models.DateTimeField(auto_now=True, editable=False)
history = HistoricalRecords()
def __str__(self):
return f'{self.equipment.name} ID:{self.id}'
@receiver(post_migrate)
def create_superuser(sender, **kwargs):
if sender.name == 'equipments':
EQUIPMENT, CREATED = Equipment.objects.get_or_create(
name="HP All-in-One PC", description="I5 6500 8GB RAM 1TB HDD")
if (CREATED):
print("Created sample equipment")
EQUIPMENT_INSTANCE, CREATED = EquipmentInstance.objects.get_or_create(
equipment=EQUIPMENT, status="Working", remarks="First PC of USTP!")
if (CREATED):
print("Created sample equipment instance")

View file

@ -0,0 +1,157 @@
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Equipment, EquipmentInstance
# -- Equipment Serializers
class EquipmentHistoricalRecordField(serializers.ListField):
child = serializers.DictField()
def to_representation(self, data):
return super().to_representation(data.values('name', 'description', 'history_date', 'history_user').order_by('-history_date'))
class EquipmentSerializer(serializers.HyperlinkedModelSerializer):
date_added = serializers.DateTimeField(
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()
name = serializers.CharField()
description = serializers.CharField()
class Meta:
model = Equipment
fields = ('id', 'name', 'description',
'last_updated', 'last_updated_by', 'date_added')
read_only_fields = ('id', 'last_updated',
'last_updated_by', 'date_added')
def get_history_user(self, obj):
return obj.history_user.username if obj.history_user else None
def get_last_updated_by(self, obj):
return obj.history.first().history_user if obj.history.first().history_user else None
class EquipmentLogsSerializer(serializers.HyperlinkedModelSerializer):
history_date = serializers.DateTimeField(
format="%m-%d-%Y %I:%M%p", read_only=True)
history_user = serializers.SerializerMethodField()
class Meta:
model = Equipment.history.model
fields = ('history_id', 'name', 'description',
'history_date', 'history_user')
read_only_fields = ('history_id', 'id', 'name', 'description',
'history_date', 'history_user')
def get_history_user(self, obj):
return obj.history_user.username if obj.history_user else None
class EquipmentLogSerializer(serializers.HyperlinkedModelSerializer):
date_added = serializers.DateTimeField(
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()
name = serializers.CharField()
description = serializers.CharField()
history = EquipmentHistoricalRecordField()
class Meta:
model = Equipment
fields = ('id', 'name', 'description',
'last_updated', 'date_added', 'last_updated_by', 'history')
read_only_fields = ('id', 'last_updated',
'date_added', 'last_updated_by', 'history')
def get_last_updated_by(self, obj):
return obj.history.first().history_user if obj.history.first().history_user else None
# -- Equipment Instance Serializers
class EquipmentInstanceHistoricalRecordField(serializers.ListField):
child = serializers.DictField()
def to_representation(self, data):
return super().to_representation(data.values('equipment', 'status', 'remarks', 'history_date', 'history_user').order_by('-history_date'))
class EquipmentInstanceSerializer(serializers.HyperlinkedModelSerializer):
equipment = serializers.PrimaryKeyRelatedField(
source='equipment.name', queryset=Equipment.objects.all())
status = serializers.CharField()
remarks = serializers.CharField()
date_added = serializers.DateTimeField(
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()
# Forbid user from changing equipment field once the instance is already created
def update(self, instance, validated_data):
# Ignore any changes to 'equipment'
validated_data.pop('equipment', None)
return super().update(instance, validated_data)
class Meta:
model = EquipmentInstance
fields = ('id', 'equipment', 'status', 'remarks',
'last_updated', 'last_updated_by', 'date_added')
read_only_fields = ('id', 'last_updated',
'last_updated_by', 'date_added')
def get_history_user(self, obj):
return obj.history_user.username if obj.history_user else None
def get_last_updated_by(self, obj):
return obj.history.first().history_user if obj.history.first().history_user else None
class EquipmentInstanceLogsSerializer(serializers.HyperlinkedModelSerializer):
history_date = serializers.DateTimeField(
format="%m-%d-%Y %I:%M%p", read_only=True)
history_user = serializers.SerializerMethodField()
class Meta:
model = EquipmentInstance.history.model
fields = ('history_id', 'status', 'remarks',
'history_date', 'history_user')
read_only_fields = ('history_id', 'id', 'status', 'remarks',
'history_date', 'history_user')
def get_history_user(self, obj):
return obj.history_user.username if obj.history_user else None
class EquipmentInstanceLogSerializer(serializers.HyperlinkedModelSerializer):
equipment = serializers.PrimaryKeyRelatedField(
source='equipment.name', queryset=Equipment.objects.all())
status = serializers.CharField()
remarks = serializers.CharField()
date_added = serializers.DateTimeField(
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()
history = EquipmentInstanceHistoricalRecordField()
# Forbid user from changing equipment field once the instance is already created
def update(self, instance, validated_data):
# Ignore any changes to 'equipment'
validated_data.pop('equipment', None)
return super().update(instance, validated_data)
class Meta:
model = EquipmentInstance
fields = ('id', 'equipment', 'status', 'remarks',
'last_updated', 'date_added', 'last_updated_by', 'history')
read_only_fields = ('id', 'last_updated',
'date_added', 'last_updated_by', 'history')
def get_last_updated_by(self, obj):
return obj.history.first().history_user if obj.history.first().history_user else None

View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View file

@ -0,0 +1,29 @@
from django.urls import include, path
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
# For viewing all equipments
router.register(r'equipments', views.EquipmentViewSet)
router.register(r'equipment_instances', views.EquipmentInstanceViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
path('', include(router.urls)),
# Logs for all equipments
path('equipments/logs', views.EquipmentsLogsViewSet.as_view()),
# Logs for each equipment
path('equipments/<int:equipment_id>/logs/',
views.EquipmentLogViewSet.as_view({'get': 'list'})),
# Last changed equipment
path('equipments/latest', views.LastUpdatedEquipmentViewSet.as_view()),
# Logs for all equipment instances
path('equipment_instances/logs', views.EquipmentInstancesLogsViewSet.as_view()),
# Logs for each equipment instance
path('equipment_instances/<int:equipment_id>/logs/',
views.EquipmentInstanceLogViewSet.as_view({'get': 'list'})),
# Last changed equipment instance
path('equipment_instances/latest',
views.LastUpdatedEquipmentInstanceViewSet.as_view())
]

View file

@ -0,0 +1,89 @@
from rest_framework.permissions import IsAuthenticated
from rest_framework import viewsets, generics
from .models import Equipment, EquipmentInstance
from . import serializers
from config.settings import DEBUG
# -- Equipment Viewsets
class EquipmentViewSet(viewsets.ModelViewSet):
if (not DEBUG):
permission_classes = [IsAuthenticated]
serializer_class = serializers.EquipmentSerializer
queryset = Equipment.objects.all().order_by('-date_added')
# For viewing all logs for all equipments
class EquipmentsLogsViewSet(generics.ListAPIView):
if (not DEBUG):
permission_classes = [IsAuthenticated]
serializer_class = serializers.EquipmentLogsSerializer
queryset = Equipment.history.all().order_by('-history_date')
# For viewing logs per individual equipment
class EquipmentLogViewSet(viewsets.ReadOnlyModelViewSet):
if (not DEBUG):
permission_classes = [IsAuthenticated]
serializer_class = serializers.EquipmentLogSerializer
def get_queryset(self):
equipment_id = self.kwargs['equipment_id']
return Equipment.objects.filter(id=equipment_id)
# Last changed equipment
class LastUpdatedEquipmentViewSet(generics.ListAPIView):
if (not DEBUG):
permission_classes = [IsAuthenticated]
serializer_class = serializers.EquipmentSerializer
queryset = Equipment.objects.all().order_by('-date_added')
def get_queryset(self):
return super().get_queryset()[:1]
# -- Equipment Instance Viewsets
class EquipmentInstanceViewSet(viewsets.ModelViewSet):
if (not DEBUG):
permission_classes = [IsAuthenticated]
serializer_class = serializers.EquipmentInstanceSerializer
queryset = EquipmentInstance.objects.all().order_by('-date_added')
# For viewing all equipment instance logs
class EquipmentInstancesLogsViewSet(generics.ListAPIView):
if (not DEBUG):
permission_classes = [IsAuthenticated]
serializer_class = serializers.EquipmentInstanceLogsSerializer
queryset = EquipmentInstance.history.all().order_by('-history_date')
# For viewing logs per individual equipment instance
class EquipmentInstanceLogViewSet(viewsets.ReadOnlyModelViewSet):
if (not DEBUG):
permission_classes = [IsAuthenticated]
serializer_class = serializers.EquipmentInstanceLogSerializer
def get_queryset(self):
equipment_id = self.kwargs['equipment_id']
return EquipmentInstance.objects.filter(id=equipment_id)
# Last changed equipment instance
class LastUpdatedEquipmentInstanceViewSet(generics.ListAPIView):
if (not DEBUG):
permission_classes = [IsAuthenticated]
serializer_class = serializers.EquipmentInstanceSerializer
queryset = EquipmentInstance.objects.all().order_by('-date_added')
def get_queryset(self):
return super().get_queryset()[:1]