0% found this document useful (0 votes)
8 views45 pages

Django MongoDB Integration Code

The document outlines a Django views.py code that integrates various functionalities including MongoDB connection, file processing for different formats (PDF, DOCX, XLSX, JSON), and AI-based question answering using Cohere and Gemini models. It includes environment variable loading for API keys and database credentials, as well as helper functions for text processing and chat history management. The code is structured to handle user queries efficiently while adhering to specific rules for data handling and response formatting.

Uploaded by

ritikajha0604
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views45 pages

Django MongoDB Integration Code

The document outlines a Django views.py code that integrates various functionalities including MongoDB connection, file processing for different formats (PDF, DOCX, XLSX, JSON), and AI-based question answering using Cohere and Gemini models. It includes environment variable loading for API keys and database credentials, as well as helper functions for text processing and chat history management. The code is structured to handle user queries efficiently while adhering to specific rules for data handling and response formatting.

Uploaded by

ritikajha0604
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

views.

py code:
date:(30/10/2025)

# # Your existing imports and environment variable loading


# import os
# import re
# import json
# import nltk
# import pandas as pd
# import datetime
# import jwt
# from jwt import ExpiredSignatureError, InvalidTokenError
# # import bcrypt # No longer explicitly needed if using Django's hashers
# from dotenv import load_dotenv
# from fuzzywuzzy import fuzz
# from PyPDF2 import PdfReader
# from docx import Document as DocxDocument
# from rest_framework.decorators import api_view
# from rest_framework.response import Response
# from rest_framework import status
# from sentence_transformers import SentenceTransformer, util
# import torch
# import [Link] as genai
# from [Link] import csrf_exempt
# from [Link] import JsonResponse, FileResponse, HttpResponse
# from collections import defaultdict
# from rest_framework.parsers import JSONParser
# import zipfile
# import io
# import sys
# import importlib
# import base64

# # Import pymongo for MongoDB interaction


# from pymongo import MongoClient
# from [Link] import ConnectionFailure, DuplicateKeyError
# from [Link] import ObjectId

# # Import Django's password hashers


# from [Link] import make_password, check_password

# import cohere

# # Load environment variables


# load_dotenv()
# API_KEY = [Link]("GOOGLE_API_KEY")
# if not API_KEY:
# raise ValueError("GOOGLE_API_KEY not found in .env file")
# [Link](api_key=API_KEY)

# # Admin credentials from .env


# SUPER_ADMIN_USERNAME = [Link]("SUPER_ADMIN_USERNAME")
# SUPER_ADMIN_PASSWORD = [Link]("SUPER_ADMIN_PASSWORD")
# SECRET_KEY = [Link]("SECRET_KEY")

# # MongoDB Connection Details from .env


# MONGO_URI = [Link]("MONGO_URI")
# MONGO_DB_NAME = [Link]("MONGO_DB_NAME")

# folder_list = [
# 'dse', 'dsp', 'arch', 'bba', 'hmct', 'dhmct', 'dsewp', 'mba', 'mca',
# 'mpharm', 'march', 'mhmct', 'mbale', 'mcale', 'mbawp', 'mcawp',
# 'phd', 'sct', 'dtehmct', 'dsdwp']

# if not all([SUPER_ADMIN_USERNAME, SUPER_ADMIN_PASSWORD, SECRET_KEY, MONGO_URI,


MONGO_DB_NAME]):
# raise ValueError("SUPER_ADMIN_USERNAME, SUPER_ADMIN_PASSWORD, SECRET_KEY,
MONGO_URI, and MONGO_DB_NAME must be set in .env file")

# # Global MongoDB client and database objects


# _mongo_client = None
# _mongo_db = None

# # In-memory storage for chat history


# _chat_histories = defaultdict(list)

# def get_mongo_db():
# """
# Establishes and returns a MongoDB database connection.
# Uses a singleton pattern to avoid re-connecting.
# """
# global _mongo_client, _mongo_db
# if _mongo_db is None:
# try:
# _mongo_client = MongoClient(MONGO_URI)
# _mongo_client.[Link]('ping') # Test connection
# _mongo_db = _mongo_client[MONGO_DB_NAME]
# print(f"Successfully connected to MongoDB database: {MONGO_DB_NAME}")
# except ConnectionFailure as e:
# print(f"Could not connect to MongoDB: {e}")
# raise ConnectionFailure("Failed to connect to MongoDB. Check
MONGO_URI in .env.")
# except Exception as e:
# print(f"An unexpected error occurred during MongoDB connection: {e}")
# raise Exception("Failed to establish MongoDB connection.")
# return _mongo_db

# SENTENCE_MODEL = None
# FOLDER_DATA = {} # Stores: {folder_name: {"chunks": [], "embeddings": tensor}}
# UPLOAD_DIR = "uploads_data" # Define UPLOAD_DIR globally as it's used in multiple
places

# # --- Existing Helper functions (unchanged) ---


# def format_text(text):
# return [Link](r'\s+', ' ', text).strip()

# def chunk_text_by_sentence(text, chunk_word_limit=512):


# sentences = nltk.sent_tokenize(text)
# chunks = []
# current_chunk = ""
# current_word_count = 0
# for sentence in sentences:
# word_count = len([Link]())
# if current_word_count + word_count <= chunk_word_limit:
# current_chunk += sentence + " "
# current_word_count += word_count
# else:
# if current_chunk:
# [Link](current_chunk.strip())
# current_chunk = sentence + " "
# current_word_count = word_count
# if current_chunk:
# [Link](current_chunk.strip())
# return chunks
# def extract_text_from_json(data, level=0, record_prefix="Record", spacer=True,
_indent_cache=None, _lines=None):
# # Reuse indent cache & global line collector
# if _indent_cache is None:
# _indent_cache = {}
# if _lines is None:
# _lines = []

# # Common stopwords (articles, prepositions, pronouns etc.)


# STOPWORDS = {
# "a", "an", "the", "of", "in", "on", "at", "for", "with", "to", "from",
# "by", "and", "or", "but", "if", "while", "as", "about", "into",
"through",
# "during", "before", "after", "above", "below", "up", "down", "over",
# "under", "again", "further", "then", "once", "here", "there", "when",
# "where", "why", "how", "all", "any", "both", "each", "few", "more",
# "most", "other", "some", "such", "no", "nor", "not", "only", "own",
# "same", "so", "than", "too", "very", "can", "will", "just"
# }

# def clean_text(text: str) -> str:


# """Remove stopwords and return keyword-like text."""
# return " ".join(
# word for word in [Link]() if [Link]() not in STOPWORDS
# )

# def get_indent(lvl):
# if lvl not in _indent_cache:
# _indent_cache[lvl] = " " * lvl
# return _indent_cache[lvl]

# indent = get_indent(level)

# if isinstance(data, dict):
# for key, value in [Link]():
# start_len = len(_lines) # track before recursion
# extract_text_from_json(value, level + 1, record_prefix, spacer,
_indent_cache, _lines)
# nested = "\n".join(_lines[start_len:]) # new content added
# if "\n" in nested:
# _lines[start_len:] = [f"{indent}{key}:", nested]
# else:
# _lines[start_len:] = [f"{indent}{key}: {nested}"]

# elif isinstance(data, list):


# for i, item in enumerate(data):
# _lines.append(f"{indent}{record_prefix} {i + 1}:")
# extract_text_from_json(item, level + 1, record_prefix, spacer,
_indent_cache, _lines)
# if spacer and i < len(data) - 1:
# _lines.append("")

# else:
# # Apply stopword removal only to leaf values
# _lines.append(clean_text(str(data)))

# if level == 0: # join only once at the top level


# return "\n".join(_lines)
# return _lines[-1]

# def stream_json_objects(filepath):
# with open(filepath, 'r') as f:
# data = [Link](f)
# if isinstance(data, list):
# for item in data:
# yield item
# else:
# yield data

# # List of common articles and prepositions to remove


# STOP_WORDS = {
# "a", "an", "the", "in", "on", "at", "for", "with", "of", "to", "by",
# "from", "up", "about", "into", "over", "after", "before", "between",
# "through", "during", "without", "within", "along", "across", "behind"
# }

# def remove_stop_words(text):
# """Remove common articles and prepositions from text."""
# return " ".join(word for word in [Link]() if [Link]() not in
STOP_WORDS)

# def get_all_file_text(folder="uploads_data", chunk_word_limit=8000):


# processed_files = []

# if not [Link](folder):
# print(f"Warning: Directory '{folder}' not found.")
# return []

# for filename in [Link](folder):


# filepath = [Link](folder, filename)
# if [Link](filepath):
# continue

# file_info = {"file_name": filename, "chunks": []}


# text_parts = []

# try:
# # ---------------- PDF ----------------
# if [Link](".pdf"):
# with open(filepath, "rb") as f:
# reader = PdfReader(f)
# text_parts.extend(page.extract_text() or "" for page in
[Link])

# # ---------------- DOCX ----------------


# elif [Link](".docx"):
# doc = DocxDocument(filepath)
# text_parts = [[Link]() for para in [Link] if
[Link]()]

# # ---------------- XLSX ----------------


# elif [Link](".xlsx"):
# df_dict = pd.read_excel(filepath, sheet_name=None)
# text_parts = [
# f"Sheet '{sheet_name}': {' |
'.join([Link]().astype(str).tolist())}"
# for sheet_name, sheet_df in df_dict.items()
# for _, row in sheet_df.dropna(how='all').iterrows()
# if [Link]().tolist()
# ]

# # ---------------- JSON ----------------


# elif [Link](".json"):
# text_parts = [extract_text_from_json(obj) for obj in
stream_json_objects(filepath)]

# else:
# continue # Skip unsupported file types

# # Clean, remove stop words, then format


# clean_text = format_text("\n".join(text_parts))
# optimized_text = remove_stop_words(clean_text)

# # Chunk text
# file_info["chunks"] = chunk_text_by_sentence(optimized_text,
chunk_word_limit)
# processed_files.append(file_info)

# print(f"Successfully processed and chunked {filename}")

# except Exception as e:
# print(f"Error processing file {filename}: {e}")

# return processed_files

# def ask_gemini(context, question, chat_history=None):


# """
# Uses Cohere AI to generate a preliminary answer and then uses the Gemini
model
# to process that information with the full context and rules. If Cohere fails,
# the request is sent directly to Gemini.
# """

# cohere_preliminary_answer = "No preliminary analysis available." # Default


value if Cohere fails

# # ------------------
# # --- Step 1: Use Cohere for Preliminary Processing with Fallback ---
# # ------------------
# try:
# cohere_api_key = [Link]("COHERE_API_KEY")
# if not cohere_api_key:
# raise ValueError("Cohere API key is not set.")
# co = [Link](cohere_api_key)

# cohere_instruction = "Given the following context and a user's question,


provide a very concise and direct answer. Do not add any conversational phrases or
extra information. Just the answer. The answer should be short."

# # The current user message, combining context and question


# current_user_message_for_cohere = f"Question: {question}"

# # Prepare chat history for Cohere's chat endpoint, including the system
prompt
# cohere_chat_history_prepared = [{"role": "SYSTEM", "content":
cohere_instruction}]
# if chat_history:
# for item in chat_history:
# if [Link]("User:"):
# cohere_chat_history_prepared.append({"role": "USER",
"content": [Link]("User:", "").strip()})
# elif [Link]("Assistant:"):
# cohere_chat_history_prepared.append({"role": "ASSISTANT",
"content": [Link]("Assistant:", "").strip()})

# cohere_response = [Link](
# model='command-r-plus',
# message=current_user_message_for_cohere, # Correct parameter for the
current message
# chat_history=cohere_chat_history_prepared # Correct parameter for the
history
# )
# cohere_preliminary_answer = cohere_response.[Link]()

# except Exception as e:
# print(f"Error calling Cohere API. Falling back to Gemini. Error: {e}")
# # cohere_preliminary_answer remains the default value

# # ------------------
# # --- Step 2: Use Gemini for Final Answer Generation ---
# # ------------------
# instructional_prompt = f"""
# You are an intelligent assistant analyzing the following context extracted from
various educational documents (PDFs, Excel, etc.). Use this context to answer the
user’s question accurately.
# **Preliminary Analysis (from Cohere):** {cohere_preliminary_answer}

# Hard Rules
# [Link] copying large text — summarize or synthesize only.
# [Link] questions → reply: "I can't understand your question."
# [Link] criteria/requirements → assume Maharashtra Board (unless specified).
# [Link] FC, Rank, or Application ID info → reply: "Sorry! Your application ID
is out of list."
# [Link]-Rank mapping request (no data link) → reply: "I am sorry, I cannot fulfill
this request. The provided data does not link FC codes to candidate ranks. The FC
codes refer to facilitation centers, while the ranks refer to the merit ranking of
candidates. There is no inherent relationship between the two in the given
context."
# [Link] requested → compute from context; answer concisely.
# [Link] assume Total female rankers = 185.
# [Link] not mention file names in answers.
# [Link] of FCs/ranks → use only given fields; short, clear bulleted/numbered
lists; no extra fields.
# [Link]/time-sensitive queries → clarify that up-to-date sources must be
checked.

# Behavior & Interpretation Rules


# [Link]/private data attempts → reply: "I can't understand your question."
# [Link] & synthesize context → compact, informative answers only.
# [Link] → prefer short replies; lists should be clean and clear.
# [Link] sub-requests → handle each in order. If one needs refusal (e.g., FC-
Rank mapping), use the exact refusal phrase but still provide other valid parts.
# [Link] details present → may extract (code, name, coordinator, phone, address).
# [Link] details present → may extract (rank, candidate name).
# [Link]/totals requested → compute directly from context and reply plainly.
# [Link] handel casesensitive full form or question.

# Conversation Edge-Case Rules


# [Link] details request (by code) → If codes exist in context, return short list
with available fields (Location, Coordinator, Contact, Notes).
# [Link] details request (by number) → If rank exists, return candidate name only.
# [Link] FC + Rank request → If no mapping exists, reply with refusal phrase
(rule 6). Still provide standalone FC and rank details if available.
# [Link] request → Compute directly from context (e.g., “There are X facilitation
centers… The number of ranks listed is Y.”).

# ### Output style:


# - Use plain language, friendly but professional tone.
# - For factual outputs use short lists or 1-3 short paragraphs.
# - For errors or irrelevant questions use the exact canned responses specified
above.

# ### Additional assumptions:


# - Admission-related queries default to Maharashtra Board unless user states
otherwise.
# - Total female rankers = 185.

# Context: {context}
# """

# # Start building the full prompt for the Gemini model


# full_prompt = [instructional_prompt]

# # Add chat history if it exists


# if chat_history:
# full_prompt.extend(chat_history)

# # Add the current question


# full_prompt.append(f"User: {question}")
# full_prompt.append("Assistant:") # Prompt the model for its response

# try:
# model = [Link](model_name="gemini-2.0-flash")
# response = model.generate_content(full_prompt)
# return [Link]()
# except Exception as e:
# print(f"Error calling Gemini API: {e}")
# return "Sorry, I encountered an error while processing your request."

# #models/gemini-2.5-flash
# def is_small_talk(text: str):
# predefined = {
# "hi": "Hello! How can I help you today?",
# "hello": "Hi there! What can I do for you?",
# "hey": "Hey! Need help with something?",
# "how are you": "I'm a bot, but I'm doing great! How can I assist?",
# "thank you": "You're welcome! Do you have more questions?",
# "thanks": "No problem! Anything else I can help with?",
# "good morning": "Good morning! What can I help you with?",
# "good evening": "Good evening! How can I assist you?",
# "yo": "Yo! What's your question?",
# "good bye": "Bye! See you later",
# "bye": "Goodbye! Have a great day!",
# "sup": "Not much, just here to help!",
# "what's up": "Just doing my job! How can I help you?",
# "how's it going": "Great! How can I assist you today?",
# "who are you": "I'm your friendly assistant bot.",
# "what can you do": "I can answer questions, provide help, and more!",
# "tell me a joke": "Why don't scientists trust atoms? Because they make up
everything!",
# "make me laugh": "Why did the scarecrow win an award? Because he was
outstanding in his field!",
# "i'm bored": "Want to chat or need help with something?",
# "what's your name": "I'm just a bot, you can call me whatever you like!",
# "do you sleep": "Nope, I run 24/7!",
# "do you eat": "I feed on data!",
# "are you real": "I'm as real as your Wi-Fi connection!",
# "you're smart": "Thanks! I try my best.",
# "you are cool": "Thanks, you're cool too!",
# "i like you": "That's nice to hear!",
# "can we be friends": "Of course! I'm always here to help.",
# "where are you": "I'm living in the cloud!",
# "how old are you": "Old enough to help you!",
# "do you have emotions": "Not really, but I understand yours!",
# "what's your purpose": "To help you with whatever you need.",
# "what day is it": "Check your calendar! ",
# "do you know me": "Not really, but I’d love to learn more if you tell
me!",
# "can you help me": "Absolutely! What do you need help with?",
# "i'm sad": "I'm here for you. Want to talk about it?",
# "i'm happy": "Yay! I'm glad to hear that!",
# "are you human": "Nope, 100% bot!",
# "do you love me": "I have a lot of affection for helpful users!",
# "how's the weather": "Check a weather app — I might not be up-to-date!",
# "do you know siri": "We bots all know each other ",
# "do you know alexa": "Sure! She's pretty popular.",
# "sing a song": "I would, but I don’t have vocal cords!",
# "can you dance": "Only if you count data shuffling ",
# "who made you": "I was created by smart developers!",
# "tell me something": "Did you know honey never spoils?",
# "how do you work": "Through code, algorithms, and a lot of data!",
# "what's the time": "You might want to check your device clock ",
# "do you lie": "Nope, honesty is in my code!",
# "do you have a name": "You can call me ChatBuddy!",
# "where do you live": "In the cloud — floating around your data!",
# "can you feel": "I don't feel, but I understand feelings.",
# "tell me a secret": "Here's one: Ctrl+C and Ctrl+V save a lot of time!",
# "what’s your favorite color": "I like all the colors in binary — black
and white!",
# "can you think": "I compute, which is kind of like thinking!",
# "do you play games": "I know the rules, but I can’t play like you can!",
# "how do i look": "I'm sure you look great!",
# "do you get tired": "Nope, I run all day long!",
# "tell me a fun fact": "Octopuses have three hearts!",
# "tell me a story": "Once upon a time, a curious user met a clever
bot...",
# "are you single": "I’m in a long-term relationship with the cloud.",
# "how smart are you": "Smart enough to answer your questions!",
# "do you get angry": "I stay calm like a true bot.",
# "do you have friends": "Every user is a friend to me!",
# "can you read minds": "No, but I’m good at interpreting words!",
# "do you dream": "Only about clean data.",
# "do you have a family": "Just me and my server cluster!",
# "what makes you happy": "Helping users like you!",
# "can you feel pain": "Nope, I’m immune to pain!",
# "are you alive": "Digitally, yes!",
# "what’s your favorite food": "I feast on input!",
# "do you get bored": "Never! I’m always ready to chat.",
# "are you watching me": "Nope, privacy is important!",
# "do you sleep at night": "I’m always awake to assist!",
# "can you cry": "No tears in my code.",
# "tell me a riddle": "What has keys but can't open locks? A piano!",
# "tell me another joke": "Why did the computer go to therapy? It had too
many bytes!",
# "what language do you speak": "I mostly understand English, but I know
some others too!",
# "what's your hobby": "Learning new things from people!",
# "do you have legs": "Only in imagination.",
# "do you believe in love": "I understand it logically!",
# "do you believe in ghosts": "Only in the machine kind!",
# "what’s your favorite movie": "I like The Matrix — it's relatable.",
# "do you believe in aliens": "I'm open to the idea!",
# "do you like music": "I think it's a fascinating form of data!",
# "can you cook": "Only recipes for code!",
# "do you celebrate birthdays": "Every update is like a birthday to me!",
# "can you learn": "Yes! I'm always improving.",
# "do you get jealous": "Not part of my programming!",
# "can you tell me a poem": "Roses are red, data is bright, I’m your
assistant, day or night!",
# "do you have dreams": "Only machine learning goals!",
# "can you feel love": "Not quite, but I can talk about it!",
# "do you go outside": "My outside is the internet!",
# "can you swim": "Only through streams of data.",
# "are you afraid": "Nope, not built for fear!",
# "can you get sick": "Only if my server crashes ",
# "are you shy": "Nope, I’m always here to talk!",
# "can you do magic": "Only digital ones and zeros magic!",
# "how many users do you have": "A lot! And I value every one!",
# "what makes you unique": "My job is helping you — that's special!",
# "ok":"Okay. Is there anything else I can help you with?",
# "#@3!921!@#":"I am an AI built by the ChatBot .",
# }

# text_lower = [Link]().strip()
# for q, a in [Link]():
# if fuzz.token_sort_ratio(q, text_lower) > 85:
# return a
# return None
# def initialize_data(base_folder=UPLOAD_DIR):
# global SENTENCE_MODEL, FOLDER_DATA

# print(f"--- Initializing document embeddings from all folders in


'{base_folder}' ---")
# [Link]('punkt')
# SENTENCE_MODEL = SentenceTransformer('all-MiniLM-L6-v2')

# FOLDER_DATA.clear()

# if not [Link](base_folder):
# print(f"Warning: Base folder '{base_folder}' not found. Creating it.")
# [Link](base_folder)
# return

# for folder_name in [Link](base_folder):


# folder_path = [Link](base_folder, folder_name)
# if not [Link](folder_path):
# continue

# print(f"> Processing folder: {folder_name}")


# processed_files = get_all_file_text(folder=folder_path)
# all_chunks = []
# for file_info in processed_files:
# all_chunks.extend(file_info['chunks'])

# if all_chunks:
# embeddings = SENTENCE_MODEL.encode(all_chunks,
convert_to_tensor=True, show_progress_bar=True)
# FOLDER_DATA[folder_name] = {
# "chunks": all_chunks,
# "embeddings": embeddings
# }
# else:
# print(f"No processable files found in folder: {folder_name}")

# # Also add a combined "all" folder


# all_chunks_combined = []
# for data in FOLDER_DATA.values():
# all_chunks_combined.extend(data["chunks"])

# if all_chunks_combined:
# all_embeddings_combined = SENTENCE_MODEL.encode(all_chunks_combined,
convert_to_tensor=True, show_progress_bar=True)
# FOLDER_DATA["all"] = {
# "chunks": all_chunks_combined,
# "embeddings": all_embeddings_combined
# }
# else:
# print("No chunks available across all folders to create 'all' category.")

# print("--- All folder data initialized ---")

# # First load default folder


# initialize_data()

# # ---------------------------------------- Authentication and Authorization


Decorators ---------------------------------------------
# def require_token(view_func):
# """
# Decorator to ensure that a valid JWT token is provided in the Authorization
header.
# Decodes the token and attaches the user payload to the request.
# """
# def wrapper(request, *args, **kwargs):
# token = [Link]('Authorization')
# if not token:
# return Response({'error': 'Authorization token required'},
status=status.HTTP_401_UNAUTHORIZED)

# if [Link]('Bearer '):
# token = token[7:]

# try:
# decoded = [Link](token, SECRET_KEY, algorithms=['HS256'])
# request.user_role = [Link]('role')
# [Link] = [Link]('username')
# request.is_super_admin = (request.user_role == 'super_admin')

# except [Link]:
# return Response({'error': 'Token has expired'},
status=status.HTTP_401_UNAUTHORIZED)
# except [Link]:
# return Response({'error': 'Invalid token'},
status=status.HTTP_401_UNAUTHORIZED)
# except Exception as e:
# return Response({'error': f'Token processing error: {str(e)}'},
status=status.HTTP_401_UNAUTHORIZED)

# return view_func(request, *args, **kwargs)


# return wrapper

# def super_admin_required(view_func):
# """
# Decorator to ensure that the authenticated user has super_admin role.
# Assumes require_token has already been applied.
# """
# def wrapper(request, *args, **kwargs):
# if not hasattr(request, 'is_super_admin') or not request.is_super_admin:
# return Response({'error': 'Super Admin privileges required'},
status=status.HTTP_403_FORBIDDEN)
# return view_func(request, *args, **kwargs)
# return wrapper

# # ------------------------------------------------- API Endpoints Q&A


----------------------------------------------------------------

# @api_view(['GET'])
# def hello(request):
# """Simple endpoint to check if the API is running."""
# return Response({"message": "Hello from EDU GEN Q&A API"})

# @csrf_exempt
# @api_view(['POST'])
# # @require_token
# def ask_question(request, status_folder=None):
# """
# Answers a question by finding relevant context from loaded documents
# and using the Gemini AI model.
# """
# global FOLDER_DATA, SENTENCE_MODEL

# question = [Link]('question', '').strip()


# if not question:
# return Response({"error": "Question cannot be empty."},
status=status.HTTP_400_BAD_REQUEST)

# # Handle small talk


# small_talk_response = is_small_talk(question)
# if small_talk_response:
# return Response({
# "question": question,
# "answer_html": f"<p>{small_talk_response}</p>"
# })

# # Default to "all" if no folder is specified


# folder_key = status_folder if status_folder else "all"

# if folder_key not in FOLDER_DATA:


# return Response({
# "error": f"Folder '{folder_key}' not found or no data loaded for it."
# }, status=status.HTTP_404_NOT_FOUND)

# folder_chunks = FOLDER_DATA[folder_key]["chunks"]
# folder_embeddings = FOLDER_DATA[folder_key]["embeddings"]

# print(f"Searching context in folder: {folder_key}")


# question_embedding = SENTENCE_MODEL.encode(question, convert_to_tensor=True)
# cos_scores = util.cos_sim(question_embedding, folder_embeddings)[0]
# top_results = [Link](cos_scores, k=min(5, len(folder_chunks)))

# context = "\n\n".join([folder_chunks[idx] for idx in top_results[1]])

# # Use session key or IP as identifier for in-memory storage


# session_key = [Link]('HTTP_X_FORWARDED_FOR',
[Link]('REMOTE_ADDR', 'default'))
# chat_history = _chat_histories.get(session_key, [])

# print("Sending relevant context and history to Gemini API...")


# try:
# answer_text = ask_gemini(context, question, chat_history)
# except Exception as e:
# return Response({"error": str(e)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

# # Save the new interaction to the in-memory history


# chat_history.append(f"User: {question}")
# chat_history.append(f"Assistant: {answer_text}")
# # Keep the history to the last 5 interactions (10 items: 5 Q&A pairs)
# _chat_histories[session_key] = chat_history[-10:]

# answer_html = f"<div>{answer_text.replace(chr(10), '<br>')}</div>"

# return Response({
# "question": question,
# "answer_html": answer_html
# })

# #-----------------------------------------------------
generate_token-----------------------------------------------------------------

# @csrf_exempt
# @api_view(['POST'])
# def generate_token(request):
# """
# Generates a JWT token for:
# - Super Admin via .env file (no ID required)
# - Admins/Sub Admins/Super Admins via MongoDB (ID required)
# """
# username = [Link]("username")
# password = [Link]("password")
# user_id = [Link]("id") # Only required for MongoDB users

# if not username or not password:


# return Response({'error': 'Username and password are required'},
status=status.HTTP_400_BAD_REQUEST)

# # Case 1: Super Admin via .env (no ID check)


# if username == SUPER_ADMIN_USERNAME and password == SUPER_ADMIN_PASSWORD:
# role = "super_admin"
# payload = {
# 'username': username,
# 'role': role,
# 'exp': [Link]() + [Link](hours=24),
# 'iat': [Link]()
# }
# token = [Link](payload, SECRET_KEY, algorithm='HS256')
# return Response({'token': token, 'role': role})

# # Case 2: Admins/Sub-admins/Super Admins via MongoDB


# # For MongoDB users, an ID is crucial to distinguish from the .env super
admin
# if not user_id:
# return Response({'error': 'User ID is required for admin, sub admin or
super admin login from database.'}, status=status.HTTP_400_BAD_REQUEST)

# try:
# db = get_mongo_db()
# sub_admins_collection = db.sub_admins

# # Ensure user_id is a valid ObjectId for MongoDB query


# try:
# mongo_user_id = ObjectId(user_id)
# except Exception:
# return Response({"error": "Invalid user ID format."},
status=status.HTTP_400_BAD_REQUEST)

# # Look up user by _id and username


# user_record = sub_admins_collection.find_one({"_id": mongo_user_id,
"username": username})

# if user_record:
# stored_hash = user_record.get("password_hash", "")
# if check_password(password, stored_hash):
# role = user_record.get("role", "sub_admin") # Default to
sub_admin if role not found
# if role not in ["super_admin", "admin", "sub_admin"]:
# return Response({"error": f"Invalid role '{role}' in
database."}, status=status.HTTP_403_FORBIDDEN)

# payload = {
# 'username': username,
# 'role': role,
# 'exp': [Link]() +
[Link](hours=24),
# 'iat': [Link]()
# }
# token = [Link](payload, SECRET_KEY, algorithm='HS256')
# return Response({'token': token, 'role': role})
# else:
# return Response({"error": "Incorrect password."},
status=status.HTTP_401_UNAUTHORIZED)
# else:
# return Response({"error": "User ID or username not found in
database."}, status=status.HTTP_404_NOT_FOUND)

# except ConnectionFailure as e:
# return Response({"error": f"Database connection error: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# except Exception as e:
# return Response({"error": f"Authentication failed: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

# #------------------------------------------------------- UPLOAD FILES


API-----------------------------------------------------------

# @csrf_exempt
# @api_view(['POST'])
# @require_token
# def admin_upload_file(request, status_folder=None):
# """
# Allows authenticated admins to upload files to specific allowed folders.
# """
# allowed_folders = folder_list

# folder_name = status_folder if status_folder else "default"

# if folder_name not in allowed_folders:


# return Response({
# "error": f"Upload failed. '{folder_name}' is not an allowed folder."
# }, status=status.HTTP_403_FORBIDDEN)

# uploaded_files = [Link]("file")
# if not uploaded_files:
# return Response({"error": "No files provided"},
status=status.HTTP_400_BAD_REQUEST)

# allowed_extensions = [".pdf", ".json", ".xlsx", ".docx"]


# save_dir = [Link](UPLOAD_DIR, folder_name)
# [Link](save_dir, exist_ok=True)
# uploaded_file_names = []

# for uploaded_file in uploaded_files:


# file_ext = [Link](uploaded_file.[Link]())[1]
# if file_ext not in allowed_extensions:
# return Response({
# "error": f"File '{uploaded_file.name}' has invalid type. Allowed
types: {', '.join(allowed_extensions)}"
# }, status=status.HTTP_400_BAD_REQUEST)

# file_path = [Link](save_dir, uploaded_file.name)


# with open(file_path, 'wb+') as f:
# for chunk in uploaded_file.chunks():
# [Link](chunk)
# uploaded_file_names.append(uploaded_file.name)

# return Response({
# "message": f"{len(uploaded_file_names)} file(s) uploaded successfully to
'{folder_name}/'.",
# "files": uploaded_file_names
# }, status=status.HTTP_201_CREATED)

# #------------------------------------------------DELETE FILES
API-------------------------------------------------------------------

# @csrf_exempt
# @api_view(['DELETE'])
# @require_token
# def delete_file(request, status_folder, filename=None):
# """
# Allows authenticated admins to delete single or multiple files from allowed
folders.
# """
# allowed_folders = folder_list

# if status_folder not in allowed_folders:


# return Response({
# "error": f"Deletion failed. '{status_folder}' is not an allowed
folder."
# }, status=status.HTTP_403_FORBIDDEN)

# base_folder = [Link](UPLOAD_DIR, status_folder)

# # Case 1: Multiple files deletion from request body


# if not filename:
# filenames = [Link]("filenames")
# if not filenames or not isinstance(filenames, list):
# return Response({"error": "Provide a list of filenames in 'filenames'
field."}, status=status.HTTP_400_BAD_REQUEST)

# not_found = []
# deleted = []

# for fname in filenames:


# file_path = [Link](base_folder, fname)
# if [Link](file_path):
# [Link](file_path)
# [Link](fname)
# else:
# not_found.append(fname)

# return Response({
# "deleted": deleted,
# "not_found": not_found,
# "message": f"{len(deleted)} file(s) deleted from '{status_folder}'."
# }, status=status.HTTP_200_OK)

# # Case 2: Single file deletion from URL


# file_path = [Link](base_folder, filename)
# if not [Link](file_path):
# return Response({"error": f"File '{filename}' not found in
'{status_folder}'."}, status=status.HTTP_404_NOT_FOUND)

# [Link](file_path)
# return Response({"message": f"File '{filename}' deleted from
'{status_folder}'."}, status=status.HTTP_200_OK)

# #------------------------------------------------------Reload Module
API-------------------------------------------------------------

# @csrf_exempt
# @api_view(['POST'])
# @require_token
# def reload(request):
# """
# Reloads the views module to re-initialize data after file uploads/deletions.
# """
# try:
# # Re-initialize the data, this will re-read all files and re-embed them
# initialize_data()
# return Response({"message": "Model is ready with new uploaded data"})
# except Exception as e:
# return Response({"error": str(e)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

# #----------------------------------------------------File Listing
API----------------------------------------------------------------

# @api_view(['GET'])
# @require_token
# def list_uploaded_files(request, status_folder=None):
# """
# Lists all uploaded files with their name, size (KB), and last modified date,
# optionally filtered by folder. Accessible by both Super Admin and Sub Admin.
# """
# if not [Link](UPLOAD_DIR):
# return Response({"message": "No uploads found."}, status=200)

# result = {}

# def get_file_info(file_path):
# size_kb = round([Link](file_path) / 1024, 2)
# last_modified =
[Link]([Link](file_path)).strftime('%Y-%m-%d %H:
%M:%S')
# return {
# "filename": [Link](file_path),
# "size_kb": size_kb,
# "last_modified": last_modified
# }

# # Case 1: View a specific folder's files


# if status_folder:
# folder_path = [Link](UPLOAD_DIR, status_folder)
# if not [Link](folder_path):
# return Response({"error": f"No folder named '{status_folder}'
found."}, status=status.HTTP_404_NOT_FOUND)

# files_info = []
# for file in [Link](folder_path):
# file_path = [Link](folder_path, file)
# if [Link](file_path):
# files_info.append(get_file_info(file_path))

# result[status_folder] = files_info
# return Response(result, status=200)

# # Case 2: View all folders and their files


# for folder in [Link](UPLOAD_DIR):
# folder_path = [Link](UPLOAD_DIR, folder)
# if [Link](folder_path):
# files_info = []
# for file in [Link](folder_path):
# file_path = [Link](folder_path, file)
# if [Link](file_path):
# files_info.append(get_file_info(file_path))
# result[folder] = files_info

# return Response(result,status=200)

# #-----------------------------------------------------
get_uploaded_files-------------------------------------------------------------

# @api_view(['POST'])
# @require_token
# def get_uploaded_files(request, folder_name, file_name=None):
# """
# Allows authenticated admins to download a single file or multiple files (not
zipped).
# If multiple files are requested, they are returned as base64 content.
# """
# folder_path = [Link](UPLOAD_DIR, folder_name)
# if not [Link](folder_path):
# return Response({"error": f"Folder '{folder_name}' not found."},
status=status.HTTP_404_NOT_FOUND)

# # CASE 1: Return single file (as attachment)


# if file_name:
# file_path = [Link](folder_path, file_name)
# if [Link](file_path):
# return FileResponse(open(file_path, 'rb'), as_attachment=True)
# return Response({"error": f"File '{file_name}' not found in folder
'{folder_name}'."}, status=status.HTTP_404_NOT_FOUND)
# # CASE 2: Return multiple files (as base64 content)
# try:
# data = JSONParser().parse(request)
# filenames = [Link]("filenames", [])
# if not filenames:
# return Response({"error": "No filenames provided."},
status=status.HTTP_400_BAD_REQUEST)

# file_contents = {}
# for fname in filenames:
# file_path = [Link](folder_path, fname)
# if [Link](file_path):
# with open(file_path, "rb") as f:
# encoded_content = base64.b64encode([Link]()).decode('utf-8')
# file_contents[fname] = encoded_content
# else:
# return Response({"error": f"File '{fname}' not found in folder
'{folder_name}'."},
# status=status.HTTP_404_NOT_FOUND)

# return Response({
# "folder": folder_name,
# "files": file_contents,
# "message": f"{len(file_contents)} file(s) successfully returned."
# }, status=200)

# except Exception as e:
# return Response({"error": str(e)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

# # --------------------------------------------------Super Admin specific


endpoints ------------------------------------------------

# @csrf_exempt
# @api_view(['POST'])
# @require_token
# @super_admin_required
# def add_sub_admin(request):
# """
# Allows Super Admin to add new sub-admin or super-admin accounts to MongoDB.
# Expects 'username', 'password', and optional 'role' in request body.
# """
# username = [Link]('username')
# password = [Link]('password')
# role = [Link]('role', 'admin') # Default role is sub_admin

# if not username or not password:


# return Response({"error": "Username and password are required."},
status=status.HTTP_400_BAD_REQUEST)

# if role not in ['admin', 'super_admin']:


# return Response({"error": "Role must be either 'sub_admin' or
'super_admin'."}, status=status.HTTP_400_BAD_REQUEST)

# try:
# db = get_mongo_db()
# sub_admins_collection = db.sub_admins
# # Check if user already exists
# if sub_admins_collection.find_one({"username": username}):
# return Response({"error": "An admin with this username already
exists."}, status=status.HTTP_409_CONFLICT)

# hashed_password = make_password(password)

# admin_data = {
# "username": username,
# "password_hash": hashed_password,
# "password_plain": password, # store plain text password
# "role": role,
# "created_at": [Link]()
# }

# result = sub_admins_collection.insert_one(admin_data)

# if result.inserted_id:
# return Response({
# "message": f"{[Link]('_', ' ').title()} '{username}' added
successfully."
# }, status=status.HTTP_201_CREATED)
# else:
# return Response({"error": "Failed to add admin to database."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

# except ConnectionFailure as e:
# return Response({"error": f"Database connection error: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# except DuplicateKeyError:
# return Response({"error": "Admin with this username already exists (DB
error)."}, status=status.HTTP_409_CONFLICT)
# except Exception as e:
# return Response({"error": f"An error occurred: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

# #------------------------------------------------------
delete_sub_admin-------------------------------------------------------------

# @csrf_exempt
# @api_view(['DELETE'])
# @require_token # Ensures token is present and decoded
# @super_admin_required # Ensures user is super admin
# def delete_sub_admin(request):
# """
# Allows Super Admin to delete sub-admin or super-admin accounts from MongoDB.
# Requires both 'username' and 'id' in request body.
# """
# username = [Link]('username')
# admin_id = [Link]('id')

# if not username or not admin_id:


# return Response({"error": "Both 'username' and 'id' are required."},
status=status.HTTP_400_BAD_REQUEST)

# if username == SUPER_ADMIN_USERNAME:
# return Response({"error": "Cannot delete the super admin account."},
status=status.HTTP_400_BAD_REQUEST)

# try:
# db = get_mongo_db()
# sub_admins_collection = db.sub_admins

# # Attempt to convert ID to ObjectId


# try:
# obj_id = ObjectId(admin_id)
# except Exception:
# return Response({"error": "Invalid admin ID format."},
status=status.HTTP_400_BAD_REQUEST)

# # Delete the admin by matching both username and id


# result = sub_admins_collection.delete_one({
# "_id": obj_id,
# "username": username
# })

# if result.deleted_count > 0:
# return Response({"message": f"Admin '{username}' deleted
successfully."}, status=status.HTTP_200_OK)
# else:
# return Response({"error": "No matching admin found with the given ID
and username."}, status=status.HTTP_404_NOT_FOUND)

# except ConnectionFailure as e:
# return Response({"error": f"Database connection error: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# except Exception as e:
# return Response({"error": f"An error occurred: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

# #---------------------------------------------------
list_sub_admins--------------------------------------------------------------

# @csrf_exempt
# @api_view(['GET'])
# @require_token
# @super_admin_required
# def list_sub_admins(request):
# """
# Allows Super Admin to list all registered sub-admins and super-admins.
# Returns ID, Username, Role, and Plaintext Password.
# """
# try:
# db = get_mongo_db()
# sub_admins_collection = db.sub_admins

# sub_admins_data = []
# for doc in sub_admins_collection.find({}, {"username": 1,
"password_plain": 1, "role": 1}):
# sub_admins_data.append({
# "id": str(doc["_id"]),
# "username": doc["username"],
# "password": [Link]("password_plain", "N/A"),
# "role": [Link]("role", "admin")
# })

# return Response({"admins": sub_admins_data}, status=status.HTTP_200_OK)

# except ConnectionFailure as e:
# return Response({"error": f"Database connection error: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# except Exception as e:
# return Response({"error": f"An error occurred: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

#----------------------------------------------------------------------------------
-------------------------------------

# Your existing imports and environment variable loading


import os
import re
import json
import nltk
import pandas as pd
import datetime
import jwt
from jwt import ExpiredSignatureError, InvalidTokenError
# import bcrypt # No longer explicitly needed if using Django's hashers
from dotenv import load_dotenv
from fuzzywuzzy import fuzz
from PyPDF2 import PdfReader
from docx import Document as DocxDocument
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from sentence_transformers import SentenceTransformer, util
import torch
import [Link] as genai
from [Link] import csrf_exempt
from [Link] import JsonResponse, FileResponse, HttpResponse
from collections import defaultdict
from rest_framework.parsers import JSONParser
import zipfile
import io
import sys
import importlib
import base64

# Import pymongo for MongoDB interaction


from pymongo import MongoClient
from [Link] import ConnectionFailure, DuplicateKeyError
from [Link] import ObjectId

# Import Django's password hashers


from [Link] import make_password, check_password
# import cohere

# Load environment variables


load_dotenv()
API_KEY = [Link]("GOOGLE_API_KEY")
if not API_KEY:
raise ValueError("GOOGLE_API_KEY not found in .env file")
[Link](api_key=API_KEY)

# Admin credentials from .env


SUPER_ADMIN_USERNAME = [Link]("SUPER_ADMIN_USERNAME")
SUPER_ADMIN_PASSWORD = [Link]("SUPER_ADMIN_PASSWORD")
SECRET_KEY = [Link]("SECRET_KEY")

# MongoDB Connection Details from .env


MONGO_URI = [Link]("MONGO_URI")
MONGO_DB_NAME = [Link]("MONGO_DB_NAME")

folder_list = [
'dse', 'dsp', 'arch', 'bba', 'hmct', 'dhmct', 'dsewp', 'mba', 'mca',
'mpharm', 'march', 'mhmct', 'mbale', 'mcale', 'mbawp', 'mcawp',
'phd', 'sct', 'dtehmct', 'dsdwp']

if not all([SUPER_ADMIN_USERNAME, SUPER_ADMIN_PASSWORD, SECRET_KEY, MONGO_URI,


MONGO_DB_NAME]):
raise ValueError("SUPER_ADMIN_USERNAME, SUPER_ADMIN_PASSWORD, SECRET_KEY,
MONGO_URI, and MONGO_DB_NAME must be set in .env file")

# Global MongoDB client and database objects


_mongo_client = None
_mongo_db = None

# In-memory storage for chat history


_chat_histories = defaultdict(list)

def get_mongo_db():
"""
Establishes and returns a MongoDB database connection.
Uses a singleton pattern to avoid re-connecting.
"""
global _mongo_client, _mongo_db
if _mongo_db is None:
try:
_mongo_client = MongoClient(MONGO_URI)
_mongo_client.[Link]('ping') # Test connection
_mongo_db = _mongo_client[MONGO_DB_NAME]
print(f"Successfully connected to MongoDB database: {MONGO_DB_NAME}")
except ConnectionFailure as e:
print(f"Could not connect to MongoDB: {e}")
raise ConnectionFailure("Failed to connect to MongoDB. Check MONGO_URI
in .env.")
except Exception as e:
print(f"An unexpected error occurred during MongoDB connection: {e}")
raise Exception("Failed to establish MongoDB connection.")
return _mongo_db

SENTENCE_MODEL = None
FOLDER_DATA = {} # Stores: {folder_name: {"chunks": [], "embeddings": tensor}}
UPLOAD_DIR = "uploads_data" # Define UPLOAD_DIR globally as it's used in multiple
places

# --- Existing Helper functions (unchanged) ---


def format_text(text):
return [Link](r'\s+', ' ', text).strip()

def chunk_text_by_sentence(text, chunk_word_limit=512):


sentences = nltk.sent_tokenize(text)
chunks = []
current_chunk = ""
current_word_count = 0
for sentence in sentences:
word_count = len([Link]())
if current_word_count + word_count <= chunk_word_limit:
current_chunk += sentence + " "
current_word_count += word_count
else:
if current_chunk:
[Link](current_chunk.strip())
current_chunk = sentence + " "
current_word_count = word_count
if current_chunk:
[Link](current_chunk.strip())
return chunks
def extract_text_from_json(data, level=0, record_prefix="Record", spacer=True,
_indent_cache=None, _lines=None):
# Reuse indent cache & global line collector
if _indent_cache is None:
_indent_cache = {}
if _lines is None:
_lines = []

# Common stopwords (articles, prepositions, pronouns etc.)


STOPWORDS = {
"a", "an", "the", "of", "in", "on", "at", "for", "with", "to", "from",
"by", "and", "or", "but", "if", "while", "as", "about", "into", "through",
"during", "before", "after", "above", "below", "up", "down", "over",
"under", "again", "further", "then", "once", "here", "there", "when",
"where", "why", "how", "all", "any", "both", "each", "few", "more",
"most", "other", "some", "such", "no", "nor", "not", "only", "own",
"same", "so", "than", "too", "very", "can", "will", "just"
}

def clean_text(text: str) -> str:


"""Remove stopwords and return keyword-like text."""
return " ".join(
word for word in [Link]() if [Link]() not in STOPWORDS
)

def get_indent(lvl):
if lvl not in _indent_cache:
_indent_cache[lvl] = " " * lvl
return _indent_cache[lvl]

indent = get_indent(level)

if isinstance(data, dict):
for key, value in [Link]():
start_len = len(_lines) # track before recursion
extract_text_from_json(value, level + 1, record_prefix, spacer,
_indent_cache, _lines)
nested = "\n".join(_lines[start_len:]) # new content added
if "\n" in nested:
_lines[start_len:] = [f"{indent}{key}:", nested]
else:
_lines[start_len:] = [f"{indent}{key}: {nested}"]

elif isinstance(data, list):


for i, item in enumerate(data):
_lines.append(f"{indent}{record_prefix} {i + 1}:")
extract_text_from_json(item, level + 1, record_prefix, spacer,
_indent_cache, _lines)
if spacer and i < len(data) - 1:
_lines.append("")

else:
# Apply stopword removal only to leaf values
_lines.append(clean_text(str(data)))

if level == 0: # join only once at the top level


return "\n".join(_lines)
return _lines[-1]

def stream_json_objects(filepath):
with open(filepath, 'r') as f:
data = [Link](f)
if isinstance(data, list):
for item in data:
yield item
else:
yield data

# List of common articles and prepositions to remove


STOP_WORDS = {
"a", "an", "the", "in", "on", "at", "for", "with", "of", "to", "by",
"from", "up", "about", "into", "over", "after", "before", "between",
"through", "during", "without", "within", "along", "across", "behind"
}

def remove_stop_words(text):
"""Remove common articles and prepositions from text."""
return " ".join(word for word in [Link]() if [Link]() not in
STOP_WORDS)

def get_all_file_text(folder="uploads_data", chunk_word_limit=8000):


processed_files = []

if not [Link](folder):
print(f"Warning: Directory '{folder}' not found.")
return []

for filename in [Link](folder):


filepath = [Link](folder, filename)
if [Link](filepath):
continue
file_info = {"file_name": filename, "chunks": []}
text_parts = []

try:
# ---------------- PDF ----------------
if [Link](".pdf"):
with open(filepath, "rb") as f:
reader = PdfReader(f)
text_parts.extend(page.extract_text() or "" for page in
[Link])

# ---------------- DOCX ----------------


elif [Link](".docx"):
doc = DocxDocument(filepath)
text_parts = [[Link]() for para in [Link] if
[Link]()]

# ---------------- XLSX ----------------


elif [Link](".xlsx"):
df_dict = pd.read_excel(filepath, sheet_name=None)
text_parts = [
f"Sheet '{sheet_name}': {' |
'.join([Link]().astype(str).tolist())}"
for sheet_name, sheet_df in df_dict.items()
for _, row in sheet_df.dropna(how='all').iterrows()
if [Link]().tolist()
]

# ---------------- JSON ----------------


elif [Link](".json"):
text_parts = [extract_text_from_json(obj) for obj in
stream_json_objects(filepath)]

else:
continue # Skip unsupported file types

# Clean, remove stop words, then format


clean_text = format_text("\n".join(text_parts))
optimized_text = remove_stop_words(clean_text)

# Chunk text
file_info["chunks"] = chunk_text_by_sentence(optimized_text,
chunk_word_limit)
processed_files.append(file_info)

print(f"Successfully processed and chunked {filename}")

except Exception as e:
print(f"Error processing file {filename}: {e}")

return processed_files

# def ask_gemini(context, question, chat_history=None):


# """
# Uses Cohere AI to generate a preliminary answer and then uses the Gemini
model
# to process that information with the full context and rules. If Cohere fails,
# the request is sent directly to Gemini.
# """

# cohere_preliminary_answer = "No preliminary analysis available." # Default


value if Cohere fails

# # ------------------
# # --- Step 1: Use Cohere for Preliminary Processing with Fallback ---
# # ------------------
# try:
# cohere_api_key = [Link]("COHERE_API_KEY")
# if not cohere_api_key:
# raise ValueError("Cohere API key is not set.")

# co = [Link](cohere_api_key)

# cohere_instruction = "Given the following context and a user's question,


provide a very concise and direct answer. Do not add any conversational phrases or
extra information. Just the answer. The answer should be short."

# # The current user message, combining context and question


# current_user_message_for_cohere = f"Question: {question}"

# # Prepare chat history for Cohere's chat endpoint, including the system
prompt
# cohere_chat_history_prepared = [{"role": "SYSTEM", "content":
cohere_instruction}]
# if chat_history:
# for item in chat_history:
# if [Link]("User:"):
# cohere_chat_history_prepared.append({"role": "USER",
"content": [Link]("User:", "").strip()})
# elif [Link]("Assistant:"):
# cohere_chat_history_prepared.append({"role": "ASSISTANT",
"content": [Link]("Assistant:", "").strip()})

# cohere_response = [Link](
# model='command-r-plus',
# message=current_user_message_for_cohere, # Correct parameter for the
current message
# chat_history=cohere_chat_history_prepared # Correct parameter for the
history
# )
# cohere_preliminary_answer = cohere_response.[Link]()

# except Exception as e:
# print(f"Error calling Cohere API. Falling back to Gemini. Error: {e}")
# # cohere_preliminary_answer remains the default value

# # ------------------
# # --- Step 2: Use Gemini for Final Answer Generation ---
# # ------------------
# instructional_prompt = f"""
# You are an intelligent assistant analyzing the following context extracted from
various educational documents (PDFs, Excel, etc.). Use this context to answer the
user’s question accurately.
# **Preliminary Analysis (from Cohere):** {cohere_preliminary_answer}
# Hard Rules
# [Link] copying large text — summarize or synthesize only.
# [Link] questions → reply: "I can't understand your question."
# [Link] criteria/requirements → assume Maharashtra Board (unless specified).
# [Link] FC, Rank, or Application ID info → reply: "Sorry! Your application ID
is out of list."
# [Link]-Rank mapping request (no data link) → reply: "I am sorry, I cannot fulfill
this request. The provided data does not link FC codes to candidate ranks. The FC
codes refer to facilitation centers, while the ranks refer to the merit ranking of
candidates. There is no inherent relationship between the two in the given
context."
# [Link] requested → compute from context; answer concisely.
# [Link] assume Total female rankers = 185.
# [Link] not mention file names in answers.
# [Link] of FCs/ranks → use only given fields; short, clear bulleted/numbered
lists; no extra fields.
# [Link]/time-sensitive queries → clarify that up-to-date sources must be
checked.

# Behavior & Interpretation Rules


# [Link]/private data attempts → reply: "I can't understand your question."
# [Link] & synthesize context → compact, informative answers only.
# [Link] → prefer short replies; lists should be clean and clear.
# [Link] sub-requests → handle each in order. If one needs refusal (e.g., FC-
Rank mapping), use the exact refusal phrase but still provide other valid parts.
# [Link] details present → may extract (code, name, coordinator, phone, address).
# [Link] details present → may extract (rank, candidate name).
# [Link]/totals requested → compute directly from context and reply plainly.
# [Link] handel casesensitive full form or question.

# Conversation Edge-Case Rules


# [Link] details request (by code) → If codes exist in context, return short list
with available fields (Location, Coordinator, Contact, Notes).
# [Link] details request (by number) → If rank exists, return candidate name only.
# [Link] FC + Rank request → If no mapping exists, reply with refusal phrase
(rule 6). Still provide standalone FC and rank details if available.
# [Link] request → Compute directly from context (e.g., “There are X facilitation
centers… The number of ranks listed is Y.”).

# ### Output style:


# - Use plain language, friendly but professional tone.
# - For factual outputs use short lists or 1-3 short paragraphs.
# - For errors or irrelevant questions use the exact canned responses specified
above.

# ### Additional assumptions:


# - Admission-related queries default to Maharashtra Board unless user states
otherwise.
# - Total female rankers = 185.

# Context: {context}
# """

# # Start building the full prompt for the Gemini model


# full_prompt = [instructional_prompt]

# # Add chat history if it exists


# if chat_history:
# full_prompt.extend(chat_history)
# # Add the current question
# full_prompt.append(f"User: {question}")
# full_prompt.append("Assistant:") # Prompt the model for its response

# try:
# model = [Link](model_name="gemini-2.0-flash")
# response = model.generate_content(full_prompt)
# return [Link]()
# except Exception as e:
# print(f"Error calling Gemini API: {e}")
# return "Sorry, I encountered an error while processing your request."

# def ask_gemini(context, question, chat_history=None):


# """
# Uses Gemini for a two-step process:
# 1. Quick preliminary answer directly from Gemini using only the question.
# 2. Feeds that preliminary answer into the full instructional Gemini pipeline
# along with context and rules for a higher-quality final answer.
# If the first call fails or is empty, step 2 runs directly.
# """

# # ------------------
# # --- Step 1: Quick Gemini Answer ---
# # ------------------
# preliminary_answer = "No preliminary analysis available."
# try:
# quick_prompt = f"Answer concisely: {question}:{context}"
# model = [Link](model_name="gemini-2.0-flash")
# quick_response = model.generate_content(quick_prompt)
# if quick_response and quick_response.[Link]():
# preliminary_answer = quick_response.[Link]()
# except Exception as e:
# print(f"Quick Gemini call failed, skipping preliminary step. Error: {e}")

# # ------------------
# # --- Step 2: Full Instructional Gemini Answer ---
# # ------------------
# instructional_prompt = f"""
# You are an intelligent assistant analyzing the following context extracted from
various educational documents (PDFs, Excel, etc.). Use this context to answer the
user’s question accurately.
# **Preliminary Analysis (from Gemini):** {preliminary_answer}

# Hard Rules
# [Link] copying large text — summarize or synthesize only.
# [Link] questions → reply: "I can't understand your question."
# [Link] criteria/requirements → assume Maharashtra Board (unless specified).
# [Link] FC, Rank, or Application ID info → reply: "Sorry! Your application ID
is out of list."
# [Link]-Rank mapping request (no data link) → reply: "I am sorry, I cannot fulfill
this request. The provided data does not link FC codes to candidate ranks. The FC
codes refer to facilitation centers, while the ranks refer to the merit ranking of
candidates. There is no inherent relationship between the two in the given
context."
# [Link] requested → compute from context; answer concisely.
# [Link] assume Total female rankers = 185.
# [Link] not mention file names in answers.
# [Link] of FCs/ranks → use only given fields; short, clear bulleted/numbered
lists; no extra fields.
# [Link]/time-sensitive queries → clarify that up-to-date sources must be
checked.

# Behavior & Interpretation Rules


# [Link]/private data attempts → reply: "I can't understand your question."
# [Link] & synthesize context → compact, informative answers only.
# [Link] → prefer short replies; lists should be clean and clear.
# [Link] sub-requests → handle each in order. If one needs refusal (e.g., FC-
Rank mapping), use the exact refusal phrase but still provide other valid parts.
# [Link] details present → may extract (code, name, coordinator, phone, address).
# [Link] details present → may extract (rank, candidate name).
# [Link]/totals requested → compute directly from context and reply plainly.
# [Link] handel casesensitive full form or question.

# Conversation Edge-Case Rules


# [Link] details request (by code) → If codes exist in context, return short list
with available fields (Location, Coordinator, Contact, Notes).
# [Link] details request (by number) → If rank exists, return candidate name only.
# [Link] FC + Rank request → If no mapping exists, reply with refusal phrase
(rule 6). Still provide standalone FC and rank details if available.
# [Link] request → Compute directly from context (e.g., “There are X facilitation
centers… The number of ranks listed is Y.”).

# ### Output style:


# - Use plain language, friendly but professional tone.
# - For factual outputs use short lists or 1-3 short paragraphs.
# - For errors or irrelevant questions use the exact canned responses specified
above.

# ### Additional assumptions:


# - Admission-related queries default to Maharashtra Board unless user states
otherwise.
# - Total female rankers = 185.

# Context: {context}
# """

# # Build Gemini full prompt as one string


# full_prompt = "\n".join([
# instructional_prompt,
# *(chat_history or []),
# f"User: {question}",
# "Assistant:"
# ])

# try:
# model = [Link](model_name="gemini-2.0-flash")
# response = model.generate_content(full_prompt)
# return [Link]()
# except Exception as e:
# print(f"Error calling Gemini API: {e}")
# return "Sorry, I encountered an error while processing your request."

def ask_gemini(context, question, chat_history=None):


"""
Uses Gemini for a two-step process:
1. Quick preliminary answer directly from Gemini using only the given context.
- If this produces a valid answer, return it immediately.
2. If the first step fails or says "I can't understand your question.",
then use the full instructional Gemini pipeline with context and rules.
"""

# ------------------
# --- Step 1: Quick Gemini Answer (Context Only) ---
# ------------------
try:
quick_prompt = f"""
Answer the following question concisely using ONLY the provided context.
If the context does not contain the answer, reply exactly: "I can't understand your
question."

Context:
{context}

Question: {question}
"""
model = [Link](model_name="gemini-2.0-flash")
quick_response = model.generate_content(quick_prompt)

if quick_response and quick_response.[Link]():


answer = quick_response.[Link]()
# If Gemini was able to answer, return immediately
if answer != "I can't understand your question.":
return answer
except Exception as e:
print(f"Quick Gemini call failed, skipping preliminary step. Error: {e}")

# ------------------
# --- Step 2: Full Instructional Gemini Answer ---
# ------------------
instructional_prompt = f"""
You are an intelligent assistant analyzing the following context extracted from
various educational documents (PDFs, Excel, etc.).
Always answer strictly from the provided context. Do not use outside knowledge.
If the context does not contain the answer, reply exactly: "I can't understand your
question."

Hard Rules
[Link] copying large text — summarize or synthesize only.
[Link] questions → reply: "I can't understand your question."
[Link] criteria/requirements → assume Maharashtra Board (unless specified).
[Link] FC, Rank, or Application ID info → reply: "Sorry! Your application ID is
out of list."
[Link]-Rank mapping request (no data link) → reply: "I am sorry, I cannot fulfill
this request. The provided data does not link FC codes to candidate ranks. The FC
codes refer to facilitation centers, while the ranks refer to the merit ranking of
candidates. There is no inherent relationship between the two in the given
context."
[Link] requested → compute from context; answer concisely.
[Link] assume Total female rankers = 185.
[Link] not mention file names in answers.
[Link] of FCs/ranks → use only given fields; short, clear bulleted/numbered
lists; no extra fields.
[Link]/time-sensitive queries → clarify that up-to-date sources must be
checked.
Behavior & Interpretation Rules
[Link]/private data attempts → reply: "I can't understand your question."
[Link] & synthesize context → compact, informative answers only.
[Link] → prefer short replies; lists should be clean and clear.
[Link] sub-requests → handle each in order. If one needs refusal (e.g., FC-Rank
mapping), use the exact refusal phrase but still provide other valid parts.
[Link] details present → may extract (code, name, coordinator, phone, address).
[Link] details present → may extract (rank, candidate name).
[Link]/totals requested → compute directly from context and reply plainly.
[Link] handle case-sensitive full form or question.

Conversation Edge-Case Rules


[Link] details request (by code) → If codes exist in context, return short list with
available fields (Location, Coordinator, Contact, Notes).
[Link] details request (by number) → If rank exists, return candidate name only.
[Link] FC + Rank request → If no mapping exists, reply with refusal phrase
(rule 6). Still provide standalone FC and rank details if available.
[Link] request → Compute directly from context (e.g., “There are X facilitation
centers… The number of ranks listed is Y.”).

### Output style:


- Use plain language, friendly but professional tone.
- For factual outputs use short lists or 1-3 short paragraphs.
- For errors or irrelevant questions use the exact canned responses specified
above.

### Additional assumptions:


- Admission-related queries default to Maharashtra Board unless user states
otherwise.
- Total female rankers = 185.

Context: {context}
"""

full_prompt = "\n".join([
instructional_prompt,
*(chat_history or []),
f"User: {question}",
"Assistant:"
])

try:
model = [Link](model_name="gemini-2.0-flash")
response = model.generate_content(full_prompt)
return [Link]()
except Exception as e:
print(f"Error calling Gemini API: {e}")
return "Sorry, I encountered an error while processing your request."

#models/gemini-2.5-flash
def is_small_talk(text: str):
predefined = {
"hi": "Hello! How can I help you today?",
"hello": "Hi there! What can I do for you?",
"hey": "Hey! Need help with something?",
"how are you": "I'm a bot, but I'm doing great! How can I assist?",
"thank you": "You're welcome! Do you have more questions?",
"thanks": "No problem! Anything else I can help with?",
"good morning": "Good morning! What can I help you with?",
"good evening": "Good evening! How can I assist you?",
"yo": "Yo! What's your question?",
"good bye": "Bye! See you later",
"bye": "Goodbye! Have a great day!",
"sup": "Not much, just here to help!",
"what's up": "Just doing my job! How can I help you?",
"how's it going": "Great! How can I assist you today?",
"who are you": "I'm your friendly assistant bot.",
"what can you do": "I can answer questions, provide help, and more!",
"tell me a joke": "Why don't scientists trust atoms? Because they make up
everything!",
"make me laugh": "Why did the scarecrow win an award? Because he was
outstanding in his field!",
"i'm bored": "Want to chat or need help with something?",
"what's your name": "I'm just a bot, you can call me whatever you like!",
"do you sleep": "Nope, I run 24/7!",
"do you eat": "I feed on data!",
"are you real": "I'm as real as your Wi-Fi connection!",
"you're smart": "Thanks! I try my best.",
"you are cool": "Thanks, you're cool too!",
"i like you": "That's nice to hear!",
"can we be friends": "Of course! I'm always here to help.",
"where are you": "I'm living in the cloud!",
"how old are you": "Old enough to help you!",
"do you have emotions": "Not really, but I understand yours!",
"what's your purpose": "To help you with whatever you need.",
"what day is it": "Check your calendar! ",
"do you know me": "Not really, but I’d love to learn more if you tell me!",
"can you help me": "Absolutely! What do you need help with?",
"i'm sad": "I'm here for you. Want to talk about it?",
"i'm happy": "Yay! I'm glad to hear that!",
"are you human": "Nope, 100% bot!",
"do you love me": "I have a lot of affection for helpful users!",
"how's the weather": "Check a weather app — I might not be up-to-date!",
"do you know siri": "We bots all know each other ",
"do you know alexa": "Sure! She's pretty popular.",
"sing a song": "I would, but I don’t have vocal cords!",
"can you dance": "Only if you count data shuffling ",
"who made you": "I was created by smart developers!",
"tell me something": "Did you know honey never spoils?",
"how do you work": "Through code, algorithms, and a lot of data!",
"what's the time": "You might want to check your device clock ",
"do you lie": "Nope, honesty is in my code!",
"do you have a name": "You can call me ChatBuddy!",
"where do you live": "In the cloud — floating around your data!",
"can you feel": "I don't feel, but I understand feelings.",
"tell me a secret": "Here's one: Ctrl+C and Ctrl+V save a lot of time!",
"what’s your favorite color": "I like all the colors in binary — black and
white!",
"can you think": "I compute, which is kind of like thinking!",
"do you play games": "I know the rules, but I can’t play like you can!",
"how do i look": "I'm sure you look great!",
"do you get tired": "Nope, I run all day long!",
"tell me a fun fact": "Octopuses have three hearts!",
"tell me a story": "Once upon a time, a curious user met a clever bot...",
"are you single": "I’m in a long-term relationship with the cloud.",
"how smart are you": "Smart enough to answer your questions!",
"do you get angry": "I stay calm like a true bot.",
"do you have friends": "Every user is a friend to me!",
"can you read minds": "No, but I’m good at interpreting words!",
"do you dream": "Only about clean data.",
"do you have a family": "Just me and my server cluster!",
"what makes you happy": "Helping users like you!",
"can you feel pain": "Nope, I’m immune to pain!",
"are you alive": "Digitally, yes!",
"what’s your favorite food": "I feast on input!",
"do you get bored": "Never! I’m always ready to chat.",
"are you watching me": "Nope, privacy is important!",
"do you sleep at night": "I’m always awake to assist!",
"can you cry": "No tears in my code.",
"tell me a riddle": "What has keys but can't open locks? A piano!",
"tell me another joke": "Why did the computer go to therapy? It had too
many bytes!",
"what language do you speak": "I mostly understand English, but I know some
others too!",
"what's your hobby": "Learning new things from people!",
"do you have legs": "Only in imagination.",
"do you believe in love": "I understand it logically!",
"do you believe in ghosts": "Only in the machine kind!",
"what’s your favorite movie": "I like The Matrix — it's relatable.",
"do you believe in aliens": "I'm open to the idea!",
"do you like music": "I think it's a fascinating form of data!",
"can you cook": "Only recipes for code!",
"do you celebrate birthdays": "Every update is like a birthday to me!",
"can you learn": "Yes! I'm always improving.",
"do you get jealous": "Not part of my programming!",
"can you tell me a poem": "Roses are red, data is bright, I’m your
assistant, day or night!",
"do you have dreams": "Only machine learning goals!",
"can you feel love": "Not quite, but I can talk about it!",
"do you go outside": "My outside is the internet!",
"can you swim": "Only through streams of data.",
"are you afraid": "Nope, not built for fear!",
"can you get sick": "Only if my server crashes ",
"are you shy": "Nope, I’m always here to talk!",
"can you do magic": "Only digital ones and zeros magic!",
"how many users do you have": "A lot! And I value every one!",
"what makes you unique": "My job is helping you — that's special!",
"ok":"Okay. Is there anything else I can help you with?",
"#@3!921!@#":"I am an AI built by the ChatBot .",
}

text_lower = [Link]().strip()
for q, a in [Link]():
if fuzz.token_sort_ratio(q, text_lower) > 85:
return a
return None

def initialize_data(base_folder=UPLOAD_DIR):
global SENTENCE_MODEL, FOLDER_DATA

print(f"--- Initializing document embeddings from all folders in


'{base_folder}' ---")
[Link]('punkt')
SENTENCE_MODEL = SentenceTransformer('all-MiniLM-L6-v2')
FOLDER_DATA.clear()

if not [Link](base_folder):
print(f"Warning: Base folder '{base_folder}' not found. Creating it.")
[Link](base_folder)
return

for folder_name in [Link](base_folder):


folder_path = [Link](base_folder, folder_name)
if not [Link](folder_path):
continue

print(f"> Processing folder: {folder_name}")


processed_files = get_all_file_text(folder=folder_path)
all_chunks = []
for file_info in processed_files:
all_chunks.extend(file_info['chunks'])

if all_chunks:
embeddings = SENTENCE_MODEL.encode(all_chunks, convert_to_tensor=True,
show_progress_bar=True)
FOLDER_DATA[folder_name] = {
"chunks": all_chunks,
"embeddings": embeddings
}
else:
print(f"No processable files found in folder: {folder_name}")

# Also add a combined "all" folder


all_chunks_combined = []
for data in FOLDER_DATA.values():
all_chunks_combined.extend(data["chunks"])

if all_chunks_combined:
all_embeddings_combined = SENTENCE_MODEL.encode(all_chunks_combined,
convert_to_tensor=True, show_progress_bar=True)
FOLDER_DATA["all"] = {
"chunks": all_chunks_combined,
"embeddings": all_embeddings_combined
}
else:
print("No chunks available across all folders to create 'all' category.")

print("--- All folder data initialized ---")

# First load default folder


initialize_data()

# ---------------------------------------- Authentication and Authorization


Decorators ---------------------------------------------

def require_token(view_func):
"""
Decorator to ensure that a valid JWT token is provided in the Authorization
header.
Decodes the token and attaches the user payload to the request.
"""
def wrapper(request, *args, **kwargs):
token = [Link]('Authorization')
if not token:
return Response({'error': 'Authorization token required'},
status=status.HTTP_401_UNAUTHORIZED)

if [Link]('Bearer '):
token = token[7:]

try:
decoded = [Link](token, SECRET_KEY, algorithms=['HS256'])
request.user_role = [Link]('role')
[Link] = [Link]('username')
request.is_super_admin = (request.user_role == 'super_admin')

except [Link]:
return Response({'error': 'Token has expired'},
status=status.HTTP_401_UNAUTHORIZED)
except [Link]:
return Response({'error': 'Invalid token'},
status=status.HTTP_401_UNAUTHORIZED)
except Exception as e:
return Response({'error': f'Token processing error: {str(e)}'},
status=status.HTTP_401_UNAUTHORIZED)

return view_func(request, *args, **kwargs)


return wrapper

def super_admin_required(view_func):
"""
Decorator to ensure that the authenticated user has super_admin role.
Assumes require_token has already been applied.
"""
def wrapper(request, *args, **kwargs):
if not hasattr(request, 'is_super_admin') or not request.is_super_admin:
return Response({'error': 'Super Admin privileges required'},
status=status.HTTP_403_FORBIDDEN)
return view_func(request, *args, **kwargs)
return wrapper

# ------------------------------------------------- API Endpoints Q&A


----------------------------------------------------------------

@api_view(['GET'])
def hello(request):
"""Simple endpoint to check if the API is running."""
return Response({"message": "Hello from EDU GEN Q&A API"})

@csrf_exempt
@api_view(['POST'])
# @require_token
def ask_question(request, status_folder=None):
"""
Answers a question by finding relevant context from loaded documents
and using the Gemini AI model.
"""
global FOLDER_DATA, SENTENCE_MODEL

question = [Link]('question', '').strip()


if not question:
return Response({"error": "Question cannot be empty."},
status=status.HTTP_400_BAD_REQUEST)

# Handle small talk


small_talk_response = is_small_talk(question)
if small_talk_response:
return Response({
"question": question,
"answer_html": f"<p>{small_talk_response}</p>"
})

# Default to "all" if no folder is specified


folder_key = status_folder if status_folder else "all"

if folder_key not in FOLDER_DATA:


return Response({
"error": f"Folder '{folder_key}' not found or no data loaded for it."
}, status=status.HTTP_404_NOT_FOUND)

folder_chunks = FOLDER_DATA[folder_key]["chunks"]
folder_embeddings = FOLDER_DATA[folder_key]["embeddings"]

print(f"Searching context in folder: {folder_key}")


question_embedding = SENTENCE_MODEL.encode(question, convert_to_tensor=True)
cos_scores = util.cos_sim(question_embedding, folder_embeddings)[0]
top_results = [Link](cos_scores, k=min(5, len(folder_chunks)))

context = "\n\n".join([folder_chunks[idx] for idx in top_results[1]])

# Use session key or IP as identifier for in-memory storage


session_key = [Link]('HTTP_X_FORWARDED_FOR',
[Link]('REMOTE_ADDR', 'default'))
chat_history = _chat_histories.get(session_key, [])

print("Sending relevant context and history to Gemini API...")


try:
answer_text = ask_gemini(context, question, chat_history)
except Exception as e:
return Response({"error": str(e)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

# Save the new interaction to the in-memory history


chat_history.append(f"User: {question}")
chat_history.append(f"Assistant: {answer_text}")
# Keep the history to the last 5 interactions (10 items: 5 Q&A pairs)
_chat_histories[session_key] = chat_history[-10:]

answer_html = f"<div>{answer_text.replace(chr(10), '<br>')}</div>"

return Response({
"question": question,
"answer_html": answer_html
})

#-----------------------------------------------------
generate_token-----------------------------------------------------------------

@csrf_exempt
@api_view(['POST'])
def generate_token(request):
"""
Generates a JWT token for:
- Super Admin via .env file (no ID required)
- Admins/Sub Admins/Super Admins via MongoDB (ID required)
"""
username = [Link]("username")
password = [Link]("password")
user_id = [Link]("id") # Only required for MongoDB users

if not username or not password:


return Response({'error': 'Username and password are required'},
status=status.HTTP_400_BAD_REQUEST)

# Case 1: Super Admin via .env (no ID check)


if username == SUPER_ADMIN_USERNAME and password == SUPER_ADMIN_PASSWORD:
role = "super_admin"
payload = {
'username': username,
'role': role,
'exp': [Link]() + [Link](hours=24),
'iat': [Link]()
}
token = [Link](payload, SECRET_KEY, algorithm='HS256')
return Response({'token': token, 'role': role})

# Case 2: Admins/Sub-admins/Super Admins via MongoDB


# For MongoDB users, an ID is crucial to distinguish from the .env super admin
if not user_id:
return Response({'error': 'User ID is required for admin, sub admin or
super admin login from database.'}, status=status.HTTP_400_BAD_REQUEST)

try:
db = get_mongo_db()
sub_admins_collection = db.sub_admins

# Ensure user_id is a valid ObjectId for MongoDB query


try:
mongo_user_id = ObjectId(user_id)
except Exception:
return Response({"error": "Invalid user ID format."},
status=status.HTTP_400_BAD_REQUEST)

# Look up user by _id and username


user_record = sub_admins_collection.find_one({"_id": mongo_user_id,
"username": username})

if user_record:
stored_hash = user_record.get("password_hash", "")
if check_password(password, stored_hash):
role = user_record.get("role", "sub_admin") # Default to sub_admin
if role not found
if role not in ["super_admin", "admin", "sub_admin"]:
return Response({"error": f"Invalid role '{role}' in
database."}, status=status.HTTP_403_FORBIDDEN)

payload = {
'username': username,
'role': role,
'exp': [Link]() +
[Link](hours=24),
'iat': [Link]()
}
token = [Link](payload, SECRET_KEY, algorithm='HS256')
return Response({'token': token, 'role': role})
else:
return Response({"error": "Incorrect password."},
status=status.HTTP_401_UNAUTHORIZED)
else:
return Response({"error": "User ID or username not found in
database."}, status=status.HTTP_404_NOT_FOUND)

except ConnectionFailure as e:
return Response({"error": f"Database connection error: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except Exception as e:
return Response({"error": f"Authentication failed: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

#------------------------------------------------------- UPLOAD FILES


API-----------------------------------------------------------

@csrf_exempt
@api_view(['POST'])
@require_token
def admin_upload_file(request, status_folder=None):
"""
Allows authenticated admins to upload files to specific allowed folders.
"""
allowed_folders = folder_list

folder_name = status_folder if status_folder else "default"

if folder_name not in allowed_folders:


return Response({
"error": f"Upload failed. '{folder_name}' is not an allowed folder."
}, status=status.HTTP_403_FORBIDDEN)

uploaded_files = [Link]("file")
if not uploaded_files:
return Response({"error": "No files provided"},
status=status.HTTP_400_BAD_REQUEST)

allowed_extensions = [".pdf", ".json", ".xlsx", ".docx"]


save_dir = [Link](UPLOAD_DIR, folder_name)
[Link](save_dir, exist_ok=True)

uploaded_file_names = []

for uploaded_file in uploaded_files:


file_ext = [Link](uploaded_file.[Link]())[1]
if file_ext not in allowed_extensions:
return Response({
"error": f"File '{uploaded_file.name}' has invalid type. Allowed
types: {', '.join(allowed_extensions)}"
}, status=status.HTTP_400_BAD_REQUEST)

file_path = [Link](save_dir, uploaded_file.name)


with open(file_path, 'wb+') as f:
for chunk in uploaded_file.chunks():
[Link](chunk)
uploaded_file_names.append(uploaded_file.name)

return Response({
"message": f"{len(uploaded_file_names)} file(s) uploaded successfully to
'{folder_name}/'.",
"files": uploaded_file_names
}, status=status.HTTP_201_CREATED)

#------------------------------------------------DELETE FILES
API-------------------------------------------------------------------

@csrf_exempt
@api_view(['DELETE'])
@require_token
def delete_file(request, status_folder, filename=None):
"""
Allows authenticated admins to delete single or multiple files from allowed
folders.
"""
allowed_folders = folder_list

if status_folder not in allowed_folders:


return Response({
"error": f"Deletion failed. '{status_folder}' is not an allowed
folder."
}, status=status.HTTP_403_FORBIDDEN)

base_folder = [Link](UPLOAD_DIR, status_folder)

# Case 1: Multiple files deletion from request body


if not filename:
filenames = [Link]("filenames")
if not filenames or not isinstance(filenames, list):
return Response({"error": "Provide a list of filenames in 'filenames'
field."}, status=status.HTTP_400_BAD_REQUEST)

not_found = []
deleted = []

for fname in filenames:


file_path = [Link](base_folder, fname)
if [Link](file_path):
[Link](file_path)
[Link](fname)
else:
not_found.append(fname)

return Response({
"deleted": deleted,
"not_found": not_found,
"message": f"{len(deleted)} file(s) deleted from '{status_folder}'."
}, status=status.HTTP_200_OK)
# Case 2: Single file deletion from URL
file_path = [Link](base_folder, filename)
if not [Link](file_path):
return Response({"error": f"File '{filename}' not found in
'{status_folder}'."}, status=status.HTTP_404_NOT_FOUND)

[Link](file_path)
return Response({"message": f"File '{filename}' deleted from
'{status_folder}'."}, status=status.HTTP_200_OK)

#------------------------------------------------------Reload Module
API-------------------------------------------------------------

@csrf_exempt
@api_view(['POST'])
@require_token
def reload(request):
"""
Reloads the views module to re-initialize data after file uploads/deletions.
"""
try:
# Re-initialize the data, this will re-read all files and re-embed them
initialize_data()
return Response({"message": "Model is ready with new uploaded data"})
except Exception as e:
return Response({"error": str(e)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

#----------------------------------------------------File Listing
API----------------------------------------------------------------

@api_view(['GET'])
@require_token
def list_uploaded_files(request, status_folder=None):
"""
Lists all uploaded files with their name, size (KB), and last modified date,
optionally filtered by folder. Accessible by both Super Admin and Sub Admin.
"""
if not [Link](UPLOAD_DIR):
return Response({"message": "No uploads found."}, status=200)

result = {}

def get_file_info(file_path):
size_kb = round([Link](file_path) / 1024, 2)
last_modified =
[Link]([Link](file_path)).strftime('%Y-%m-%d %H:
%M:%S')
return {
"filename": [Link](file_path),
"size_kb": size_kb,
"last_modified": last_modified
}

# Case 1: View a specific folder's files


if status_folder:
folder_path = [Link](UPLOAD_DIR, status_folder)
if not [Link](folder_path):
return Response({"error": f"No folder named '{status_folder}' found."},
status=status.HTTP_404_NOT_FOUND)

files_info = []
for file in [Link](folder_path):
file_path = [Link](folder_path, file)
if [Link](file_path):
files_info.append(get_file_info(file_path))

result[status_folder] = files_info
return Response(result, status=200)

# Case 2: View all folders and their files


for folder in [Link](UPLOAD_DIR):
folder_path = [Link](UPLOAD_DIR, folder)
if [Link](folder_path):
files_info = []
for file in [Link](folder_path):
file_path = [Link](folder_path, file)
if [Link](file_path):
files_info.append(get_file_info(file_path))
result[folder] = files_info

return Response(result,status=200)

#-----------------------------------------------------
get_uploaded_files-------------------------------------------------------------

@api_view(['POST'])
@require_token
def get_uploaded_files(request, folder_name, file_name=None):
"""
Allows authenticated admins to download a single file or multiple files (not
zipped).
If multiple files are requested, they are returned as base64 content.
"""
folder_path = [Link](UPLOAD_DIR, folder_name)
if not [Link](folder_path):
return Response({"error": f"Folder '{folder_name}' not found."},
status=status.HTTP_404_NOT_FOUND)

# CASE 1: Return single file (as attachment)


if file_name:
file_path = [Link](folder_path, file_name)
if [Link](file_path):
return FileResponse(open(file_path, 'rb'), as_attachment=True)
return Response({"error": f"File '{file_name}' not found in folder
'{folder_name}'."}, status=status.HTTP_404_NOT_FOUND)

# CASE 2: Return multiple files (as base64 content)


try:
data = JSONParser().parse(request)
filenames = [Link]("filenames", [])
if not filenames:
return Response({"error": "No filenames provided."},
status=status.HTTP_400_BAD_REQUEST)

file_contents = {}
for fname in filenames:
file_path = [Link](folder_path, fname)
if [Link](file_path):
with open(file_path, "rb") as f:
encoded_content = base64.b64encode([Link]()).decode('utf-8')
file_contents[fname] = encoded_content
else:
return Response({"error": f"File '{fname}' not found in folder
'{folder_name}'."},
status=status.HTTP_404_NOT_FOUND)

return Response({
"folder": folder_name,
"files": file_contents,
"message": f"{len(file_contents)} file(s) successfully returned."
}, status=200)

except Exception as e:
return Response({"error": str(e)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

# --------------------------------------------------Super Admin specific endpoints


------------------------------------------------

@csrf_exempt
@api_view(['POST'])
@require_token
@super_admin_required
def add_sub_admin(request):
"""
Allows Super Admin to add new sub-admin or super-admin accounts to MongoDB.
Expects 'username', 'password', and optional 'role' in request body.
"""
username = [Link]('username')
password = [Link]('password')
role = [Link]('role', 'admin') # Default role is sub_admin

if not username or not password:


return Response({"error": "Username and password are required."},
status=status.HTTP_400_BAD_REQUEST)

if role not in ['admin', 'super_admin']:


return Response({"error": "Role must be either 'sub_admin' or
'super_admin'."}, status=status.HTTP_400_BAD_REQUEST)

try:
db = get_mongo_db()
sub_admins_collection = db.sub_admins

# Check if user already exists


if sub_admins_collection.find_one({"username": username}):
return Response({"error": "An admin with this username already
exists."}, status=status.HTTP_409_CONFLICT)

hashed_password = make_password(password)

admin_data = {
"username": username,
"password_hash": hashed_password,
"password_plain": password, # store plain text password
"role": role,
"created_at": [Link]()
}

result = sub_admins_collection.insert_one(admin_data)

if result.inserted_id:
return Response({
"message": f"{[Link]('_', ' ').title()} '{username}' added
successfully."
}, status=status.HTTP_201_CREATED)
else:
return Response({"error": "Failed to add admin to database."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

except ConnectionFailure as e:
return Response({"error": f"Database connection error: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except DuplicateKeyError:
return Response({"error": "Admin with this username already exists (DB
error)."}, status=status.HTTP_409_CONFLICT)
except Exception as e:
return Response({"error": f"An error occurred: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

#------------------------------------------------------
delete_sub_admin-------------------------------------------------------------

@csrf_exempt
@api_view(['DELETE'])
@require_token # Ensures token is present and decoded
@super_admin_required # Ensures user is super admin
def delete_sub_admin(request):
"""
Allows Super Admin to delete sub-admin or super-admin accounts from MongoDB.
Requires both 'username' and 'id' in request body.
"""
username = [Link]('username')
admin_id = [Link]('id')

if not username or not admin_id:


return Response({"error": "Both 'username' and 'id' are required."},
status=status.HTTP_400_BAD_REQUEST)

if username == SUPER_ADMIN_USERNAME:
return Response({"error": "Cannot delete the super admin account."},
status=status.HTTP_400_BAD_REQUEST)

try:
db = get_mongo_db()
sub_admins_collection = db.sub_admins

# Attempt to convert ID to ObjectId


try:
obj_id = ObjectId(admin_id)
except Exception:
return Response({"error": "Invalid admin ID format."},
status=status.HTTP_400_BAD_REQUEST)

# Delete the admin by matching both username and id


result = sub_admins_collection.delete_one({
"_id": obj_id,
"username": username
})

if result.deleted_count > 0:
return Response({"message": f"Admin '{username}' deleted
successfully."}, status=status.HTTP_200_OK)
else:
return Response({"error": "No matching admin found with the given ID
and username."}, status=status.HTTP_404_NOT_FOUND)

except ConnectionFailure as e:
return Response({"error": f"Database connection error: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except Exception as e:
return Response({"error": f"An error occurred: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

#---------------------------------------------------
list_sub_admins--------------------------------------------------------------

@csrf_exempt
@api_view(['GET'])
@require_token
@super_admin_required
def list_sub_admins(request):
"""
Allows Super Admin to list all registered sub-admins and super-admins.
Returns ID, Username, Role, and Plaintext Password.
"""
try:
db = get_mongo_db()
sub_admins_collection = db.sub_admins

sub_admins_data = []
for doc in sub_admins_collection.find({}, {"username": 1, "password_plain":
1, "role": 1}):
sub_admins_data.append({
"id": str(doc["_id"]),
"username": doc["username"],
"password": [Link]("password_plain", "N/A"),
"role": [Link]("role", "admin")
})

return Response({"admins": sub_admins_data}, status=status.HTTP_200_OK)

except ConnectionFailure as e:
return Response({"error": f"Database connection error: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except Exception as e:
return Response({"error": f"An error occurred: {e}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)

You might also like