mirror of
https://github.com/lemeow125/Borrowing-TrackerBackend.git
synced 2024-11-16 22:09:27 +08:00
Added transactions and updated models to reflect transactions
This commit is contained in:
parent
d915852632
commit
2c8cc87cbe
39 changed files with 635 additions and 291 deletions
|
@ -6,9 +6,9 @@ from .models import CustomUser
|
|||
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
model = CustomUser
|
||||
list_display = UserAdmin.list_display + ('is_technician',)
|
||||
list_display = UserAdmin.list_display + ('is_technician', 'is_teacher')
|
||||
fieldsets = UserAdmin.fieldsets + (
|
||||
(None, {'fields': ('is_technician',)}),
|
||||
(None, {'fields': ('is_technician', 'is_teacher')}),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 4.2.7 on 2023-12-02 12:25
|
||||
# Generated by Django 4.2.7 on 2023-12-08 14:41
|
||||
|
||||
import accounts.models
|
||||
import django.contrib.auth.models
|
||||
|
@ -31,6 +31,7 @@ class Migration(migrations.Migration):
|
|||
('last_name', models.CharField(max_length=100)),
|
||||
('is_active', models.BooleanField(default=False)),
|
||||
('is_technician', models.BooleanField(default=False)),
|
||||
('is_teacher', models.BooleanField(default=False)),
|
||||
('avatar', models.ImageField(null=True, upload_to=accounts.models.CustomUser._get_upload_to)),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
|
|
|
@ -38,6 +38,7 @@ class CustomUser(AbstractUser):
|
|||
# is_admin inherited from base user class
|
||||
is_active = models.BooleanField(default=False)
|
||||
is_technician = models.BooleanField(default=False)
|
||||
is_teacher = models.BooleanField(default=False)
|
||||
avatar = models.ImageField(upload_to=_get_upload_to, null=True)
|
||||
|
||||
@property
|
||||
|
@ -66,7 +67,7 @@ def create_superuser(sender, **kwargs):
|
|||
print('Created admin account')
|
||||
superuser.save()
|
||||
|
||||
username = 'test-user-technician'
|
||||
username = 'test-technician'
|
||||
email = os.getenv('DJANGO_ADMIN_EMAIL')
|
||||
password = os.getenv('DJANGO_ADMIN_PASSWORD')
|
||||
first_name = 'Test'
|
||||
|
@ -81,3 +82,35 @@ def create_superuser(sender, **kwargs):
|
|||
user.is_active = True
|
||||
print('Created debug technician account')
|
||||
user.save()
|
||||
|
||||
username = 'test-teacher'
|
||||
email = os.getenv('DJANGO_ADMIN_EMAIL')
|
||||
password = os.getenv('DJANGO_ADMIN_PASSWORD')
|
||||
first_name = 'Test'
|
||||
last_name = 'Teacher'
|
||||
|
||||
if not User.objects.filter(username=username).exists():
|
||||
# Create the superuser with is_active set to False
|
||||
user = User.objects.create_user(
|
||||
username=username, email=email, password=password, first_name=first_name, last_name=last_name, is_teacher=True)
|
||||
|
||||
# Activate the user
|
||||
user.is_active = True
|
||||
print('Created debug teacher account')
|
||||
user.save()
|
||||
|
||||
username = 'test-student'
|
||||
email = os.getenv('DJANGO_ADMIN_EMAIL')
|
||||
password = os.getenv('DJANGO_ADMIN_PASSWORD')
|
||||
first_name = 'Test'
|
||||
last_name = 'Student'
|
||||
|
||||
if not User.objects.filter(username=username).exists():
|
||||
# Create the superuser with is_active set to False
|
||||
user = User.objects.create_user(
|
||||
username=username, email=email, password=password, first_name=first_name, last_name=last_name)
|
||||
|
||||
# Activate the user
|
||||
user.is_active = True
|
||||
print('Created debug student account')
|
||||
user.save()
|
||||
|
|
|
@ -9,3 +9,23 @@ class IsTechnician(BasePermission):
|
|||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
return request.user.is_authenticated and request.user.is_technician
|
||||
|
||||
|
||||
class IsTeacher(BasePermission):
|
||||
message = "You must be a teacher to perform this action."
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return request.user.is_authenticated and request.user.is_teacher
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
return request.user.is_authenticated and request.user.is_teacher
|
||||
|
||||
|
||||
class IsStudent(BasePermission):
|
||||
message = "You must be a student to perform this action."
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return request.user.is_authenticated and request.user.is_student
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
return request.user.is_authenticated and request.user.is_student
|
||||
|
|
|
@ -18,7 +18,8 @@ class CustomUserSerializer(BaseUserSerializer):
|
|||
|
||||
class Meta(BaseUserSerializer.Meta):
|
||||
model = CustomUser
|
||||
fields = ('username', 'email', 'avatar', 'first_name', 'last_name',)
|
||||
fields = ('username', 'email', 'avatar', 'first_name',
|
||||
'last_name', 'is_teacher', 'is_technician')
|
||||
|
||||
|
||||
class UserRegistrationSerializer(serializers.ModelSerializer):
|
||||
|
@ -30,6 +31,7 @@ class UserRegistrationSerializer(serializers.ModelSerializer):
|
|||
model = CustomUser # Use your custom user model here
|
||||
fields = ('username', 'email', 'password', 'avatar',
|
||||
'first_name', 'last_name')
|
||||
read_only_fields = ('is_teacher', 'is_technician')
|
||||
|
||||
def validate(self, attrs):
|
||||
user = self.Meta.model(**attrs)
|
||||
|
|
|
@ -3,5 +3,5 @@ from django.urls import path, include
|
|||
urlpatterns = [
|
||||
path('accounts/', include('accounts.urls')),
|
||||
path('equipments/', include('equipments.urls')),
|
||||
path('equipment_groups/', include('equipment_groups.urls'))
|
||||
path('transactions/', include('transactions.urls'))
|
||||
]
|
||||
|
|
3
equipment_tracker/breakages/admin.py
Normal file
3
equipment_tracker/breakages/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -1,6 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class EquipmentGroupsConfig(AppConfig):
|
||||
class BreakagesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'equipment_groups'
|
||||
name = 'breakages'
|
3
equipment_tracker/breakages/models.py
Normal file
3
equipment_tracker/breakages/models.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
3
equipment_tracker/breakages/views.py
Normal file
3
equipment_tracker/breakages/views.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
|
@ -73,7 +73,7 @@ INSTALLED_APPS = [
|
|||
'drf_spectacular_sidecar',
|
||||
'accounts',
|
||||
'equipments',
|
||||
'equipment_groups',
|
||||
'transactions',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -137,8 +137,8 @@ REST_FRAMEWORK = {
|
|||
|
||||
# DRF-Spectacular
|
||||
SPECTACULAR_SETTINGS = {
|
||||
'TITLE': 'CITC Equipment Tracker Backend',
|
||||
'DESCRIPTION': 'An IT Elective 4 Project',
|
||||
'TITLE': 'Equipment Tracker Backend',
|
||||
'DESCRIPTION': 'A Project',
|
||||
'VERSION': '1.0.0',
|
||||
'SERVE_INCLUDE_SCHEMA': False,
|
||||
'SWAGGER_UI_DIST': 'SIDECAR',
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
from django.contrib import admin
|
||||
from .models import EquipmentGroup
|
||||
from simple_history.admin import SimpleHistoryAdmin
|
||||
|
||||
|
||||
@admin.register(EquipmentGroup)
|
||||
class EquipmentGroupAdmin(SimpleHistoryAdmin):
|
||||
readonly_fields = ['status', 'date_added', 'last_updated']
|
||||
list_display = ('name', 'status', 'date_added', 'last_updated')
|
|
@ -1,53 +0,0 @@
|
|||
# Generated by Django 4.2.7 on 2023-12-02 12:25
|
||||
|
||||
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),
|
||||
('equipments', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HistoricalEquipmentGroup',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('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)),
|
||||
('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 group',
|
||||
'verbose_name_plural': 'historical equipment groups',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EquipmentGroup',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('remarks', models.TextField(max_length=512)),
|
||||
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('equipments', models.ManyToManyField(to='equipments.equipmentinstance')),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -1,16 +0,0 @@
|
|||
# Generated by Django 4.2.7 on 2023-12-05 12:31
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment_groups', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='HistoricalEquipmentGroup',
|
||||
),
|
||||
]
|
Binary file not shown.
Binary file not shown.
|
@ -1,39 +0,0 @@
|
|||
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
|
||||
from equipments.models import EquipmentInstance
|
||||
|
||||
|
||||
class EquipmentGroup(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
remarks = models.TextField(max_length=512)
|
||||
date_added = models.DateTimeField(default=now, editable=False)
|
||||
last_updated = models.DateTimeField(auto_now=True, editable=False)
|
||||
equipments = models.ManyToManyField(EquipmentInstance)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
if self.equipments.filter(status='Broken').exists():
|
||||
return 'Broken'
|
||||
elif self.equipments.filter(status='Under Maintenance').exists():
|
||||
return 'Under Maintenance'
|
||||
elif self.equipments.filter(status='Decomissioned').exists():
|
||||
return 'Decomissioned'
|
||||
else:
|
||||
return 'Working'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
@receiver(post_migrate)
|
||||
def create_superuser(sender, **kwargs):
|
||||
if sender.name == 'equipment_groups':
|
||||
PC = EquipmentInstance.objects.filter(id=1).first().id
|
||||
KEYBOARD = EquipmentInstance.objects.filter(id=2).first().id
|
||||
MOUSE = EquipmentInstance.objects.filter(id=3).first().id
|
||||
GROUP, CREATED = EquipmentGroup.objects.get_or_create(
|
||||
name="HP All-In-One PC Set", remarks="First PC set of citc tracker!")
|
||||
GROUP.equipments.set([PC, KEYBOARD, MOUSE])
|
|
@ -1,29 +0,0 @@
|
|||
from rest_framework import serializers
|
||||
from .models import EquipmentGroup, EquipmentInstance
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
|
||||
# -- Equipment Group Serializers
|
||||
|
||||
|
||||
class EquipmentGroupSerializer(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)
|
||||
equipments = serializers.SlugRelatedField(
|
||||
many=True, slug_field='id', queryset=EquipmentInstance.objects.all())
|
||||
name = serializers.CharField()
|
||||
remarks = serializers.CharField()
|
||||
|
||||
class Meta:
|
||||
model = EquipmentGroup
|
||||
fields = ('__all__')
|
||||
read_only_fields = ('id', 'last_updated', 'equipments',
|
||||
'last_updated_by', 'date_added')
|
||||
|
||||
def to_representation(self, instance):
|
||||
representation = super().to_representation(instance)
|
||||
representation['equipments'] = [
|
||||
equipment.equipment.name for equipment in instance.equipments.all()]
|
||||
return representation
|
|
@ -1,16 +0,0 @@
|
|||
from django.urls import include, path
|
||||
from rest_framework import routers
|
||||
from . import views
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
# For viewing all equipments
|
||||
router.register(r'equipment_groups', views.EquipmentGroupViewSet)
|
||||
|
||||
# Wire up our API using automatic URL routing.
|
||||
# Additionally, we include login URLs for the browsable API.
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
# Last changed equipment group
|
||||
path('equipment_groups/latest',
|
||||
views.LastUpdatedEquipmentGroupViewSet.as_view()),
|
||||
]
|
|
@ -1,27 +0,0 @@
|
|||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework import viewsets, generics
|
||||
from .models import EquipmentGroup
|
||||
from . import serializers
|
||||
from config.settings import DEBUG
|
||||
from accounts.permissions import IsTechnician
|
||||
|
||||
# -- Equipment Viewsets
|
||||
|
||||
|
||||
class EquipmentGroupViewSet(viewsets.ModelViewSet):
|
||||
if (not DEBUG):
|
||||
permission_classes = [IsAuthenticated, IsTechnician]
|
||||
serializer_class = serializers.EquipmentGroupSerializer
|
||||
queryset = EquipmentGroup.objects.all().order_by('id')
|
||||
|
||||
# Last changed equipment
|
||||
|
||||
|
||||
class LastUpdatedEquipmentGroupViewSet(generics.ListAPIView):
|
||||
if (not DEBUG):
|
||||
permission_classes = [IsAuthenticated, IsTechnician]
|
||||
serializer_class = serializers.EquipmentGroupSerializer
|
||||
queryset = EquipmentGroup.objects.all().order_by('-date_added')
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset()[:1]
|
11
equipment_tracker/equipments/choices.py
Normal file
11
equipment_tracker/equipments/choices.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
EQUIPMENT_CATEGORY_CHOICES = (
|
||||
('Glassware', 'Glassware'),
|
||||
('Miscellaneous', 'Miscellaneous')
|
||||
)
|
||||
|
||||
|
||||
EQUIPMENT_INSTANCE_STATUS_CHOICES = (
|
||||
('Working', 'Working'),
|
||||
('Broken', 'Broken'),
|
||||
('Borrowed', 'Borrowed'),
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 4.2.7 on 2023-12-02 12:25
|
||||
# Generated by Django 4.2.7 on 2023-12-08 14:41
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
@ -21,18 +21,18 @@ class Migration(migrations.Migration):
|
|||
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)),
|
||||
('category', models.CharField(choices=[('Glassware', 'Glassware'), ('Miscellaneous', 'Miscellaneous')], default='Miscellaneous', max_length=20)),
|
||||
('description', models.TextField(max_length=512, null=True)),
|
||||
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('category', models.CharField(choices=[('PC', 'PC'), ('NETWORKING', 'Networking'), ('CCTV', 'CCTV'), ('FURNITURE', 'Furniture'), ('PERIPHERALS', 'Peripherals'), ('MISC', 'Miscellaneous')], default='MISC', max_length=20)),
|
||||
],
|
||||
),
|
||||
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'), ('MAINTENANCE', 'Under Maintenance'), ('DECOMISSIONED', 'Decomissioned')], default='PENDING', max_length=20)),
|
||||
('remarks', models.TextField(max_length=512)),
|
||||
('status', models.CharField(choices=[('Working', 'Working'), ('Broken', 'Broken'), ('Borrowed', 'Borrowed')], default='PENDING', max_length=20)),
|
||||
('remarks', models.TextField(max_length=512, null=True)),
|
||||
('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)),
|
||||
|
@ -55,10 +55,10 @@ class Migration(migrations.Migration):
|
|||
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)),
|
||||
('category', models.CharField(choices=[('Glassware', 'Glassware'), ('Miscellaneous', 'Miscellaneous')], default='Miscellaneous', max_length=20)),
|
||||
('description', models.TextField(max_length=512, null=True)),
|
||||
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||
('last_updated', models.DateTimeField(blank=True, editable=False)),
|
||||
('category', models.CharField(choices=[('PC', 'PC'), ('NETWORKING', 'Networking'), ('CCTV', 'CCTV'), ('FURNITURE', 'Furniture'), ('PERIPHERALS', 'Peripherals'), ('MISC', 'Miscellaneous')], default='MISC', max_length=20)),
|
||||
('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)),
|
||||
|
@ -77,8 +77,8 @@ class Migration(migrations.Migration):
|
|||
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'), ('MAINTENANCE', 'Under Maintenance'), ('DECOMISSIONED', 'Decomissioned')], default='PENDING', max_length=20)),
|
||||
('remarks', models.TextField(max_length=512)),
|
||||
('status', models.CharField(choices=[('Working', 'Working'), ('Broken', 'Broken'), ('Borrowed', 'Borrowed')], default='PENDING', max_length=20)),
|
||||
('remarks', models.TextField(max_length=512, null=True)),
|
||||
('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')),
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
# Generated by Django 4.2.7 on 2023-12-02 13:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipments', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipment',
|
||||
name='description',
|
||||
field=models.TextField(max_length=512, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentinstance',
|
||||
name='remarks',
|
||||
field=models.TextField(max_length=512, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalequipment',
|
||||
name='description',
|
||||
field=models.TextField(max_length=512, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalequipmentinstance',
|
||||
name='remarks',
|
||||
field=models.TextField(max_length=512, null=True),
|
||||
),
|
||||
]
|
|
@ -3,26 +3,19 @@ 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):
|
||||
|
||||
CATEGORY_CHOICES = (
|
||||
('PC', 'PC'),
|
||||
('NETWORKING', 'Networking'),
|
||||
('CCTV', 'CCTV'),
|
||||
('FURNITURE', 'Furniture'),
|
||||
('PERIPHERALS', 'Peripherals'),
|
||||
('MISC', 'Miscellaneous')
|
||||
EQUIPMENT_CATEGORY_CHOICES = (
|
||||
('Glassware', 'Glassware'),
|
||||
('Miscellaneous', 'Miscellaneous')
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=40)
|
||||
category = models.CharField(
|
||||
max_length=20, choices=EQUIPMENT_CATEGORY_CHOICES, default='Miscellaneous')
|
||||
description = models.TextField(max_length=512, null=True)
|
||||
date_added = models.DateTimeField(default=now, editable=False)
|
||||
last_updated = models.DateTimeField(auto_now=True, editable=False)
|
||||
category = models.CharField(
|
||||
max_length=20, choices=CATEGORY_CHOICES, default='MISC')
|
||||
history = HistoricalRecords()
|
||||
|
||||
def __str__(self):
|
||||
|
@ -30,16 +23,14 @@ class Equipment(models.Model):
|
|||
|
||||
|
||||
class EquipmentInstance(models.Model):
|
||||
STATUS_CHOICES = (
|
||||
('WORKING', 'Working'),
|
||||
('BROKEN', 'Broken'),
|
||||
('MAINTENANCE', 'Under Maintenance'),
|
||||
('DECOMISSIONED', 'Decomissioned'),
|
||||
EQUIPMENT_INSTANCE_STATUS_CHOICES = (
|
||||
('Working', 'Working'),
|
||||
('Broken', 'Broken'),
|
||||
('Borrowed', 'Borrowed'),
|
||||
)
|
||||
|
||||
equipment = models.ForeignKey(Equipment, on_delete=models.CASCADE)
|
||||
status = models.CharField(
|
||||
max_length=20, choices=STATUS_CHOICES, default='PENDING')
|
||||
max_length=20, choices=EQUIPMENT_INSTANCE_STATUS_CHOICES, default='PENDING')
|
||||
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)
|
||||
|
@ -53,14 +44,14 @@ class EquipmentInstance(models.Model):
|
|||
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", category="PC")
|
||||
name="Pyrex Beaker", description="", category="Glassware")
|
||||
EQUIPMENT_INSTANCE, CREATED = EquipmentInstance.objects.get_or_create(
|
||||
equipment=EQUIPMENT, status="WORKING", remarks="First PC of citc equipment tracker!")
|
||||
equipment=EQUIPMENT, status="Working", remarks="First beaker of equipment tracker!")
|
||||
EQUIPMENT, CREATED = Equipment.objects.get_or_create(
|
||||
name="HP Keyboard", description="Generic Membrane Keyboard", category="PERIPHERALS")
|
||||
name="Bunsen Burner", description="", category="Miscellaneous")
|
||||
EQUIPMENT_INSTANCE, CREATED = EquipmentInstance.objects.get_or_create(
|
||||
equipment=EQUIPMENT, status="WORKING", remarks="First keyboard of citc equipment tracker!")
|
||||
equipment=EQUIPMENT, status="Working", remarks="First bunsen burner of equipment tracker!")
|
||||
EQUIPMENT, CREATED = Equipment.objects.get_or_create(
|
||||
name="HP Mouse", description="Generic Mouse", category="PERIPHERALS")
|
||||
name="Microscope", description="", category="Miscellaneous")
|
||||
EQUIPMENT_INSTANCE, CREATED = EquipmentInstance.objects.get_or_create(
|
||||
equipment=EQUIPMENT, status="WORKING", remarks="First mouse of citc equipment tracker!")
|
||||
equipment=EQUIPMENT, status="Working", remarks="First microscope of equipment tracker!")
|
||||
|
|
|
@ -3,7 +3,6 @@ from .models import Equipment, EquipmentInstance
|
|||
from drf_spectacular.utils import extend_schema_field
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from django.db.models import F
|
||||
from accounts.models import CustomUser
|
||||
|
||||
# -- Equipment Serializers
|
||||
|
||||
|
@ -104,7 +103,8 @@ class EquipmentInstanceSerializer(serializers.HyperlinkedModelSerializer):
|
|||
last_updated = serializers.DateTimeField(
|
||||
format="%m-%d-%Y %I:%M%p", read_only=True)
|
||||
last_updated_by = serializers.SerializerMethodField()
|
||||
status = serializers.ChoiceField(choices=EquipmentInstance.STATUS_CHOICES)
|
||||
status = serializers.ChoiceField(
|
||||
choices=EquipmentInstance.EQUIPMENT_INSTANCE_STATUS_CHOICES)
|
||||
|
||||
# Forbid user from changing equipment field once the instance is already created
|
||||
def update(self, instance, validated_data):
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
openapi: 3.0.3
|
||||
info:
|
||||
title: CITC Equipment Tracker Backend
|
||||
title: Equipment Tracker Backend
|
||||
version: 1.0.0
|
||||
description: An IT Elective 4 Project
|
||||
description: A Project
|
||||
paths:
|
||||
/api/v1/accounts/jwt/create/:
|
||||
post:
|
||||
|
@ -520,6 +520,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -547,6 +548,7 @@ paths:
|
|||
required: true
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'201':
|
||||
content:
|
||||
|
@ -567,6 +569,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -590,6 +593,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -622,6 +626,7 @@ paths:
|
|||
required: true
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -653,6 +658,7 @@ paths:
|
|||
$ref: '#/components/schemas/PatchedEquipmentInstance'
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -673,6 +679,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'204':
|
||||
description: No response body
|
||||
|
@ -683,6 +690,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -699,6 +707,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -715,6 +724,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -742,6 +752,7 @@ paths:
|
|||
required: true
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'201':
|
||||
content:
|
||||
|
@ -762,6 +773,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -785,6 +797,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -817,6 +830,7 @@ paths:
|
|||
required: true
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -848,6 +862,7 @@ paths:
|
|||
$ref: '#/components/schemas/PatchedEquipment'
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -868,6 +883,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'204':
|
||||
description: No response body
|
||||
|
@ -878,6 +894,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -894,6 +911,7 @@ paths:
|
|||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
|
@ -903,6 +921,147 @@ paths:
|
|||
items:
|
||||
$ref: '#/components/schemas/EquipmentLogs'
|
||||
description: ''
|
||||
/api/v1/transactions/:
|
||||
get:
|
||||
operationId: api_v1_transactions_list
|
||||
tags:
|
||||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Transaction'
|
||||
description: ''
|
||||
post:
|
||||
operationId: api_v1_transactions_create
|
||||
tags:
|
||||
- api
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Transaction'
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Transaction'
|
||||
multipart/form-data:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Transaction'
|
||||
required: true
|
||||
security:
|
||||
- jwtAuth: []
|
||||
responses:
|
||||
'201':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Transaction'
|
||||
description: ''
|
||||
/api/v1/transactions/{id}/:
|
||||
get:
|
||||
operationId: api_v1_transactions_retrieve
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: integer
|
||||
description: A unique integer value identifying this transaction.
|
||||
required: true
|
||||
tags:
|
||||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Transaction'
|
||||
description: ''
|
||||
put:
|
||||
operationId: api_v1_transactions_update
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: integer
|
||||
description: A unique integer value identifying this transaction.
|
||||
required: true
|
||||
tags:
|
||||
- api
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Transaction'
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Transaction'
|
||||
multipart/form-data:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Transaction'
|
||||
required: true
|
||||
security:
|
||||
- jwtAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Transaction'
|
||||
description: ''
|
||||
patch:
|
||||
operationId: api_v1_transactions_partial_update
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: integer
|
||||
description: A unique integer value identifying this transaction.
|
||||
required: true
|
||||
tags:
|
||||
- api
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PatchedTransaction'
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PatchedTransaction'
|
||||
multipart/form-data:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PatchedTransaction'
|
||||
security:
|
||||
- jwtAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Transaction'
|
||||
description: ''
|
||||
delete:
|
||||
operationId: api_v1_transactions_destroy
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: integer
|
||||
description: A unique integer value identifying this transaction.
|
||||
required: true
|
||||
tags:
|
||||
- api
|
||||
security:
|
||||
- jwtAuth: []
|
||||
responses:
|
||||
'204':
|
||||
description: No response body
|
||||
components:
|
||||
schemas:
|
||||
Activation:
|
||||
|
@ -917,20 +1076,12 @@ components:
|
|||
- uid
|
||||
CategoryEnum:
|
||||
enum:
|
||||
- PC
|
||||
- NETWORKING
|
||||
- CCTV
|
||||
- FURNITURE
|
||||
- PERIPHERALS
|
||||
- MISC
|
||||
- Glassware
|
||||
- Miscellaneous
|
||||
type: string
|
||||
description: |-
|
||||
* `PC` - PC
|
||||
* `NETWORKING` - Networking
|
||||
* `CCTV` - CCTV
|
||||
* `FURNITURE` - Furniture
|
||||
* `PERIPHERALS` - Peripherals
|
||||
* `MISC` - Miscellaneous
|
||||
* `Glassware` - Glassware
|
||||
* `Miscellaneous` - Miscellaneous
|
||||
CustomUser:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -953,6 +1104,10 @@ components:
|
|||
last_name:
|
||||
type: string
|
||||
maxLength: 100
|
||||
is_teacher:
|
||||
type: boolean
|
||||
is_technician:
|
||||
type: boolean
|
||||
required:
|
||||
- avatar
|
||||
- first_name
|
||||
|
@ -1215,6 +1370,10 @@ components:
|
|||
last_name:
|
||||
type: string
|
||||
maxLength: 100
|
||||
is_teacher:
|
||||
type: boolean
|
||||
is_technician:
|
||||
type: boolean
|
||||
PatchedEquipment:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1267,6 +1426,29 @@ components:
|
|||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
PatchedTransaction:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
readOnly: true
|
||||
borrower:
|
||||
type: integer
|
||||
nullable: true
|
||||
teacher:
|
||||
type: string
|
||||
format: uri
|
||||
nullable: true
|
||||
equipments:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
transaction_status:
|
||||
$ref: '#/components/schemas/TransactionStatusEnum'
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
SendEmailReset:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1302,16 +1484,14 @@ components:
|
|||
- new_username
|
||||
StatusEnum:
|
||||
enum:
|
||||
- WORKING
|
||||
- BROKEN
|
||||
- MAINTENANCE
|
||||
- DECOMISSIONED
|
||||
- Working
|
||||
- Broken
|
||||
- Borrowed
|
||||
type: string
|
||||
description: |-
|
||||
* `WORKING` - Working
|
||||
* `BROKEN` - Broken
|
||||
* `MAINTENANCE` - Under Maintenance
|
||||
* `DECOMISSIONED` - Decomissioned
|
||||
* `Working` - Working
|
||||
* `Broken` - Broken
|
||||
* `Borrowed` - Borrowed
|
||||
TokenObtainPair:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1352,6 +1532,53 @@ components:
|
|||
writeOnly: true
|
||||
required:
|
||||
- token
|
||||
Transaction:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
readOnly: true
|
||||
borrower:
|
||||
type: integer
|
||||
nullable: true
|
||||
teacher:
|
||||
type: string
|
||||
format: uri
|
||||
nullable: true
|
||||
equipments:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
transaction_status:
|
||||
$ref: '#/components/schemas/TransactionStatusEnum'
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
required:
|
||||
- id
|
||||
- timestamp
|
||||
- transaction_status
|
||||
TransactionStatusEnum:
|
||||
enum:
|
||||
- Pending Approval
|
||||
- Approved
|
||||
- Rejected
|
||||
- Cancelled
|
||||
- Borrowed
|
||||
- 'Returned: Pending Checking'
|
||||
- 'With Breakages: Pending Resolution'
|
||||
- Finalized
|
||||
type: string
|
||||
description: |-
|
||||
* `Pending Approval` - Pending Approval
|
||||
* `Approved` - Approved
|
||||
* `Rejected` - Rejected
|
||||
* `Cancelled` - Cancelled
|
||||
* `Borrowed` - Borrowed
|
||||
* `Returned: Pending Checking` - Returned: Pending Checking
|
||||
* `With Breakages: Pending Resolution` - With Breakages: Pending Resolution
|
||||
* `Finalized` - Finalized
|
||||
UserRegistration:
|
||||
type: object
|
||||
properties:
|
||||
|
|
0
equipment_tracker/transactions/__init__.py
Normal file
0
equipment_tracker/transactions/__init__.py
Normal file
16
equipment_tracker/transactions/admin.py
Normal file
16
equipment_tracker/transactions/admin.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from django.contrib import admin
|
||||
from .models import Transaction
|
||||
from accounts.models import CustomUser
|
||||
|
||||
|
||||
class TransactionAdmin(admin.ModelAdmin):
|
||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||
if db_field.name == "borrower":
|
||||
kwargs["queryset"] = CustomUser.objects.exclude(
|
||||
is_technician=True).exclude(is_teacher=True)
|
||||
elif db_field.name == "teacher":
|
||||
kwargs["queryset"] = CustomUser.objects.filter(is_teacher=True)
|
||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
|
||||
|
||||
admin.site.register(Transaction, TransactionAdmin)
|
6
equipment_tracker/transactions/apps.py
Normal file
6
equipment_tracker/transactions/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TransactionsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'transactions'
|
30
equipment_tracker/transactions/migrations/0001_initial.py
Normal file
30
equipment_tracker/transactions/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Generated by Django 4.2.7 on 2023-12-08 14:41
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('equipments', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Transaction',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('transaction_status', models.CharField(choices=[('Pending Approval', 'Pending Approval'), ('Approved', 'Approved'), ('Rejected', 'Rejected'), ('Cancelled', 'Cancelled'), ('Borrowed', 'Borrowed'), ('Returned: Pending Checking', 'Returned: Pending Checking'), ('With Breakages: Pending Resolution', 'With Breakages: Pending Resolution'), ('Finalized', 'Finalized')], default='Pending', max_length=40)),
|
||||
('timestamp', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||
('borrower', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowed_transactions', to=settings.AUTH_USER_MODEL)),
|
||||
('equipments', models.ManyToManyField(to='equipments.equipmentinstance')),
|
||||
('teacher', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='teacher_transactions', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
0
equipment_tracker/transactions/migrations/__init__.py
Normal file
0
equipment_tracker/transactions/migrations/__init__.py
Normal file
38
equipment_tracker/transactions/models.py
Normal file
38
equipment_tracker/transactions/models.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from django.db import models
|
||||
from accounts.models import CustomUser
|
||||
from equipments.models import EquipmentInstance
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
class Transaction(models.Model):
|
||||
TRANSACTION_STATUS_CHOICES = (
|
||||
# Transaction is pending approval
|
||||
('Pending Approval', 'Pending Approval'),
|
||||
# Transaction has been approved, pending delivery by labtech
|
||||
('Approved', 'Approved'),
|
||||
# Tranasction has been rejected
|
||||
('Rejected', 'Rejected'),
|
||||
# Transaction has been approved but has been cancelled due to rare circumstances
|
||||
('Cancelled', 'Cancelled'),
|
||||
# Transaction has been delivered and is on borrow
|
||||
('Borrowed', 'Borrowed'),
|
||||
# Transaction has been returned, pending checking
|
||||
('Returned: Pending Checking', 'Returned: Pending Checking'),
|
||||
# Transaction has been breakages after being returned, pending resolution
|
||||
('With Breakages: Pending Resolution',
|
||||
'With Breakages: Pending Resolution'),
|
||||
# Transaction has been finalized
|
||||
('Finalized', 'Finalized'),
|
||||
)
|
||||
|
||||
borrower = models.ForeignKey(
|
||||
CustomUser, on_delete=models.SET_NULL, null=True, related_name='borrowed_transactions')
|
||||
teacher = models.ForeignKey(
|
||||
CustomUser, on_delete=models.SET_NULL, null=True, related_name='teacher_transactions')
|
||||
equipments = models.ManyToManyField(EquipmentInstance)
|
||||
transaction_status = models.CharField(
|
||||
max_length=40, choices=TRANSACTION_STATUS_CHOICES, default='Pending')
|
||||
timestamp = models.DateTimeField(default=now, editable=False)
|
||||
|
||||
def __str__(self):
|
||||
return f"Transaction #{self.id} under {self.teacher} by {self.borrower}"
|
156
equipment_tracker/transactions/serializers.py
Normal file
156
equipment_tracker/transactions/serializers.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
from rest_framework import serializers
|
||||
from accounts.models import CustomUser
|
||||
from equipments.models import EquipmentInstance
|
||||
from .models import Transaction
|
||||
from accounts.models import CustomUser
|
||||
from config.settings import DEBUG
|
||||
|
||||
|
||||
class TransactionSerializer(serializers.HyperlinkedModelSerializer):
|
||||
borrower = serializers.SlugRelatedField(
|
||||
many=False, slug_field='id', queryset=CustomUser.objects.all(), required=False, allow_null=True)
|
||||
|
||||
equipments = serializers.SlugRelatedField(
|
||||
many=True, slug_field='id', queryset=EquipmentInstance.objects.filter(status="Working"), required=False)
|
||||
|
||||
transaction_status = serializers.ChoiceField(
|
||||
choices=Transaction.TRANSACTION_STATUS_CHOICES)
|
||||
|
||||
class Meta:
|
||||
model = Transaction
|
||||
fields = ['id', 'borrower', 'teacher',
|
||||
'equipments', 'transaction_status', 'timestamp']
|
||||
read_only_fields = ['id', 'timestamp']
|
||||
|
||||
def create(self, validated_data):
|
||||
# Any transactions created will be associated with the one sending the POST/CREATE request
|
||||
user = self.context['request'].user
|
||||
validated_data.data['borrower'] = user
|
||||
|
||||
# All created transactions will be labelled as Pending
|
||||
validated_data['transaction_status'] = 'Pending'
|
||||
|
||||
# If no teacher responsible for the borrow transaction is selected, raise an error
|
||||
if 'teacher' not in validated_data:
|
||||
raise serializers.ValidationError(
|
||||
"You have not selected a teacher responsible for your borrow transaction")
|
||||
|
||||
# If no borrower responsible for the borrow transaction is selected, raise an error
|
||||
if 'borrower' not in validated_data:
|
||||
raise serializers.ValidationError(
|
||||
"No borrower assigned for this transaction!")
|
||||
|
||||
# If the user in the teacher field != actually a teacher, raise an error
|
||||
borrower = validated_data.get('borrower')
|
||||
if borrower and borrower.is_teacher or borrower.is_technician:
|
||||
raise serializers.ValidationError(
|
||||
"The borrower must be a student. Not a teacher or techician")
|
||||
|
||||
# If the user in the teacher field != actually a teacher, raise an error
|
||||
teacher = validated_data.get('teacher')
|
||||
if teacher and not teacher.is_teacher:
|
||||
raise serializers.ValidationError(
|
||||
"The assigned teacher != a valid teacher")
|
||||
|
||||
# If the user in the teacher field != actually a teacher, raise an error
|
||||
teacher = validated_data.get('teacher')
|
||||
if teacher and not teacher.is_teacher:
|
||||
raise serializers.ValidationError(
|
||||
"The specified user != a teacher.")
|
||||
|
||||
# If there are no equipments specified, raise an error
|
||||
if 'equipments' in validated_data and validated_data['equipments'] == []:
|
||||
raise serializers.ValidationError(
|
||||
"You cannot create a transaction without any equipments selected"
|
||||
)
|
||||
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
user = self.context['request'].user
|
||||
equipments = validated_data['equipments']
|
||||
|
||||
# Check if any of the equipment instances are already in a non-finalized transaction
|
||||
for equipment in equipments:
|
||||
existing__pending_transactions = Transaction.objects.filter(
|
||||
equipments=equipment, status__in=['Pending', 'Approved', 'Borrowed', 'With Breakages: Pending Resolution'])
|
||||
if existing__pending_transactions.exists():
|
||||
raise serializers.ValidationError(
|
||||
f"Equipment {equipment.id} is still part of a non-finalized transaction")
|
||||
|
||||
# If user != a teacher or a technician, forbid them from changing the status of a transaction
|
||||
if not user.is_teacher and not user.technician and 'transaction_status' in validated_data and validated_data['transaction_status'] != instance.status:
|
||||
raise serializers.ValidationError(
|
||||
"You are not a teacher or technician. You do not have permission to change the status of transactions"
|
||||
)
|
||||
|
||||
# If user != a teacher, forbid them from changing the status of a transaction
|
||||
if not user.is_teacher and 'transaction_status' in validated_data and validated_data['transaction_status'] != instance.status:
|
||||
raise serializers.ValidationError(
|
||||
"You do not have permission to change the status of a transaction"
|
||||
)
|
||||
# Do not allow changes to equipments on created transactions
|
||||
if 'equipments' in validated_data and instance.equipments != validated_data['equipments']:
|
||||
raise serializers.ValidationError(
|
||||
"You cannot change the equipments of an already created transaction"
|
||||
)
|
||||
|
||||
# For already finalized/done transactions (Rejected or Finalized ones)
|
||||
# Do not allow any changes to already finalized transactions
|
||||
if instance.status in ['Rejected', 'Finalized']:
|
||||
raise serializers.ValidationError(
|
||||
"Unable to update rejected or finalized transaction. Please create a new one"
|
||||
)
|
||||
|
||||
# Check if the update involves the transaction status
|
||||
if 'transaction_status' in validated_data:
|
||||
|
||||
# For Pending transactions
|
||||
# If not changing to Approved or Rejected, throw an error
|
||||
if instance.status == 'Pending' and validated_data['transaction_status'] != 'Approved' or validated_data['transaction_status'] != 'Rejected':
|
||||
raise serializers.ValidationError(
|
||||
"A pending transaction can only change to Approved or Rejected"
|
||||
)
|
||||
|
||||
# For Approved transactions,
|
||||
# If not changing to Borrowed or Cancelled, throw an error
|
||||
# Already approved transactions can only be moved to Borrowed or Cancelled
|
||||
if instance.status == 'Approved' and validated_data['transaction_status'] != 'Borrowed' or validated_data != 'Cancelled':
|
||||
raise serializers.ValidationError(
|
||||
"An already approved transaction can only changed to Borrowed (On borrow) or Cancelled"
|
||||
)
|
||||
|
||||
# For Borrowed transactions,
|
||||
# If not changing to returned, throw an error
|
||||
# Borrowed transactions that can only be changed to returned, pending checking for broken items
|
||||
if instance.status == 'Borrowed' and validated_data['transaction_status'] != 'Finalized' or validated_data != 'With Breakages: Pending Resolution':
|
||||
raise serializers.ValidationError(
|
||||
"A borrowed transaction can only changed to status of Finalized or With Breakages: Pending Resolution"
|
||||
)
|
||||
|
||||
# For Return: Pending Checking transactions,
|
||||
# If not changing to With Breakages: Pending Resolution or Finalized, throw an error
|
||||
# Returned transactions can only be finalized without any issues or be marked as with breakages
|
||||
if instance.status == 'Returned: Pending Checking' and validated_data['transaction_status'] != 'Finalized' or validated_data != 'With Breakages: Pending Resolution':
|
||||
raise serializers.ValidationError(
|
||||
"A borrowed transaction can only changed to status of Finalized or With Breakages: Pending Resolution"
|
||||
)
|
||||
|
||||
# For transactions with pending breakage resolutions,
|
||||
# Do not allow updating of status. It should be updated within its respective breakage report
|
||||
# If it has been resolved there, this field will automatically update to Finalized
|
||||
if instance.status == 'With Breakages: Pending Resolution':
|
||||
raise serializers.ValidationError(
|
||||
"A transaction with pending breakage resolutions must be updated or resolved in its respective breakage report"
|
||||
)
|
||||
|
||||
# If there are no issues and a transaction changes from Approved to Borrowed, label the selected equipment's statuses as Borrowed
|
||||
if instance.status == 'Approved' and validated_data['transaction_status'] == 'Borrowed':
|
||||
equipments = validated_data.get('equipments', [])
|
||||
for equipment in equipments:
|
||||
equipment.status = 'Borrowed'
|
||||
equipment.save()
|
||||
return super().update(validated_data)
|
||||
# Changing equipment status of broken items when returned is handled in breakage reports
|
||||
|
||||
return super().update(instance, validated_data)
|
3
equipment_tracker/transactions/tests.py
Normal file
3
equipment_tracker/transactions/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
10
equipment_tracker/transactions/urls.py
Normal file
10
equipment_tracker/transactions/urls.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.urls import include, path
|
||||
from rest_framework import routers
|
||||
from . import views
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'', views.TransactionViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
13
equipment_tracker/transactions/views.py
Normal file
13
equipment_tracker/transactions/views.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework import viewsets, generics
|
||||
from .serializers import TransactionSerializer
|
||||
from .models import Transaction
|
||||
|
||||
|
||||
class TransactionViewSet(viewsets.ModelViewSet):
|
||||
# Only allow GET, POST/CREATE
|
||||
# Transactions cannot be deleted
|
||||
http_method_names = ['get', 'post']
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = TransactionSerializer
|
||||
queryset = Transaction.objects.all()
|
Loading…
Reference in a new issue