mirror of
https://github.com/lemeow125/Borrowing-TrackerBackend.git
synced 2024-11-17 06:19:26 +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):
|
class CustomUserAdmin(UserAdmin):
|
||||||
model = CustomUser
|
model = CustomUser
|
||||||
list_display = UserAdmin.list_display + ('is_technician',)
|
list_display = UserAdmin.list_display + ('is_technician', 'is_teacher')
|
||||||
fieldsets = UserAdmin.fieldsets + (
|
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 accounts.models
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
|
@ -31,6 +31,7 @@ class Migration(migrations.Migration):
|
||||||
('last_name', models.CharField(max_length=100)),
|
('last_name', models.CharField(max_length=100)),
|
||||||
('is_active', models.BooleanField(default=False)),
|
('is_active', models.BooleanField(default=False)),
|
||||||
('is_technician', 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)),
|
('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')),
|
('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')),
|
('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_admin inherited from base user class
|
||||||
is_active = models.BooleanField(default=False)
|
is_active = models.BooleanField(default=False)
|
||||||
is_technician = 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)
|
avatar = models.ImageField(upload_to=_get_upload_to, null=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -66,7 +67,7 @@ def create_superuser(sender, **kwargs):
|
||||||
print('Created admin account')
|
print('Created admin account')
|
||||||
superuser.save()
|
superuser.save()
|
||||||
|
|
||||||
username = 'test-user-technician'
|
username = 'test-technician'
|
||||||
email = os.getenv('DJANGO_ADMIN_EMAIL')
|
email = os.getenv('DJANGO_ADMIN_EMAIL')
|
||||||
password = os.getenv('DJANGO_ADMIN_PASSWORD')
|
password = os.getenv('DJANGO_ADMIN_PASSWORD')
|
||||||
first_name = 'Test'
|
first_name = 'Test'
|
||||||
|
@ -81,3 +82,35 @@ def create_superuser(sender, **kwargs):
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
print('Created debug technician account')
|
print('Created debug technician account')
|
||||||
user.save()
|
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):
|
def has_object_permission(self, request, view, obj):
|
||||||
return request.user.is_authenticated and request.user.is_technician
|
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):
|
class Meta(BaseUserSerializer.Meta):
|
||||||
model = CustomUser
|
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):
|
class UserRegistrationSerializer(serializers.ModelSerializer):
|
||||||
|
@ -30,6 +31,7 @@ class UserRegistrationSerializer(serializers.ModelSerializer):
|
||||||
model = CustomUser # Use your custom user model here
|
model = CustomUser # Use your custom user model here
|
||||||
fields = ('username', 'email', 'password', 'avatar',
|
fields = ('username', 'email', 'password', 'avatar',
|
||||||
'first_name', 'last_name')
|
'first_name', 'last_name')
|
||||||
|
read_only_fields = ('is_teacher', 'is_technician')
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
user = self.Meta.model(**attrs)
|
user = self.Meta.model(**attrs)
|
||||||
|
|
|
@ -3,5 +3,5 @@ from django.urls import path, include
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('accounts/', include('accounts.urls')),
|
path('accounts/', include('accounts.urls')),
|
||||||
path('equipments/', include('equipments.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
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class EquipmentGroupsConfig(AppConfig):
|
class BreakagesConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
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',
|
'drf_spectacular_sidecar',
|
||||||
'accounts',
|
'accounts',
|
||||||
'equipments',
|
'equipments',
|
||||||
'equipment_groups',
|
'transactions',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -137,8 +137,8 @@ REST_FRAMEWORK = {
|
||||||
|
|
||||||
# DRF-Spectacular
|
# DRF-Spectacular
|
||||||
SPECTACULAR_SETTINGS = {
|
SPECTACULAR_SETTINGS = {
|
||||||
'TITLE': 'CITC Equipment Tracker Backend',
|
'TITLE': 'Equipment Tracker Backend',
|
||||||
'DESCRIPTION': 'An IT Elective 4 Project',
|
'DESCRIPTION': 'A Project',
|
||||||
'VERSION': '1.0.0',
|
'VERSION': '1.0.0',
|
||||||
'SERVE_INCLUDE_SCHEMA': False,
|
'SERVE_INCLUDE_SCHEMA': False,
|
||||||
'SWAGGER_UI_DIST': 'SIDECAR',
|
'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.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -21,18 +21,18 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=40)),
|
('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)),
|
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
('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(
|
migrations.CreateModel(
|
||||||
name='HistoricalEquipmentInstance',
|
name='HistoricalEquipmentInstance',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
('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)),
|
('status', models.CharField(choices=[('Working', 'Working'), ('Broken', 'Broken'), ('Borrowed', 'Borrowed')], default='PENDING', max_length=20)),
|
||||||
('remarks', models.TextField(max_length=512)),
|
('remarks', models.TextField(max_length=512, null=True)),
|
||||||
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||||
('last_updated', models.DateTimeField(blank=True, editable=False)),
|
('last_updated', models.DateTimeField(blank=True, editable=False)),
|
||||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
@ -55,10 +55,10 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=40)),
|
('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)),
|
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||||
('last_updated', models.DateTimeField(blank=True, 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_id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
('history_date', models.DateTimeField(db_index=True)),
|
('history_date', models.DateTimeField(db_index=True)),
|
||||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||||
|
@ -77,8 +77,8 @@ class Migration(migrations.Migration):
|
||||||
name='EquipmentInstance',
|
name='EquipmentInstance',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('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)),
|
('status', models.CharField(choices=[('Working', 'Working'), ('Broken', 'Broken'), ('Borrowed', 'Borrowed')], default='PENDING', max_length=20)),
|
||||||
('remarks', models.TextField(max_length=512)),
|
('remarks', models.TextField(max_length=512, null=True)),
|
||||||
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
('date_added', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
('last_updated', models.DateTimeField(auto_now=True)),
|
||||||
('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipments.equipment')),
|
('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 simple_history.models import HistoricalRecords
|
||||||
from django.db.models.signals import post_migrate
|
from django.db.models.signals import post_migrate
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
# Create your models here.
|
|
||||||
|
|
||||||
|
|
||||||
class Equipment(models.Model):
|
class Equipment(models.Model):
|
||||||
|
EQUIPMENT_CATEGORY_CHOICES = (
|
||||||
CATEGORY_CHOICES = (
|
('Glassware', 'Glassware'),
|
||||||
('PC', 'PC'),
|
('Miscellaneous', 'Miscellaneous')
|
||||||
('NETWORKING', 'Networking'),
|
|
||||||
('CCTV', 'CCTV'),
|
|
||||||
('FURNITURE', 'Furniture'),
|
|
||||||
('PERIPHERALS', 'Peripherals'),
|
|
||||||
('MISC', 'Miscellaneous')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(max_length=40)
|
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)
|
description = models.TextField(max_length=512, null=True)
|
||||||
date_added = models.DateTimeField(default=now, editable=False)
|
date_added = models.DateTimeField(default=now, editable=False)
|
||||||
last_updated = models.DateTimeField(auto_now=True, editable=False)
|
last_updated = models.DateTimeField(auto_now=True, editable=False)
|
||||||
category = models.CharField(
|
|
||||||
max_length=20, choices=CATEGORY_CHOICES, default='MISC')
|
|
||||||
history = HistoricalRecords()
|
history = HistoricalRecords()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -30,16 +23,14 @@ class Equipment(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class EquipmentInstance(models.Model):
|
class EquipmentInstance(models.Model):
|
||||||
STATUS_CHOICES = (
|
EQUIPMENT_INSTANCE_STATUS_CHOICES = (
|
||||||
('WORKING', 'Working'),
|
('Working', 'Working'),
|
||||||
('BROKEN', 'Broken'),
|
('Broken', 'Broken'),
|
||||||
('MAINTENANCE', 'Under Maintenance'),
|
('Borrowed', 'Borrowed'),
|
||||||
('DECOMISSIONED', 'Decomissioned'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
equipment = models.ForeignKey(Equipment, on_delete=models.CASCADE)
|
equipment = models.ForeignKey(Equipment, on_delete=models.CASCADE)
|
||||||
status = models.CharField(
|
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)
|
remarks = models.TextField(max_length=512, null=True)
|
||||||
date_added = models.DateTimeField(default=now, editable=False)
|
date_added = models.DateTimeField(default=now, editable=False)
|
||||||
last_updated = models.DateTimeField(auto_now=True, editable=False)
|
last_updated = models.DateTimeField(auto_now=True, editable=False)
|
||||||
|
@ -53,14 +44,14 @@ class EquipmentInstance(models.Model):
|
||||||
def create_superuser(sender, **kwargs):
|
def create_superuser(sender, **kwargs):
|
||||||
if sender.name == 'equipments':
|
if sender.name == 'equipments':
|
||||||
EQUIPMENT, CREATED = Equipment.objects.get_or_create(
|
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_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(
|
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_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(
|
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_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.utils import extend_schema_field
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from accounts.models import CustomUser
|
|
||||||
|
|
||||||
# -- Equipment Serializers
|
# -- Equipment Serializers
|
||||||
|
|
||||||
|
@ -104,7 +103,8 @@ class EquipmentInstanceSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
last_updated = serializers.DateTimeField(
|
last_updated = serializers.DateTimeField(
|
||||||
format="%m-%d-%Y %I:%M%p", read_only=True)
|
format="%m-%d-%Y %I:%M%p", read_only=True)
|
||||||
last_updated_by = serializers.SerializerMethodField()
|
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
|
# Forbid user from changing equipment field once the instance is already created
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: CITC Equipment Tracker Backend
|
title: Equipment Tracker Backend
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
description: An IT Elective 4 Project
|
description: A Project
|
||||||
paths:
|
paths:
|
||||||
/api/v1/accounts/jwt/create/:
|
/api/v1/accounts/jwt/create/:
|
||||||
post:
|
post:
|
||||||
|
@ -520,6 +520,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -547,6 +548,7 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
content:
|
content:
|
||||||
|
@ -567,6 +569,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -590,6 +593,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -622,6 +626,7 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -653,6 +658,7 @@ paths:
|
||||||
$ref: '#/components/schemas/PatchedEquipmentInstance'
|
$ref: '#/components/schemas/PatchedEquipmentInstance'
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -673,6 +679,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: No response body
|
description: No response body
|
||||||
|
@ -683,6 +690,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -699,6 +707,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -715,6 +724,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -742,6 +752,7 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
content:
|
content:
|
||||||
|
@ -762,6 +773,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -785,6 +797,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -817,6 +830,7 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -848,6 +862,7 @@ paths:
|
||||||
$ref: '#/components/schemas/PatchedEquipment'
|
$ref: '#/components/schemas/PatchedEquipment'
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -868,6 +883,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: No response body
|
description: No response body
|
||||||
|
@ -878,6 +894,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -894,6 +911,7 @@ paths:
|
||||||
- api
|
- api
|
||||||
security:
|
security:
|
||||||
- jwtAuth: []
|
- jwtAuth: []
|
||||||
|
- {}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -903,6 +921,147 @@ paths:
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/EquipmentLogs'
|
$ref: '#/components/schemas/EquipmentLogs'
|
||||||
description: ''
|
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:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
Activation:
|
Activation:
|
||||||
|
@ -917,20 +1076,12 @@ components:
|
||||||
- uid
|
- uid
|
||||||
CategoryEnum:
|
CategoryEnum:
|
||||||
enum:
|
enum:
|
||||||
- PC
|
- Glassware
|
||||||
- NETWORKING
|
- Miscellaneous
|
||||||
- CCTV
|
|
||||||
- FURNITURE
|
|
||||||
- PERIPHERALS
|
|
||||||
- MISC
|
|
||||||
type: string
|
type: string
|
||||||
description: |-
|
description: |-
|
||||||
* `PC` - PC
|
* `Glassware` - Glassware
|
||||||
* `NETWORKING` - Networking
|
* `Miscellaneous` - Miscellaneous
|
||||||
* `CCTV` - CCTV
|
|
||||||
* `FURNITURE` - Furniture
|
|
||||||
* `PERIPHERALS` - Peripherals
|
|
||||||
* `MISC` - Miscellaneous
|
|
||||||
CustomUser:
|
CustomUser:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -953,6 +1104,10 @@ components:
|
||||||
last_name:
|
last_name:
|
||||||
type: string
|
type: string
|
||||||
maxLength: 100
|
maxLength: 100
|
||||||
|
is_teacher:
|
||||||
|
type: boolean
|
||||||
|
is_technician:
|
||||||
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- avatar
|
- avatar
|
||||||
- first_name
|
- first_name
|
||||||
|
@ -1215,6 +1370,10 @@ components:
|
||||||
last_name:
|
last_name:
|
||||||
type: string
|
type: string
|
||||||
maxLength: 100
|
maxLength: 100
|
||||||
|
is_teacher:
|
||||||
|
type: boolean
|
||||||
|
is_technician:
|
||||||
|
type: boolean
|
||||||
PatchedEquipment:
|
PatchedEquipment:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1267,6 +1426,29 @@ components:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
readOnly: true
|
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:
|
SendEmailReset:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1302,16 +1484,14 @@ components:
|
||||||
- new_username
|
- new_username
|
||||||
StatusEnum:
|
StatusEnum:
|
||||||
enum:
|
enum:
|
||||||
- WORKING
|
- Working
|
||||||
- BROKEN
|
- Broken
|
||||||
- MAINTENANCE
|
- Borrowed
|
||||||
- DECOMISSIONED
|
|
||||||
type: string
|
type: string
|
||||||
description: |-
|
description: |-
|
||||||
* `WORKING` - Working
|
* `Working` - Working
|
||||||
* `BROKEN` - Broken
|
* `Broken` - Broken
|
||||||
* `MAINTENANCE` - Under Maintenance
|
* `Borrowed` - Borrowed
|
||||||
* `DECOMISSIONED` - Decomissioned
|
|
||||||
TokenObtainPair:
|
TokenObtainPair:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1352,6 +1532,53 @@ components:
|
||||||
writeOnly: true
|
writeOnly: true
|
||||||
required:
|
required:
|
||||||
- token
|
- 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:
|
UserRegistration:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
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