diff --git a/.gitignore b/.gitignore index 238e0ab..445be84 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,4 @@ dmypy.json /CDC_Backend/Storage/ .idea *.pyc +dev.env diff --git a/CDC_Backend/APIs/admin.py b/CDC_Backend/APIs/admin.py index e2a87c3..ab52c34 100644 --- a/CDC_Backend/APIs/admin.py +++ b/CDC_Backend/APIs/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin +from simple_history.admin import SimpleHistoryAdmin from django.contrib.admin.templatetags.admin_urls import admin_urlname from django.shortcuts import resolve_url from django.utils.html import format_html @@ -6,8 +7,8 @@ from django.utils.safestring import SafeText from .models import * -admin.site.register(User) -admin.site.register(Admin) +admin.site.register(User, SimpleHistoryAdmin) +admin.site.register(Admin, SimpleHistoryAdmin) admin.site.site_header = "CDC Recruitment Portal" @@ -18,7 +19,7 @@ def model_admin_url(obj, name=None) -> str: @admin.register(Student) -class Student(admin.ModelAdmin): +class Student(SimpleHistoryAdmin): list_display = ("roll_no", "name", "batch", "branch", "phone_number", 'can_apply') search_fields = ("roll_no", "name", "phone_number") ordering = ("roll_no", "name", "batch", "branch", "phone_number") @@ -37,7 +38,7 @@ class Student(admin.ModelAdmin): @admin.register(Placement) -class Placement(admin.ModelAdmin): +class Placement(SimpleHistoryAdmin): list_display = (COMPANY_NAME, CONTACT_PERSON_NAME, PHONE_NUMBER, 'tier', 'compensation_CTC') search_fields = (COMPANY_NAME, CONTACT_PERSON_NAME) ordering = (COMPANY_NAME, CONTACT_PERSON_NAME, 'tier', 'compensation_CTC') @@ -45,7 +46,7 @@ class Placement(admin.ModelAdmin): @admin.register(PlacementApplication) -class PlacementApplication(admin.ModelAdmin): +class PlacementApplication(SimpleHistoryAdmin): list_display = ('id', 'Placement', 'Student', 'selected') search_fields = ('id',) ordering = ('id',) @@ -59,7 +60,7 @@ class PlacementApplication(admin.ModelAdmin): @admin.register(PrePlacementOffer) -class PrePlacementOffer(admin.ModelAdmin): +class PrePlacementOffer(SimpleHistoryAdmin): list_display = ('company', 'Student', 'accepted') search_fields = ('company',) ordering = ('company',) diff --git a/CDC_Backend/APIs/adminViews.py b/CDC_Backend/APIs/adminViews.py index 66a7f8f..da935d9 100644 --- a/CDC_Backend/APIs/adminViews.py +++ b/CDC_Backend/APIs/adminViews.py @@ -39,6 +39,7 @@ def markStatus(request, id, email, user_type): sendEmail(email, subject, data, STUDENT_APPLICATION_STATUS_SELECTED_TEMPLATE) else: sendEmail(email, subject, data, STUDENT_APPLICATION_STATUS_NOT_SELECTED_TEMPLATE) + application.chaged_by = get_object_or_404(User, id=id) application.save() else: raise ValueError("Student - " + i[STUDENT_ID] + " didn't apply for this opening") @@ -89,6 +90,7 @@ def updateDeadline(request, id, email, user_type): opening = get_object_or_404(Placement, pk=data[OPENING_ID]) # Updating deadline date with correct format in datetime field opening.deadline_datetime = datetime.datetime.strptime(data[DEADLINE_DATETIME], '%Y-%m-%d %H:%M:%S %z') + opening.changed_by = get_object_or_404(User, id=id) opening.save() return Response({'action': "Update Deadline", 'message': "Deadline Updated"}, status=status.HTTP_200_OK) @@ -111,6 +113,7 @@ def updateOfferAccepted(request, id, email, user_type): opening = get_object_or_404(Placement, pk=data[OPENING_ID]) opening.offer_accepted = True if data[OFFER_ACCEPTED] == True else False print(opening.offer_accepted) + opening.changed_by = get_object_or_404(User, id=id) opening.save() return Response({'action': "Update Offer Accepted", 'message': "Offer Accepted Updated"}, status=status.HTTP_200_OK) @@ -131,6 +134,7 @@ def updateEmailVerified(request, id, email, user_type): data = request.data opening = get_object_or_404(Placement, pk=data[OPENING_ID]) opening.email_verified = True if data[EMAIL_VERIFIED] == "true" else False + opening.changed_by = get_object_or_404(User, id=id) opening.save() return Response({'action': "Update Email Verified", 'message': "Email Verified Updated"}, status=status.HTTP_200_OK) @@ -152,6 +156,7 @@ def deleteAdditionalInfo(request, id, email, user_type): opening = get_object_or_404(Placement, pk=data[OPENING_ID]) if data[FIELD] in opening.additional_info: opening.additional_info.remove(data[FIELD]) + opening.changed_by = get_object_or_404(User, id=id) opening.save() return Response({'action': "Delete Additional Info", 'message': "Additional Info Deleted"}, status=status.HTTP_200_OK) @@ -182,6 +187,7 @@ def addAdditionalInfo(request, id, email, user_type): status=status.HTTP_200_OK) else: raise ValueError("Additional Info Found") + except Http404: return Response({'action': "Add Additional Info", 'message': 'Opening Not Found'}, status=status.HTTP_404_NOT_FOUND) @@ -240,6 +246,7 @@ def submitApplication(request, id, email, user_type): else: additional_info[i] = data[ADDITIONAL_INFO][i] application.additional_info = json.dumps(additional_info) + application.changed_by = get_object_or_404(User, id=id) application.save() return Response({'action': "Add Student Application", 'message': "Application added"}, status=status.HTTP_200_OK) @@ -259,6 +266,7 @@ def submitApplication(request, id, email, user_type): additional_info[i] = data[ADDITIONAL_INFO][i] application.additional_info = json.dumps(additional_info) + application.changed_by = get_object_or_404(User, id=id) application.save() return Response({'action': "Add Student Application", 'message': "Application updated"}, status=status.HTTP_200_OK) @@ -349,6 +357,7 @@ def addPPO(request, id, email, user_type): PPO.tier = data[TIER] if COMPENSATION_DETAILS in data: PPO.compensation_details = data[COMPENSATION_DETAILS] + PPO.changed_by = get_object_or_404(User, id=id) PPO.save() return Response({'action': "Add PPO", 'message': "PPO added"}, status=status.HTTP_200_OK) diff --git a/CDC_Backend/APIs/models.py b/CDC_Backend/APIs/models.py index b986089..d013140 100644 --- a/CDC_Backend/APIs/models.py +++ b/CDC_Backend/APIs/models.py @@ -1,6 +1,7 @@ from django.contrib.postgres.fields import ArrayField from django.db import models from django.utils import timezone +from simple_history.models import HistoricalRecords from .constants import * @@ -13,6 +14,7 @@ class User(models.Model): id = models.CharField(blank=False, max_length=25) user_type = ArrayField(models.CharField(blank=False, max_length=10), size=4, default=list, blank=False) last_login_time = models.DateTimeField(default=timezone.now) + history = HistoricalRecords() class Meta: verbose_name_plural = "User" @@ -30,14 +32,41 @@ class Student(models.Model): default=list, blank=True) cpi = models.DecimalField(decimal_places=2, max_digits=4) can_apply = models.BooleanField(default=True, verbose_name='Registered') + changed_by = models.ForeignKey(User, blank=True, on_delete=models.RESTRICT, default=None, null=True) + history = HistoricalRecords(user_model=User) def __str__(self): return str(self.roll_no) + @property + def _history_user(self): + return self.changed_by + + @_history_user.setter + def _history_user(self, value): + if isinstance(value, User): + self.changed_by = value + else: + self.changed_by = None + + class Admin(models.Model): id = models.CharField(blank=False, max_length=15, primary_key=True) name = models.CharField(blank=False, max_length=JNF_TEXT_MAX_CHARACTER_COUNT) + changed_by = models.ForeignKey(User, blank=True, on_delete=models.RESTRICT, default=None, null=True) + history = HistoricalRecords(user_model=User) + + @property + def _history_user(self): + return self.changed_by + + @_history_user.setter + def _history_user(self, value): + if isinstance(value, User): + self.changed_by = value + else: + self.changed_by = None def two_day_after_today(): @@ -114,7 +143,8 @@ class Placement(models.Model): deadline_datetime = models.DateTimeField(blank=False, verbose_name="Deadline Date", default=two_day_after_today) created_at = models.DateTimeField(blank=False, default=None, null=True) updated_at = models.DateTimeField(blank=False, default=None, null=True) - + changed_by = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True) + history = HistoricalRecords(user_model=User) def format(self): if self.company_name is not None: self.company_name = self.company_name.strip()[:JNF_SMALLTEXT_MAX_CHARACTER_COUNT] @@ -158,6 +188,17 @@ class Placement(models.Model): if self.additional_info is not None: self.additional_info = [info.strip()[:JNF_TEXTMEDIUM_MAX_CHARACTER_COUNT] for info in list(self.additional_info)] + @property + def _history_user(self): + return self.changed_by + + @_history_user.setter + def _history_user(self, value): + if isinstance(value, User): + self.changed_by = value + else: + self.changed_by = None + def save(self, *args, **kwargs): ''' On save, add timestamps ''' if not self.created_at: @@ -179,6 +220,8 @@ class PlacementApplication(models.Model): selected = models.BooleanField(null=True, default=None, blank=True) applied_at = models.DateTimeField(blank=False, default=None, null=True) updated_at = models.DateTimeField(blank=False, default=None, null=True) + changed_by = models.ForeignKey(User, blank=False, on_delete=models.RESTRICT, default=None, null=True) + history = HistoricalRecords(user_model=User) def save(self, *args, **kwargs): ''' On save, add timestamps ''' @@ -188,6 +231,17 @@ class PlacementApplication(models.Model): return super(PlacementApplication, self).save(*args, **kwargs) + @property + def _history_user(self): + return self.changed_by + + @_history_user.setter + def _history_user(self, value): + if isinstance(value, User): + self.changed_by = value + else: + self.changed_by = None + class Meta: verbose_name_plural = "Placement Applications" unique_together = ('placement_id', 'student_id') @@ -206,3 +260,16 @@ class PrePlacementOffer(models.Model): tier = models.CharField(blank=False, choices=TIERS, max_length=10) designation = models.CharField(blank=False, max_length=25, default=None, null=True) accepted = models.BooleanField(default=None, null=True) + changed_by = models.ForeignKey(User, blank=False, on_delete=models.RESTRICT, default=None, null=True) + history = HistoricalRecords(user_model=User) + + @property + def _history_user(self): + return self.changed_by + + @_history_user.setter + def _history_user(self, value): + if isinstance(value, User): + self.changed_by = value + else: + self.changed_by = None diff --git a/CDC_Backend/APIs/studentViews.py b/CDC_Backend/APIs/studentViews.py index d3c3fe9..27c7c28 100644 --- a/CDC_Backend/APIs/studentViews.py +++ b/CDC_Backend/APIs/studentViews.py @@ -44,7 +44,7 @@ def addResume(request, id, email, user_type): destination_path = STORAGE_DESTINATION_RESUMES + str(student.roll_no) + "/" file_name = saveFile(file, destination_path) student.resumes.append(file_name) - + student.changed_by = get_object_or_404(User, id=id) student.save() return Response({'action': "Upload Resume", 'message': "Resume Added"}, status=status.HTTP_200_OK) @@ -104,6 +104,7 @@ def deleteResume(request, id, email, user_type): if path.exists(destination_path): # remove(destination_path) student.resumes.remove(file_name) + student.changed_by = get_object_or_404(User, id=id) student.save() return Response({'action': "Delete Resume", 'message': "Resume Deleted"}, status=status.HTTP_200_OK) @@ -177,7 +178,7 @@ def submitApplication(request, id, email, user_type): } subject = STUDENT_APPLICATION_SUBMITTED_TEMPLATE_SUBJECT.format(company_name=opening.company_name) sendEmail(email, subject, data, STUDENT_APPLICATION_SUBMITTED_TEMPLATE) - + application.changed_by = get_object_or_404(User, id=id) application.save() return Response({'action': "Submit Application", 'message': "Application Submitted"}, status=status.HTTP_200_OK) diff --git a/CDC_Backend/CDC_Backend/settings.py b/CDC_Backend/CDC_Backend/settings.py index 4c24f27..22552bf 100644 --- a/CDC_Backend/CDC_Backend/settings.py +++ b/CDC_Backend/CDC_Backend/settings.py @@ -29,7 +29,7 @@ SECRET_KEY = 'e_i2g3z!y4+p3dwm%k9k=zmsot@aya-0$mmetgxz4mp#8_oy#*' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ['cdc-iitdh.herokuapp.com/', 'localhost', '192.168.29.199'] +ALLOWED_HOSTS = ['cdc-iitdh.herokuapp.com/', 'localhost', '192.168.29.199', '127.0.0.1'] # Application definition @@ -44,7 +44,8 @@ INSTALLED_APPS = [ 'rest_framework', 'corsheaders', 'django_db_logger', - 'background_task' + 'background_task', + 'simple_history', ] MIDDLEWARE = [ @@ -57,7 +58,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - + 'simple_history.middleware.HistoryRequestMiddleware', ] ROOT_URLCONF = 'CDC_Backend.urls' diff --git a/requirements.txt b/requirements.txt index 6ca6f8a..5fbc28b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,12 +5,14 @@ certifi==2021.10.8 chardet==4.0.0 charset-normalizer==2.0.12 colorama==0.4.4 +dill==0.3.5.1 dj-database-url==0.5.0 Django==3.2.13 django-background-tasks==1.2.5 django-compat==1.0.15 django-cors-headers==3.11.0 django-db-logger==0.1.12 +django-simple-history==3.1.1 djangorestframework==3.13.1 google-auth==2.6.6 gunicorn==20.1.0 @@ -26,17 +28,18 @@ platformdirs==2.5.1 psycopg2-binary==2.9.3 pyasn1==0.4.8 pyasn1-modules==0.2.8 +PyJWT==2.4.0 pylint==2.13.5 python-dotenv==0.20.0 pytz==2022.1 -PyJWT==2.4.0 requests==2.27.1 rsa==4.8 six==1.16.0 sqlparse==0.4.2 toml==0.10.2 -typing_extensions==4.1.1 +tomli==2.0.1 +typing-extensions==4.1.1 urllib3==1.26.9 whitenoise==6.0.0 +wrapt==1.14.0 zipp==3.8.0 -wrapt==1.14.0 \ No newline at end of file