Demo data - Quick demo creating - pending few works on this

This commit is contained in:
Vidhya 2026-09-26 00:55:02 +05:30
parent a3fcd51571
commit 0de280b2e7
139 changed files with 453 additions and 11 deletions

View File

@ -224,6 +224,15 @@ class EducationDashboard extends Component {
});
}
openDepartments() {
this.actionService.doAction({
name: "Departments", type: "ir.actions.act_window",
res_model: "education.department",
views: [[false, "list"], [false, "form"]],
target: "current"
});
}
openAddStudent() {
this.actionService.doAction({
name: "New Student", type: "ir.actions.act_window",

View File

@ -61,7 +61,7 @@
<div style="font-size:0.72rem;color:#64748b;font-weight:600;margin-top:2px;">Programs</div>
</div>
</div>
<div class="edu-kpi">
<div class="edu-kpi" t-on-click="openDepartments">
<div class="edu-icon" style="background:#fff7ed;color:#ea580c;"><i class="fa fa-sitemap"/></div>
<div>
<div style="font-size:1.55rem;font-weight:800;color:#0f172a;line-height:1.1;"><t t-esc="state.kpis.dept_count || 0"/></div>

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from . import controllers
def post_init_hook(env):
"""
Hook to run after module installation.
Creates basic demo data if it doesn't exist.
"""
from .demo_data_generator import generate_demo_data
try:
generate_demo_data(env)
except Exception as e:
import logging
logging.getLogger(__name__).error(f"Failed to generate demo data: {e}")

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
{
'name': 'Education Demo Data',
'version': '19.0.1.0.0',
'summary': 'Demo Data & Secure Demo Login for Education ERP',
'author': 'Metatroncube',
'category': 'Education',
'depends': [
'education_base',
'education_student',
'education_lms',
'education_portal',
'education_exam',
'education_timetable'
],
'data': [
'data/demo_users.xml',
],
'demo': [],
'installable': True,
'application': False,
'auto_install': False,
'post_init_hook': 'post_init_hook',
}

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import demo_login

View File

@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
# pyrefly: ignore [missing-import]
from odoo import http, exceptions
# pyrefly: ignore [missing-import]
from odoo.http import request
class EducationDemoController(http.Controller):
@http.route('/demo/login', type='http', auth='public', methods=['POST'], website=True, csrf=False)
def demo_login(self, role=None, **kw):
"""
Secure Demo Login Endpoint.
Accepts only 'role' (admin, faculty, student).
Redirects to safe destinations.
"""
if not role:
raise exceptions.AccessDenied("Missing role parameter.")
# Map allowed roles to specific, pre-configured demo user logins
# Do not allow arbitrary user login
demo_accounts = {
'admin': {
'login': 'demo.admin',
'password': 'Demo@Admin2026',
'redirect': '/web'
},
'faculty': {
'login': 'demo.faculty',
'password': 'Demo@Faculty2026',
'redirect': '/web'
},
'student': {
'login': 'demo.student',
'password': 'Demo@Student2026',
'redirect': '/my/education/dashboard'
}
}
if role not in demo_accounts:
raise exceptions.AccessDenied("Invalid role.")
account = demo_accounts[role]
db = request.db
if not db:
# If no DB is set, force it to the single DB (we can get it if there's only one)
dbs = http.db_list()
if len(dbs) == 1:
db = dbs[0]
else:
return request.redirect('/web/login')
try:
credential = {
'login': account['login'],
'password': account['password'],
'type': 'password'
}
if request.env.cr:
auth_info = request.session.authenticate(request.env, credential)
uid = auth_info.get('uid') if auth_info else request.session.uid
else:
import odoo
with odoo.modules.registry.Registry(db).cursor() as cr:
env = odoo.api.Environment(cr, None, {})
auth_info = request.session.authenticate(env, credential)
request._save_session(env)
request.session.db = db
uid = auth_info.get('uid') if auth_info else request.session.uid
except Exception:
uid = False
if not uid:
# Maybe the demo user doesn't exist yet, or password was changed.
# Redirect to login with error.
return request.redirect('/web/login?error=Demo account not properly configured.')
# Protect against open redirects: Only use our pre-defined safe redirects
return request.redirect(account['redirect'])

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!-- Demo Admin User -->
<record id="demo_user_admin" model="res.users">
<field name="name">Demo Admin</field>
<field name="login">demo.admin</field>
<field name="password">Demo@Admin2026</field>
<field name="email">demo.admin@example.com</field>
</record>
<!-- Demo Faculty User -->
<record id="demo_user_faculty" model="res.users">
<field name="name">Demo Faculty</field>
<field name="login">demo.faculty</field>
<field name="password">Demo@Faculty2026</field>
<field name="email">demo.faculty@example.com</field>
</record>
<!-- Demo Student Portal User -->
<!-- Usually students are portal users -->
<record id="demo_user_student" model="res.users">
<field name="name">Demo Student</field>
<field name="login">demo.student</field>
<field name="password">Demo@Student2026</field>
<field name="email">demo.student@example.com</field>
</record>
</odoo>

View File

@ -0,0 +1,224 @@
# -*- coding: utf-8 -*-
def generate_demo_data(env):
"""
Generate realistic demo data for the Education ERP.
We do this via ORM to avoid complex XML constraint failures.
"""
# 1. Company / Campus / Department
demo_company = env['res.company'].search([('name', '=', 'Metatron Demo Company')], limit=1)
if not demo_company:
demo_company = env['res.company'].create({
'name': 'Metatron Demo Company',
'email': 'demo@metatroncubesolutions.com'
})
# Explicitly ensure the main admin (ID 2) does not have access to this company
main_admin = env['res.users'].browse(2)
if main_admin.exists():
main_admin.sudo().write({'company_ids': [(3, demo_company.id)]})
company = demo_company
campus = env['education.campus'].search([('name', '=', 'Demo Campus')], limit=1)
if not campus:
campus = env['education.campus'].create({
'name': 'Demo Campus',
'code': 'DEMO-CAMPUS',
'company_id': company.id
})
dept = env['education.department'].search([('name', '=', 'Computer Science Demo')], limit=1)
if not dept:
dept = env['education.department'].create({
'name': 'Computer Science Demo',
'code': 'CS-DEMO',
'company_id': company.id,
'campus_id': campus.id
})
# 2. Academic Level & Program
level = env['education.academic.level'].search([('name', '=', 'Undergraduate Demo')], limit=1)
if not level:
level = env['education.academic.level'].create({
'name': 'Undergraduate Demo',
'code': 'UG-DEMO',
'company_id': company.id
})
program = env['education.program'].search([('code', '=', 'BTECH-CS-DEMO')], limit=1)
if not program:
program = env['education.program'].create({
'name': 'B.Tech Computer Science (Demo)',
'code': 'BTECH-CS-DEMO',
'level_id': level.id,
'department_id': dept.id,
'duration': 4,
'duration_unit': 'years'
})
# 3. Academic Year & Term
import datetime
today = datetime.date.today()
year = env['education.academic.year'].search([('name', '=', 'Demo Year 2026-2027')], limit=1)
if not year:
year = env['education.academic.year'].create({
'name': 'Demo Year 2026-2027',
'code': 'DY2627',
'start_date': today,
'end_date': today + datetime.timedelta(days=365),
'company_id': company.id
})
term = env['education.academic.term'].search([('code', '=', 'DT-FALL-26')], limit=1)
if not term:
term = env['education.academic.term'].create({
'name': 'Demo Fall 2026',
'code': 'DT-FALL-26',
'academic_year_id': year.id,
'start_date': today,
'end_date': today + datetime.timedelta(days=120)
})
# 4. Users & Roles
admin_user = env['res.users'].search([('login', '=', 'demo.admin')], limit=1)
faculty_user = env['res.users'].search([('login', '=', 'demo.faculty')], limit=1)
student_user = env['res.users'].search([('login', '=', 'demo.student')], limit=1)
# Assign groups dynamically
erp_manager_group = env.ref('base.group_erp_manager', raise_if_not_found=False)
system_group = env.ref('base.group_system', raise_if_not_found=False)
user_group = env.ref('base.group_user', raise_if_not_found=False)
portal_group = env.ref('base.group_portal', raise_if_not_found=False)
if admin_user:
admin_groups = []
if erp_manager_group: admin_groups.append(erp_manager_group.id)
if system_group: admin_groups.append(system_group.id)
if admin_groups:
admin_user.sudo().write({
'groups_id': [(4, g) for g in admin_groups],
'company_id': company.id,
'company_ids': [(6, 0, [company.id])]
})
if faculty_user and user_group:
faculty_groups = [user_group.id]
# Give basic student read access
student_user_grp = env.ref('education_student.group_education_student_user', raise_if_not_found=False)
if student_user_grp: faculty_groups.append(student_user_grp.id)
# Give LMS Faculty access
lms_faculty_grp = env.ref('education_lms.group_education_lms_faculty', raise_if_not_found=False)
if lms_faculty_grp: faculty_groups.append(lms_faculty_grp.id)
faculty_user.sudo().write({
'groups_id': [(4, g) for g in faculty_groups],
'company_id': company.id,
'company_ids': [(6, 0, [company.id])]
})
if student_user and portal_group:
student_user.sudo().write({
'groups_id': [(4, portal_group.id)],
'company_id': company.id,
'company_ids': [(6, 0, [company.id])]
})
# 5. Create Student Record
if student_user:
student = env['education.student'].search([('partner_id', '=', student_user.partner_id.id)], limit=1)
if not student:
student = env['education.student'].create({
'first_name': 'Demo',
'last_name': 'Student',
'partner_id': student_user.partner_id.id,
'program_id': program.id,
'campus_id': campus.id,
'academic_year_id': year.id,
'status': 'active'
})
# Map student ID to portal user if required by ERP architecture
student_user.partner_id.write({'is_student': True})
# 6. Create LMS Course with Faculty
if faculty_user:
course = env['education.lms.course'].search([('code', '=', 'CS101-DEMO')], limit=1)
if not course:
course = env['education.lms.course'].create({
'name': 'Introduction to Computer Science (Demo)',
'code': 'CS101-DEMO',
'faculty_id': faculty_user.id,
'program_id': program.id,
'state': 'published',
'description': 'A comprehensive demo course.'
})
# Create a section and material
section = env['education.lms.section'].create({
'name': 'Week 1: Basics',
'course_id': course.id,
'sequence': 1
})
env['education.lms.material'].create({
'name': 'Syllabus & Introduction',
'section_id': section.id,
'material_type': 'document',
'content': 'Welcome to the course!'
})
# Create an assignment
env['education.lms.assignment'].create({
'name': 'First Demo Assignment',
'course_id': course.id,
'due_date': today + datetime.timedelta(days=7),
'max_marks': 100,
'state': 'published'
})
# Enroll the demo student
if student_user:
student_record = env['education.student'].search([('partner_id', '=', student_user.partner_id.id)], limit=1)
if student_record:
env['education.lms.enrollment'].create({
'course_id': course.id,
'student_id': student_record.id,
'state': 'enrolled'
})
# 7. Library Demo Data (Optional, if library module exists)
if 'education.library.book' in env:
book = env['education.library.book'].search([('isbn', '=', 'DEMO-ISBN-123')], limit=1)
if not book:
book = env['education.library.book'].create({
'name': 'The Art of Computer Programming (Demo Edition)',
'author': 'Donald Knuth',
'isbn': 'DEMO-ISBN-123',
'total_copies': 5
})
# If student is a library member, create a checkout
if student_user:
student_record = env['education.student'].search([('partner_id', '=', student_user.partner_id.id)], limit=1)
if student_record:
member = env['education.library.member'].search([('student_id', '=', student_record.id)], limit=1)
if not member:
member = env['education.library.member'].create({
'student_id': student_record.id,
'member_type': 'student',
'state': 'active'
})
env['education.library.checkout'].create({
'member_id': member.id,
'book_id': book.id,
'checkout_date': today,
'due_date': today + datetime.timedelta(days=14),
'state': 'issued'
})
env.cr.commit()

View File

@ -0,0 +1 @@
from . import test_demo_login

View File

@ -0,0 +1,32 @@
import odoo.tests
from odoo.tests.common import HttpCase
from unittest.mock import patch
@odoo.tests.tagged('-at_install', 'post_install')
class TestDemoLogin(HttpCase):
def test_01_demo_login_admin(self):
""" Test admin demo login via endpoint """
response = self.url_open('/demo/login', data={'role': 'admin'})
self.assertEqual(response.status_code, 200, "Should redirect or load a page")
def test_02_demo_login_faculty(self):
""" Test faculty demo login """
response = self.url_open('/demo/login', data={'role': 'faculty'})
self.assertEqual(response.status_code, 200, "Should redirect or load a page")
def test_03_demo_login_student(self):
""" Test student demo login """
response = self.url_open('/demo/login', data={'role': 'student'})
self.assertEqual(response.status_code, 200, "Should redirect or load a page")
def test_04_demo_login_invalid_role(self):
""" Test invalid roles get rejected """
response = self.url_open('/demo/login', data={'role': 'superadmin'})
self.assertEqual(response.status_code, 403, "Should be Access Denied")
def test_05_demo_login_missing_role(self):
""" Test missing role gets rejected """
# using GET instead of POST causes a 405 Method Not Allowed, or missing role in POST causes 403
response = self.url_open('/demo/login', data={})
self.assertEqual(response.status_code, 403, "Should be Access Denied")

Some files were not shown because too many files have changed in this diff Show More