Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ recosenv/

*.pyc
__pycache__/
db.sqlite3
db.sqlite3

credentials.json
saved_login.pickle
.env
5 changes: 3 additions & 2 deletions api/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@


class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'
default_auto_field = "django.db.models.BigAutoField"
name = "api"

20 changes: 20 additions & 0 deletions api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
from rest_framework import serializers
from interview.models import Interview
from interviewConversation.models import InterviewConversation
from job.models import Job
from candidate.models import Candidate

class InterviewSerializer(serializers.ModelSerializer):
interview_link = serializers.CharField(read_only=True)
google_event_id = serializers.CharField(read_only=True)
created_at = serializers.DateTimeField(read_only=True)
updated_at = serializers.DateTimeField(read_only=True)

class Meta:
model = Interview
fields = [
"id",
"scheduled_at",
"status",
"interview_link",
"google_event_id",
"created_at",
"updated_at",
]



class InterviewConversationSerializer(serializers.ModelSerializer):
class Meta:
Expand Down
6 changes: 4 additions & 2 deletions api/urls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@

from rest_framework.routers import DefaultRouter
from .views import InterviewConversationViewSet, JobViewSet, CandidateViewSet
from .views import InterviewConversationViewSet, JobViewSet, CandidateViewSet, InterviewViewSet
from django.urls import path,include

router = DefaultRouter()
router.register(r'interview_conversations', InterviewConversationViewSet)
router.register(r'jobs', JobViewSet)
router.register(r'candidates', CandidateViewSet)
router.register(r'interview', InterviewViewSet)


urlpatterns = [
path('', include(router.urls))
path('', include(router.urls)),
]
31 changes: 27 additions & 4 deletions api/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
from django.shortcuts import render

from rest_framework import viewsets
from rest_framework import viewsets, status
from rest_framework.response import Response
from interviewConversation.models import InterviewConversation
from job.models import Job
from candidate.models import Candidate
from .serializers import InterviewConversationSerializer, JobSerializer, CandidateSerializer
from interview.models import Interview
from .serializers import (
InterviewConversationSerializer,
JobSerializer,
CandidateSerializer,
InterviewSerializer
)
from interview.utils import create_google_calendar_event

class InterviewConversationViewSet(viewsets.ModelViewSet):
queryset = InterviewConversation.objects.all()
Expand All @@ -17,3 +23,20 @@ class JobViewSet(viewsets.ModelViewSet):
class CandidateViewSet(viewsets.ModelViewSet):
queryset = Candidate.objects.all()
serializer_class = CandidateSerializer

class InterviewViewSet(viewsets.ModelViewSet):
queryset = Interview.objects.all()
serializer_class = InterviewSerializer

def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
interview = serializer.save()


event_id, hangout_link = create_google_calendar_event(interview)
interview.google_event_id = event_id
interview.interview_link = hangout_link
interview.save()
output_serializer = self.get_serializer(interview)
return Response(output_serializer.data, status=status.HTTP_201_CREATED)
Empty file added interview/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions interview/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.contrib import admin

from .models import Interview

admin.site.register(Interview)
6 changes: 6 additions & 0 deletions interview/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class InterviewConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "interview"
46 changes: 46 additions & 0 deletions interview/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Generated by Django 5.2.6 on 2025-09-10 11:17

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="Interview",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("scheduled_at", models.DateTimeField()),
("interview_link", models.URLField(blank=True, null=True)),
(
"status",
models.CharField(
choices=[
("Scheduled", "Scheduled"),
("Completed", "Completed"),
("Canceled", "Canceled"),
],
max_length=20,
),
),
(
"google_event_id",
models.CharField(blank=True, max_length=255, null=True),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
],
),
]
Empty file.
17 changes: 17 additions & 0 deletions interview/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.db import models

class Interview(models.Model):
STATUS_CHOICES = [
('Scheduled', 'Scheduled'),
('Completed', 'Completed'),
('Canceled', 'Canceled'),
]
scheduled_at = models.DateTimeField()
interview_link = models.URLField(blank=True, null=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
google_event_id = models.CharField(max_length=255, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return f"Interview: {self.pk} ({self.status})"
19 changes: 19 additions & 0 deletions interview/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.test import TestCase
from django.utils import timezone
from .models import Interview

class InterviewModelTest(TestCase):
def test_create_interview(self):
scheduled_time = timezone.now()
interview = Interview.objects.create(
scheduled_at=scheduled_time,
status='Scheduled',
)
self.assertIsInstance(interview, Interview)
self.assertEqual(interview.status, 'Scheduled')
self.assertEqual(interview.scheduled_at, scheduled_time)
self.assertIsNone(interview.interview_link)
self.assertIsNone(interview.google_event_id)
self.assertIsNotNone(interview.created_at)
self.assertIsNotNone(interview.updated_at)
self.assertEqual(str(interview), f"Interview: {interview.pk} ({interview.status})")
63 changes: 63 additions & 0 deletions interview/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from google.auth.transport.requests import Request
from datetime import timedelta
import pickle
import os

SCOPES = ['https://www.googleapis.com/auth/calendar']
CREDENTIALS_FILE = 'credentials.json'

def get_simple_login():
login_info = None
if os.path.exists('saved_login.pickle'):
with open('saved_login.pickle', 'rb') as saved_file:
login_info = pickle.load(saved_file)
if not login_info or not login_info.valid:
if login_info and login_info.expired and login_info.refresh_token:
login_info.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
login_info = flow.run_local_server(port=0)
with open('saved_login.pickle', 'wb') as saved_file:
pickle.dump(login_info, saved_file)
return login_info

def create_google_calendar_event(interview):
credentials = get_simple_login()
service = build('calendar', 'v3', credentials=credentials)
calendar_id = 'primary'

start_time = interview.scheduled_at.isoformat()
end_time = (interview.scheduled_at + timedelta(hours=1)).isoformat()

event = {
'summary': f'Interview {interview.pk} ({interview.status})',
'description': 'Interview scheduled via platform',
'start': {'dateTime': start_time, 'timeZone': 'UTC'},
'end': {'dateTime': end_time, 'timeZone': 'UTC'},
'conferenceData': {
'createRequest': {
'requestId': f'{interview.pk}-meet',
'conferenceSolutionKey': {'type': 'hangoutsMeet'}
}
},
'reminders': {
'useDefault': False,
'overrides': [
{'method': 'email', 'minutes': 24 * 60},
{'method': 'popup', 'minutes': 10},
],
},
}

created_event = service.events().insert(
calendarId=calendar_id,
body=event,
conferenceDataVersion=1,
sendUpdates='all'
).execute()

event_id = created_event.get('id')
hangout_link = created_event.get('hangoutLink')
return event_id, hangout_link
1 change: 1 addition & 0 deletions interview/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from rest_framework import viewsets
2 changes: 1 addition & 1 deletion manage.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python

"""Django's command-line utility for administrative tasks."""
import os
import sys
Expand Down
1 change: 1 addition & 0 deletions recos/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"job",
"candidate",
"api",
"interview",
]

MIDDLEWARE = [
Expand Down
21 changes: 2 additions & 19 deletions recos/urls.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,7 @@
"""
URL configuration for recos project.

The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
]
path('api/', include('api.urls')),
]
16 changes: 9 additions & 7 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
asgiref==3.9.1
black==25.1.0
cachetools==5.5.2
certifi==2025.8.3
charset-normalizer==3.4.3
click==8.2.1
django==5.2.6
mypy-extensions==1.1.0
packaging==25.0
pathspec==0.12.1
platformdirs==4.4.0
sqlparse==0.5.3
tomli==2.2.1
typing-extensions==4.15.0
djangorestframework==3.16.1
google-api-core==2.25.1
google-api-python-client==2.181.0
google-auth==2.40.3
google-auth-httplib2==0.2.0
google-auth-oauthlib==1.2.2
googleapis-common-protos==1.70.0