diff --git a/equipment_tracker/accounts/admin.py b/equipment_tracker/accounts/admin.py index eda007b..33db357 100644 --- a/equipment_tracker/accounts/admin.py +++ b/equipment_tracker/accounts/admin.py @@ -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')}), ) diff --git a/equipment_tracker/accounts/migrations/0001_initial.py b/equipment_tracker/accounts/migrations/0001_initial.py index 6479e54..c9975de 100644 --- a/equipment_tracker/accounts/migrations/0001_initial.py +++ b/equipment_tracker/accounts/migrations/0001_initial.py @@ -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')), diff --git a/equipment_tracker/accounts/models.py b/equipment_tracker/accounts/models.py index 12816f8..c9dd977 100644 --- a/equipment_tracker/accounts/models.py +++ b/equipment_tracker/accounts/models.py @@ -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() diff --git a/equipment_tracker/accounts/permissions.py b/equipment_tracker/accounts/permissions.py index 8486605..0d4ed90 100644 --- a/equipment_tracker/accounts/permissions.py +++ b/equipment_tracker/accounts/permissions.py @@ -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 diff --git a/equipment_tracker/accounts/serializers.py b/equipment_tracker/accounts/serializers.py index d878cce..4c81def 100644 --- a/equipment_tracker/accounts/serializers.py +++ b/equipment_tracker/accounts/serializers.py @@ -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) diff --git a/equipment_tracker/api/urls.py b/equipment_tracker/api/urls.py index c8177c4..c5b9e1f 100644 --- a/equipment_tracker/api/urls.py +++ b/equipment_tracker/api/urls.py @@ -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')) ] diff --git a/equipment_tracker/equipment_groups/__init__.py b/equipment_tracker/breakages/__init__.py similarity index 100% rename from equipment_tracker/equipment_groups/__init__.py rename to equipment_tracker/breakages/__init__.py diff --git a/equipment_tracker/breakages/admin.py b/equipment_tracker/breakages/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/equipment_tracker/breakages/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/equipment_tracker/equipment_groups/apps.py b/equipment_tracker/breakages/apps.py similarity index 57% rename from equipment_tracker/equipment_groups/apps.py rename to equipment_tracker/breakages/apps.py index 3992bc6..c0d817b 100644 --- a/equipment_tracker/equipment_groups/apps.py +++ b/equipment_tracker/breakages/apps.py @@ -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' diff --git a/equipment_tracker/equipment_groups/migrations/__init__.py b/equipment_tracker/breakages/migrations/__init__.py similarity index 100% rename from equipment_tracker/equipment_groups/migrations/__init__.py rename to equipment_tracker/breakages/migrations/__init__.py diff --git a/equipment_tracker/breakages/models.py b/equipment_tracker/breakages/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/equipment_tracker/breakages/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/equipment_tracker/equipment_groups/tests.py b/equipment_tracker/breakages/tests.py similarity index 100% rename from equipment_tracker/equipment_groups/tests.py rename to equipment_tracker/breakages/tests.py diff --git a/equipment_tracker/breakages/views.py b/equipment_tracker/breakages/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/equipment_tracker/breakages/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/equipment_tracker/config/settings.py b/equipment_tracker/config/settings.py index ee53d4f..72867d5 100644 --- a/equipment_tracker/config/settings.py +++ b/equipment_tracker/config/settings.py @@ -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', diff --git a/equipment_tracker/equipment_groups/admin.py b/equipment_tracker/equipment_groups/admin.py deleted file mode 100644 index 28ed500..0000000 --- a/equipment_tracker/equipment_groups/admin.py +++ /dev/null @@ -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') diff --git a/equipment_tracker/equipment_groups/migrations/0001_initial.py b/equipment_tracker/equipment_groups/migrations/0001_initial.py deleted file mode 100644 index b4c6f84..0000000 --- a/equipment_tracker/equipment_groups/migrations/0001_initial.py +++ /dev/null @@ -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')), - ], - ), - ] diff --git a/equipment_tracker/equipment_groups/migrations/0002_delete_historicalequipmentgroup.py b/equipment_tracker/equipment_groups/migrations/0002_delete_historicalequipmentgroup.py deleted file mode 100644 index 9ca2035..0000000 --- a/equipment_tracker/equipment_groups/migrations/0002_delete_historicalequipmentgroup.py +++ /dev/null @@ -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', - ), - ] diff --git a/equipment_tracker/equipment_groups/migrations/__pycache__/0001_initial.cpython-311.pyc b/equipment_tracker/equipment_groups/migrations/__pycache__/0001_initial.cpython-311.pyc deleted file mode 100644 index 0c119f9..0000000 Binary files a/equipment_tracker/equipment_groups/migrations/__pycache__/0001_initial.cpython-311.pyc and /dev/null differ diff --git a/equipment_tracker/equipment_groups/migrations/__pycache__/__init__.cpython-311.pyc b/equipment_tracker/equipment_groups/migrations/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 711896f..0000000 Binary files a/equipment_tracker/equipment_groups/migrations/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/equipment_tracker/equipment_groups/models.py b/equipment_tracker/equipment_groups/models.py deleted file mode 100644 index 5c99fb3..0000000 --- a/equipment_tracker/equipment_groups/models.py +++ /dev/null @@ -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]) diff --git a/equipment_tracker/equipment_groups/serializers.py b/equipment_tracker/equipment_groups/serializers.py deleted file mode 100644 index 671b43c..0000000 --- a/equipment_tracker/equipment_groups/serializers.py +++ /dev/null @@ -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 diff --git a/equipment_tracker/equipment_groups/urls.py b/equipment_tracker/equipment_groups/urls.py deleted file mode 100644 index d99a0c3..0000000 --- a/equipment_tracker/equipment_groups/urls.py +++ /dev/null @@ -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()), -] diff --git a/equipment_tracker/equipment_groups/views.py b/equipment_tracker/equipment_groups/views.py deleted file mode 100644 index af61f71..0000000 --- a/equipment_tracker/equipment_groups/views.py +++ /dev/null @@ -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] diff --git a/equipment_tracker/equipments/choices.py b/equipment_tracker/equipments/choices.py new file mode 100644 index 0000000..cbad9b3 --- /dev/null +++ b/equipment_tracker/equipments/choices.py @@ -0,0 +1,11 @@ +EQUIPMENT_CATEGORY_CHOICES = ( + ('Glassware', 'Glassware'), + ('Miscellaneous', 'Miscellaneous') +) + + +EQUIPMENT_INSTANCE_STATUS_CHOICES = ( + ('Working', 'Working'), + ('Broken', 'Broken'), + ('Borrowed', 'Borrowed'), +) diff --git a/equipment_tracker/equipments/migrations/0001_initial.py b/equipment_tracker/equipments/migrations/0001_initial.py index 9288eb1..33f3ba8 100644 --- a/equipment_tracker/equipments/migrations/0001_initial.py +++ b/equipment_tracker/equipments/migrations/0001_initial.py @@ -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')), diff --git a/equipment_tracker/equipments/migrations/0002_alter_equipment_description_and_more.py b/equipment_tracker/equipments/migrations/0002_alter_equipment_description_and_more.py deleted file mode 100644 index dfb2bc7..0000000 --- a/equipment_tracker/equipments/migrations/0002_alter_equipment_description_and_more.py +++ /dev/null @@ -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), - ), - ] diff --git a/equipment_tracker/equipments/models.py b/equipment_tracker/equipments/models.py index b7476cf..a1ab5c5 100644 --- a/equipment_tracker/equipments/models.py +++ b/equipment_tracker/equipments/models.py @@ -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!") diff --git a/equipment_tracker/equipments/serializers.py b/equipment_tracker/equipments/serializers.py index 00e9a13..774d140 100644 --- a/equipment_tracker/equipments/serializers.py +++ b/equipment_tracker/equipments/serializers.py @@ -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): diff --git a/equipment_tracker/schema.yml b/equipment_tracker/schema.yml index 1282edd..4d3387a 100644 --- a/equipment_tracker/schema.yml +++ b/equipment_tracker/schema.yml @@ -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: diff --git a/equipment_tracker/transactions/__init__.py b/equipment_tracker/transactions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/equipment_tracker/transactions/admin.py b/equipment_tracker/transactions/admin.py new file mode 100644 index 0000000..cda940a --- /dev/null +++ b/equipment_tracker/transactions/admin.py @@ -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) diff --git a/equipment_tracker/transactions/apps.py b/equipment_tracker/transactions/apps.py new file mode 100644 index 0000000..f806531 --- /dev/null +++ b/equipment_tracker/transactions/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TransactionsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'transactions' diff --git a/equipment_tracker/transactions/migrations/0001_initial.py b/equipment_tracker/transactions/migrations/0001_initial.py new file mode 100644 index 0000000..eccc11a --- /dev/null +++ b/equipment_tracker/transactions/migrations/0001_initial.py @@ -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)), + ], + ), + ] diff --git a/equipment_tracker/transactions/migrations/__init__.py b/equipment_tracker/transactions/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/equipment_tracker/transactions/models.py b/equipment_tracker/transactions/models.py new file mode 100644 index 0000000..4125bf1 --- /dev/null +++ b/equipment_tracker/transactions/models.py @@ -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}" diff --git a/equipment_tracker/transactions/serializers.py b/equipment_tracker/transactions/serializers.py new file mode 100644 index 0000000..e3d13c4 --- /dev/null +++ b/equipment_tracker/transactions/serializers.py @@ -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) diff --git a/equipment_tracker/transactions/tests.py b/equipment_tracker/transactions/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/equipment_tracker/transactions/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/equipment_tracker/transactions/urls.py b/equipment_tracker/transactions/urls.py new file mode 100644 index 0000000..101e758 --- /dev/null +++ b/equipment_tracker/transactions/urls.py @@ -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)), +] diff --git a/equipment_tracker/transactions/views.py b/equipment_tracker/transactions/views.py new file mode 100644 index 0000000..a0d4066 --- /dev/null +++ b/equipment_tracker/transactions/views.py @@ -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()