Merge pull request #11 from lemeow125/feature/equipment_groups

Feature/equipment groups
This commit is contained in:
Keannu Bernasol 2023-11-19 18:59:24 +08:00 committed by GitHub
commit 3dcd845f72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1557 additions and 30 deletions

View file

@ -6,6 +6,7 @@ from .models import CustomUser
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):
model = CustomUser model = CustomUser
list_display = UserAdmin.list_display + ('is_technician',)
admin.site.register(CustomUser, CustomUserAdmin) admin.site.register(CustomUser, CustomUserAdmin)

View file

@ -0,0 +1,18 @@
# Generated by Django 4.2.7 on 2023-11-13 10:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='customuser',
name='is_technician',
field=models.BooleanField(default=False),
),
]

View file

@ -37,6 +37,7 @@ class CustomUser(AbstractUser):
# Password inherited from base user class # Password inherited from base user class
# 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)
avatar = models.ImageField(upload_to=_get_upload_to, null=True) avatar = models.ImageField(upload_to=_get_upload_to, null=True)
@property @property
@ -65,11 +66,11 @@ def create_superuser(sender, **kwargs):
print('Created admin account') print('Created admin account')
superuser.save() superuser.save()
username = 'usertest1' username = 'test-user-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'
last_name = 'User' last_name = 'Technician'
if not User.objects.filter(username=username).exists(): if not User.objects.filter(username=username).exists():
# Create the superuser with is_active set to False # Create the superuser with is_active set to False
@ -78,5 +79,5 @@ def create_superuser(sender, **kwargs):
# Activate the user # Activate the user
user.is_active = True user.is_active = True
print('Created debug user account') print('Created debug technician account')
user.save() user.save()

View file

@ -0,0 +1,11 @@
from rest_framework.permissions import BasePermission
class IsTechnician(BasePermission):
message = "You must be a technician to perform this action."
def has_permission(self, request, view):
return request.user.is_authenticated and request.user.is_technician
def has_object_permission(self, request, view, obj):
return request.user.is_authenticated and request.user.is_technician

View file

@ -29,7 +29,6 @@ SECRET_KEY = str(os.getenv('SECRET_KEY'))
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
FRONTEND_DEBUG = True
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ['*']
# CSRF_TRUSTED_ORIGINS = [] To-do: Specify URL to web frontend # CSRF_TRUSTED_ORIGINS = [] To-do: Specify URL to web frontend
@ -71,6 +70,7 @@ INSTALLED_APPS = [
'drf_spectacular_sidecar', 'drf_spectacular_sidecar',
'accounts', 'accounts',
'equipments', 'equipments',
'equipment_groups',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -162,9 +162,6 @@ AUTH_USER_MODEL = 'accounts.CustomUser'
DJOSER = { DJOSER = {
'SEND_ACTIVATION_EMAIL': True, 'SEND_ACTIVATION_EMAIL': True,
'SEND_CONFIRMATION_EMAIL': True, 'SEND_CONFIRMATION_EMAIL': True,
'EMAIL': {
'activation': 'config.email.ActivationEmail'
},
'ACTIVATION_URL': 'activation/{uid}/{token}', 'ACTIVATION_URL': 'activation/{uid}/{token}',
'USER_AUTHENTICATION_RULES': ['djoser.authentication.TokenAuthenticationRule'], 'USER_AUTHENTICATION_RULES': ['djoser.authentication.TokenAuthenticationRule'],
'SERIALIZERS': { 'SERIALIZERS': {
@ -210,11 +207,7 @@ USE_TZ = True
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
DOMAIN = '' DOMAIN = 'CITC-Tracker.keannu1.duckdns.org'
if (FRONTEND_DEBUG):
DOMAIN = 'exp'
else:
DOMAIN = 'citctracker'
SITE_NAME = 'CITC Equipment Tracker' SITE_NAME = 'CITC Equipment Tracker'

View file

@ -0,0 +1,9 @@
from django.contrib import admin
from .models import EquipmentGroup
from simple_history.admin import SimpleHistoryAdmin
@admin.register(EquipmentGroup)
class EquipmentGroupAdmin(SimpleHistoryAdmin):
readonly_fields = ['status']
list_display = ('name', 'status', 'date_added', 'last_updated')

View file

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

View file

@ -0,0 +1,53 @@
# Generated by Django 4.2.7 on 2023-11-13 09:43
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', '0003_historicalequipment'),
]
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')),
],
),
]

View file

@ -0,0 +1,40 @@
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)
history = HistoricalRecords()
@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])

View file

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

View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

@ -2,5 +2,14 @@ from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin from simple_history.admin import SimpleHistoryAdmin
from .models import Equipment, EquipmentInstance from .models import Equipment, EquipmentInstance
admin.site.register(Equipment, SimpleHistoryAdmin)
admin.site.register(EquipmentInstance, SimpleHistoryAdmin) @admin.register(Equipment)
class EquipmentAdmin(SimpleHistoryAdmin):
readonly_fields = ('date_added', 'last_updated')
list_display = ('name', 'date_added', 'last_updated')
@admin.register(EquipmentInstance)
class EquipmentInstanceAdmin(SimpleHistoryAdmin):
list_display = ('equipment', 'status', 'remarks',
'date_added', 'last_updated')

View file

@ -42,9 +42,13 @@ 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") name="HP All-in-One PC", description="I5 6500 8GB RAM 1TB HDD")
if (CREATED):
print("Created sample equipment")
EQUIPMENT_INSTANCE, CREATED = EquipmentInstance.objects.get_or_create( EQUIPMENT_INSTANCE, CREATED = EquipmentInstance.objects.get_or_create(
equipment=EQUIPMENT, status="Working", remarks="First PC of USTP!") equipment=EQUIPMENT, status="Working", remarks="First PC of citc equipment tracker!")
if (CREATED): EQUIPMENT, CREATED = Equipment.objects.get_or_create(
print("Created sample equipment instance") name="HP Keyboard", description="Generic Membrane Keyboard")
EQUIPMENT_INSTANCE, CREATED = EquipmentInstance.objects.get_or_create(
equipment=EQUIPMENT, status="Working", remarks="First keyboard of citc equipment tracker!")
EQUIPMENT, CREATED = Equipment.objects.get_or_create(
name="HP Mouse", description="Generic Mouse")
EQUIPMENT_INSTANCE, CREATED = EquipmentInstance.objects.get_or_create(
equipment=EQUIPMENT, status="Working", remarks="First mouse of citc equipment tracker!")

View file

@ -1,6 +1,8 @@
from rest_framework import serializers from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Equipment, EquipmentInstance from .models import Equipment, EquipmentInstance
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes
from accounts.models import CustomUser
# -- Equipment Serializers # -- Equipment Serializers
@ -31,6 +33,7 @@ class EquipmentSerializer(serializers.HyperlinkedModelSerializer):
def get_history_user(self, obj): def get_history_user(self, obj):
return obj.history_user.username if obj.history_user else None return obj.history_user.username if obj.history_user else None
@extend_schema_field(OpenApiTypes.STR)
def get_last_updated_by(self, obj): def get_last_updated_by(self, obj):
return obj.history.first().history_user if obj.history.first().history_user else None return obj.history.first().history_user if obj.history.first().history_user else None
@ -47,6 +50,7 @@ class EquipmentLogsSerializer(serializers.HyperlinkedModelSerializer):
read_only_fields = ('history_id', 'id', 'name', 'description', read_only_fields = ('history_id', 'id', 'name', 'description',
'history_date', 'history_user') 'history_date', 'history_user')
@extend_schema_field(OpenApiTypes.STR)
def get_history_user(self, obj): def get_history_user(self, obj):
return obj.history_user.username if obj.history_user else None return obj.history_user.username if obj.history_user else None
@ -68,6 +72,7 @@ class EquipmentLogSerializer(serializers.HyperlinkedModelSerializer):
read_only_fields = ('id', 'last_updated', read_only_fields = ('id', 'last_updated',
'date_added', 'last_updated_by', 'history') 'date_added', 'last_updated_by', 'history')
@extend_schema_field(OpenApiTypes.STR)
def get_last_updated_by(self, obj): def get_last_updated_by(self, obj):
return obj.history.first().history_user if obj.history.first().history_user else None return obj.history.first().history_user if obj.history.first().history_user else None
@ -106,9 +111,11 @@ class EquipmentInstanceSerializer(serializers.HyperlinkedModelSerializer):
read_only_fields = ('id', 'last_updated', read_only_fields = ('id', 'last_updated',
'last_updated_by', 'date_added') 'last_updated_by', 'date_added')
@extend_schema_field(OpenApiTypes.STR)
def get_history_user(self, obj): def get_history_user(self, obj):
return obj.history_user.username if obj.history_user else None return obj.history_user.username if obj.history_user else None
@extend_schema_field(OpenApiTypes.STR)
def get_last_updated_by(self, obj): def get_last_updated_by(self, obj):
return obj.history.first().history_user if obj.history.first().history_user else None return obj.history.first().history_user if obj.history.first().history_user else None
@ -127,6 +134,7 @@ class EquipmentInstanceLogsSerializer(serializers.HyperlinkedModelSerializer):
read_only_fields = ('history_id', 'id', 'equipment', 'status', 'remarks', read_only_fields = ('history_id', 'id', 'equipment', 'status', 'remarks',
'history_date', 'history_user') 'history_date', 'history_user')
@extend_schema_field(OpenApiTypes.STR)
def get_history_user(self, obj): def get_history_user(self, obj):
return obj.history_user.username if obj.history_user else None return obj.history_user.username if obj.history_user else None
@ -156,5 +164,6 @@ class EquipmentInstanceLogSerializer(serializers.HyperlinkedModelSerializer):
read_only_fields = ('id', 'last_updated', read_only_fields = ('id', 'last_updated',
'date_added', 'last_updated_by', 'history') 'date_added', 'last_updated_by', 'history')
@extend_schema_field(OpenApiTypes.STR)
def get_last_updated_by(self, obj): def get_last_updated_by(self, obj):
return obj.history.first().history_user if obj.history.first().history_user else None return obj.history.first().history_user if obj.history.first().history_user else None

View file

@ -3,13 +3,14 @@ from rest_framework import viewsets, generics
from .models import Equipment, EquipmentInstance from .models import Equipment, EquipmentInstance
from . import serializers from . import serializers
from config.settings import DEBUG from config.settings import DEBUG
from accounts.permissions import IsTechnician
# -- Equipment Viewsets # -- Equipment Viewsets
class EquipmentViewSet(viewsets.ModelViewSet): class EquipmentViewSet(viewsets.ModelViewSet):
if (not DEBUG): if (not DEBUG):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated, IsTechnician]
serializer_class = serializers.EquipmentSerializer serializer_class = serializers.EquipmentSerializer
queryset = Equipment.objects.all().order_by('-date_added') queryset = Equipment.objects.all().order_by('-date_added')
@ -18,7 +19,7 @@ class EquipmentViewSet(viewsets.ModelViewSet):
class EquipmentsLogsViewSet(generics.ListAPIView): class EquipmentsLogsViewSet(generics.ListAPIView):
if (not DEBUG): if (not DEBUG):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated, IsTechnician]
serializer_class = serializers.EquipmentLogsSerializer serializer_class = serializers.EquipmentLogsSerializer
queryset = Equipment.history.all().order_by('-history_date') queryset = Equipment.history.all().order_by('-history_date')
@ -27,7 +28,7 @@ class EquipmentsLogsViewSet(generics.ListAPIView):
class EquipmentLogViewSet(viewsets.ReadOnlyModelViewSet): class EquipmentLogViewSet(viewsets.ReadOnlyModelViewSet):
if (not DEBUG): if (not DEBUG):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated, IsTechnician]
serializer_class = serializers.EquipmentLogSerializer serializer_class = serializers.EquipmentLogSerializer
def get_queryset(self): def get_queryset(self):
@ -39,7 +40,7 @@ class EquipmentLogViewSet(viewsets.ReadOnlyModelViewSet):
class LastUpdatedEquipmentViewSet(generics.ListAPIView): class LastUpdatedEquipmentViewSet(generics.ListAPIView):
if (not DEBUG): if (not DEBUG):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated, IsTechnician]
serializer_class = serializers.EquipmentSerializer serializer_class = serializers.EquipmentSerializer
queryset = Equipment.objects.all().order_by('-date_added') queryset = Equipment.objects.all().order_by('-date_added')
@ -51,7 +52,7 @@ class LastUpdatedEquipmentViewSet(generics.ListAPIView):
class EquipmentInstanceViewSet(viewsets.ModelViewSet): class EquipmentInstanceViewSet(viewsets.ModelViewSet):
if (not DEBUG): if (not DEBUG):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated, IsTechnician]
serializer_class = serializers.EquipmentInstanceSerializer serializer_class = serializers.EquipmentInstanceSerializer
queryset = EquipmentInstance.objects.all().order_by('-date_added') queryset = EquipmentInstance.objects.all().order_by('-date_added')
@ -60,7 +61,7 @@ class EquipmentInstanceViewSet(viewsets.ModelViewSet):
class EquipmentInstancesLogsViewSet(generics.ListAPIView): class EquipmentInstancesLogsViewSet(generics.ListAPIView):
if (not DEBUG): if (not DEBUG):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated, IsTechnician]
serializer_class = serializers.EquipmentInstanceLogsSerializer serializer_class = serializers.EquipmentInstanceLogsSerializer
queryset = EquipmentInstance.history.all().order_by('-history_date') queryset = EquipmentInstance.history.all().order_by('-history_date')
@ -69,7 +70,7 @@ class EquipmentInstancesLogsViewSet(generics.ListAPIView):
class EquipmentInstanceLogViewSet(viewsets.ReadOnlyModelViewSet): class EquipmentInstanceLogViewSet(viewsets.ReadOnlyModelViewSet):
if (not DEBUG): if (not DEBUG):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated, IsTechnician]
serializer_class = serializers.EquipmentInstanceLogSerializer serializer_class = serializers.EquipmentInstanceLogSerializer
def get_queryset(self): def get_queryset(self):
@ -81,7 +82,7 @@ class EquipmentInstanceLogViewSet(viewsets.ReadOnlyModelViewSet):
class LastUpdatedEquipmentInstanceViewSet(generics.ListAPIView): class LastUpdatedEquipmentInstanceViewSet(generics.ListAPIView):
if (not DEBUG): if (not DEBUG):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated, IsTechnician]
serializer_class = serializers.EquipmentInstanceSerializer serializer_class = serializers.EquipmentInstanceSerializer
queryset = EquipmentInstance.objects.all().order_by('-date_added') queryset = EquipmentInstance.objects.all().order_by('-date_added')

View file

@ -997,7 +997,7 @@ components:
equipment: equipment:
type: integer type: integer
status: status:
type: string $ref: '#/components/schemas/StatusEnum'
remarks: remarks:
type: string type: string
last_updated: last_updated:
@ -1062,6 +1062,11 @@ components:
history_id: history_id:
type: integer type: integer
readOnly: true readOnly: true
id:
type: integer
readOnly: true
equipment:
type: integer
status: status:
allOf: allOf:
- $ref: '#/components/schemas/StatusEnum' - $ref: '#/components/schemas/StatusEnum'
@ -1077,9 +1082,11 @@ components:
type: string type: string
readOnly: true readOnly: true
required: required:
- equipment
- history_date - history_date
- history_id - history_id
- history_user - history_user
- id
- remarks - remarks
- status - status
EquipmentLog: EquipmentLog:
@ -1206,7 +1213,7 @@ components:
equipment: equipment:
type: integer type: integer
status: status:
type: string $ref: '#/components/schemas/StatusEnum'
remarks: remarks:
type: string type: string
last_updated: last_updated:

File diff suppressed because it is too large Load diff