first push message

This commit is contained in:
2026-07-01 14:41:49 +07:00
parent 6667dec2bf
commit 58b5f46cc4
2951 changed files with 316619 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# from . import controllers
from . import models
# from . import wizard
+41
View File
@@ -0,0 +1,41 @@
{
'name': "ck_signup",
'summary': "Short (1 phrase/line) summary of the module's purpose",
'description': """
Long description of module's purpose
""",
'author': "My Company",
'website': "https://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
# for the full list
'category': 'Uncategorized',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['base', 'mail', 'portal', 'web', 'website', 'auth_signup', 'register_v15'],
# always loaded
'data': [
'security/ir.model.access.csv',
# 'data/mail_template.xml',
# 'views/signup_form.xml',
# 'views/views.xml',
# 'views/templates.xml',
'views/pin_code_spm.xml',
# 'data/template_mail_amt.xml',
# 'views/reset_password_template.xml',
# 'views/multi_user_creation_wizard.xml',
# 'reports/template_scholarship_code.xml',
# 'reports/report_scholarship_code.xml',
],
# only loaded in demonstration mode
'demo': [
'demo/demo.xml',
],
}
+2
View File
@@ -0,0 +1,2 @@
from . import controllers
# from . import login_controller
+480
View File
@@ -0,0 +1,480 @@
# -*- coding: utf-8 -*-
import logging
import re
import time
from collections import defaultdict
import werkzeug
from odoo import http, _, SUPERUSER_ID, tools, fields
from odoo.exceptions import AccessDenied, UserError, ValidationError
from odoo.http import request, route, SessionExpiredException
from odoo.addons.auth_signup.models.res_users import SignupError
from odoo.addons.auth_signup.controllers.main import AuthSignupHome, ensure_db
_logger = logging.getLogger(__name__)
# ==============================================================================
# SECURITY CONFIGURATION
# ==============================================================================
SECURITY_CONFIG = {
'max_login_attempts': 5,
'lockout_duration_minutes': 15,
'max_pin_attempts': 3,
'pin_lockout_duration_minutes': 30,
'password_min_length': 8,
}
_rate_limit_cache = defaultdict(list)
def _check_rate_limit(key, max_attempts, lockout_minutes):
"""Check if request is rate-limited."""
now = time.time()
window_start = now - (lockout_minutes * 60)
_rate_limit_cache[key] = [t for t in _rate_limit_cache[key] if t > window_start]
if len(_rate_limit_cache[key]) >= max_attempts:
remaining = lockout_minutes * 60 - (now - _rate_limit_cache[key][0])
return True, max(0, int(remaining / 60))
_rate_limit_cache[key].append(now)
return False, 0
def _sanitize_input(value):
"""Basic input sanitization."""
if not value:
return value
if isinstance(value, str):
value = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', value)
return value[:2000] if len(value) > 2000 else value
return value
def _validate_password_strength(password):
"""Validate password meets security requirements."""
errors = []
if len(password) < SECURITY_CONFIG['password_min_length']:
errors.append(_('ពាក្យសម្ងាត់ត្រូវមានយ៉ាងតិច %d តួអក្សរ') % SECURITY_CONFIG['password_min_length'])
if not re.search(r'[A-Z]', password):
errors.append(_('ពាក្យសម្ងាត់ត្រូវមានអក្សរធំយ៉ាងតិច ១'))
if not re.search(r'[a-z]', password):
errors.append(_('ពាក្យសម្ងាត់ត្រូវមានអក្សរតូចយ៉ាងតិច ១'))
if not re.search(r'\d', password):
errors.append(_('ពាក្យសម្ងាត់ត្រូវមានលេខយ៉ាងតិច ១'))
return errors
# ==============================================================================
# CONTROLLER CLASS - ODOO 19 COMPATIBLE
# ==============================================================================
class SignupVerifyEmail(AuthSignupHome):
"""High-security signup/login controller for Odoo 19"""
@route('/web/login', type='http', auth='public', website=True)
def web_login(self, *args, **kw):
ensure_db()
client_ip = request.httprequest.remote_addr
login_attempt = kw.get('login', '').lower().strip()
rate_key = f"login:{client_ip}:{login_attempt}"
is_locked, remaining_min = _check_rate_limit(
rate_key,
SECURITY_CONFIG['max_login_attempts'],
SECURITY_CONFIG['lockout_duration_minutes']
)
if is_locked:
kw['error'] = _("សូមរង់ចាំ %d នាទីមុនពេលសាកល្បងម្តងទៀត (ការពារសុវត្ថិភាព)") % remaining_min
return request.render('web.login', self._prepare_login_context(kw))
# 🔐 Handle POST authentication - ODOO 19 FIX
if request.httprequest.method == 'POST' and login_attempt:
try:
# ✅ Odoo 19: authenticate() takes ONLY (login, password) - returns bool
auth_success = request.session.authenticate(
login_attempt,
kw.get('password', '')
)
if auth_success:
# ✅ Successful auth: regenerate session
if hasattr(request.session, 'rotate_session_token'):
request.session.rotate_session_token()
user = request.env['res.users'].sudo().search(
[('login', '=', login_attempt)], limit=1
)
# First login flow
if user and user.first_login:
qcontext = self.get_auth_signup_qcontext()
qcontext['user_id'] = user.id
return request.render('ck_signup.reset_password_direct', qcontext)
# Handle redirect safely
redirect = kw.get('redirect') or request.params.get('redirect')
if redirect and self._is_safe_redirect(redirect):
return request.redirect(redirect)
return request.redirect('/web')
except AccessDenied:
_logger.warning("Failed login attempt for: %s from %s", login_attempt, client_ip)
kw['error'] = _("លេខកូដ ឬ ពាក្យសម្ងាត់ មិនត្រឹមត្រូវទេ")
except Exception as e:
_logger.exception("Login error for %s: %s", login_attempt, str(e))
kw['error'] = _("មានបញ្ហាក្នុងការចូលប្រើប្រាស់")
# Render login page
response = super().web_login(*args, **kw)
if hasattr(response, 'qcontext'):
response.qcontext.update(self.get_auth_signup_config())
return response
def _prepare_login_context(self, kw):
"""Prepare secure context for login template - ODOO 19 DEBUG FIX."""
ctx = self.get_auth_signup_qcontext()
# ✅ Odoo 19: debug must be string for template 'in' check
debug_val = request.env.context.get('debug')
ctx.update({
'redirect': kw.get('redirect'),
'error': kw.get('error'),
'message': kw.get('message'),
'debug': 'assets' if debug_val else '', # ✅ String, not bool!
'login': _sanitize_input(kw.get('login', '')),
})
return ctx
def _is_safe_redirect(self, url):
"""Prevent open redirect attacks."""
if not url:
return False
if url.startswith(('/', 'http://localhost', 'https://localhost')):
return True
base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url')
if base_url and url.startswith(base_url):
return True
_logger.warning("Blocked unsafe redirect: %s", url)
return False
# ==============================================================================
# PASSWORD CHANGE - ODOO 19 COMPATIBLE
# ==============================================================================
@route('/web/reset_password/submit', type='http', methods=['POST'],
auth='public', website=True, csrf=True)
def change_password(self, **kw):
values = {}
try:
required = ['user_name', 'old_password', 'new_password', 'confirm_new_password', 'name']
if not all(kw.get(f) for f in required):
values['error'] = _("សូមបំពេញព័ត៌មានទាំងអស់")
return request.render('ck_signup.reset_password_direct', values)
if kw['new_password'] != kw['confirm_new_password']:
values['error'] = _("ពាក្យសម្ងាត់មិនត្រូវគ្នា! សូមបញ្ចូលវាម្តងទៀត")
return request.render('ck_signup.reset_password_direct', values)
strength_errors = _validate_password_strength(kw['new_password'])
if strength_errors:
values['error'] = '; '.join(strength_errors)
return request.render('ck_signup.reset_password_direct', values)
# ✅ Odoo 19: authenticate returns bool
try:
auth_success = request.session.authenticate(
_sanitize_input(kw['user_name']),
kw['old_password']
)
except AccessDenied:
auth_success = False
if not auth_success:
_logger.warning("Password change auth failed for: %s", kw.get('user_name'))
values['error'] = _("លេខកូដ ឬ ពាក្យសម្ងាត់ចាស់ មិនត្រឹមត្រូវទេ")
return request.render('ck_signup.reset_password_direct', values)
# Get current user AFTER successful auth
user = request.env.user
if user.has_group('base.group_public'):
values['error'] = _("អ្នកប្រើប្រាស់សាធារណៈមិនអាចផ្លាស់ប្តូរពាក្យសម្ងាត់បានទេ")
return request.render('ck_signup.reset_password_direct', values)
# ✅ Secure password update via ORM
user.sudo().write({
'name': _sanitize_input(kw['name']),
'password': kw['new_password'],
'first_login': False,
})
_logger.info("Password changed for user: %s", user.login)
success_msg = werkzeug.urls.url_quote(_('ការផ្លាស់ប្តូរពាក្យសម្ងាត់ទទួលបានជោគជ័យ'))
return request.redirect(f'/web/login?message={success_msg}')
except Exception as e:
_logger.exception("Password change error: %s", str(e))
values['error'] = _("មានបញ្ហាក្នុងការផ្លាស់ប្តូរពាក្យសម្ងាត់")
return request.render('ck_signup.reset_password_direct', values)
# ==============================================================================
# SIGNUP - ODOO 19 COMPATIBLE
# ==============================================================================
# @route('/web/signup', type='http', auth='public', website=True)
# def web_auth_signup(self, *args, **kw):
# """Secure signup flow - Odoo 19 compatible."""
# param_obj = request.env['ir.config_parameter'].sudo()
# qcontext = self.get_auth_signup_qcontext()
#
# request.params['background_src'] = _sanitize_input(
# param_obj.get_param('login_form_header_register') or ''
# )
# request.params['background'] = _sanitize_input(
# param_obj.get_param('id_backgroud_mptc') or ''
# )
#
# companies = request.env["res.groups"].sudo().search(
# [('name', '=', 'អ្នកសុំអាហារូបករណ៍')]
# )
# qcontext['groups'] = companies
#
# if not qcontext.get('token') and not qcontext.get('signup_enabled'):
# raise werkzeug.exceptions.NotFound()
#
# if 'error' not in qcontext and request.httprequest.method == 'POST':
# if kw.get('password') != kw.get('confirm_password'):
# qcontext['error'] = _("ពាក្យសម្ងាត់មិនត្រូវគ្នា! សូមបញ្ចូលវាម្តងទៀត")
# return request.render('ck_signup.signup', qcontext)
#
# if kw.get('password'):
# strength_errors = _validate_password_strength(kw['password'])
# if strength_errors:
# qcontext['error'] = '; '.join(strength_errors)
# return request.render('ck_signup.signup', qcontext)
#
# login = _sanitize_input(qcontext.get('login', '')).lower()
#
# existing_user = request.env["res.users"].sudo().search(
# [("login", "=", login), ("active", "=", True)], limit=1
# )
# if existing_user:
# qcontext['error'] = _("អ៊ីម៉ែលនេះត្រូវបានចុះឈ្មោះរួចហើយ")
# return request.render('ck_signup.signup', qcontext)
#
# try:
# self.do_signup(qcontext)
#
# if login:
# request.session['signup_login'] = login
# user_sudo = request.env['res.users'].sudo().search([('login', '=', login)], limit=1)
#
# if user_sudo:
# country_id = kw.get('country')
# if country_id:
# try:
# country = request.env['res.country'].sudo().browse(int(country_id))
# if country.exists():
# user_sudo.partner_id.sudo().write({'country_id': country.id})
# except (ValueError, TypeError):
# pass
# user_sudo.sudo().log_ids.unlink()
#
# qcontext['message'] = _("សូមបញ្ចូល Pin Code ដែលបានផ្ញើទៅក្នុងអ៊ីម៉ែលរបស់អ្នក ដើម្បីផ្ទៀងផ្ទាត់គណនី!")
# return request.render('ck_signup.verify_pin_code', qcontext)
#
# except (SignupError, ValidationError, AssertionError) as e:
# _logger.error("Signup error for %s: %s", login, str(e))
#
# pending_user = request.env["res.users"].sudo().search(
# [("login", "=", login), ("active", "=", False)], limit=1
# )
#
# if pending_user:
# qcontext['message'] = _(
# "សូមបញ្ចូល Pin Code ដែលបានផ្ញើទៅក្នុងអ៊ីម៉ែលរបស់អ្នក ដើម្បីផ្ទៀងផ្ទាត់គណនី!")
# qcontext['error'] = ""
# return request.render('ck_signup.verify_pin_code', qcontext)
# else:
# qcontext['error'] = _("មិនអាចបង្កើតគណនីថ្មីបានទេ")
#
# return request.render('ck_signup.signup', qcontext)
# ==============================================================================
# PIN CODE VERIFICATION - ODOO 19
# ==============================================================================
@route('/web/verify_pin_code', type='http', auth='public', website=True, csrf=True)
def verify_pin_code(self, *args, **kw):
qcontext = self.get_auth_signup_qcontext()
login = _sanitize_input(kw.get('login', '')).lower().strip()
pin_code = _sanitize_input(kw.get('pin_code', '')).strip()
if not login or not pin_code:
qcontext['error'] = _("សូមបញ្ចូលអ៊ីម៉ែល និង PIN Code")
return request.render('ck_signup.verify_pin_code', qcontext)
rate_key = f"pin:{login}"
is_locked, remaining_min = _check_rate_limit(
rate_key,
SECURITY_CONFIG['max_pin_attempts'],
SECURITY_CONFIG['pin_lockout_duration_minutes']
)
if is_locked:
qcontext['error'] = _("សូមរង់ចាំ %d នាទី (ការពារសុវត្ថិភាព)") % remaining_min
return request.render('ck_signup.verify_pin_code', qcontext)
# ✅ ORM only - NO RAW SQL
user = request.env["res.users"].sudo().search(
[("login", "=", login),
("pin_code", "=", pin_code),
("active", "=", False)],
limit=1
)
if user:
try:
user.sudo().write({
'active': True,
'pin_code': False,
})
if user.partner_id:
user.partner_id.sudo().write({'active': True})
companies = request.env["res.groups"].sudo().search(
[('name', '=', 'អ្នកសុំអាហារូបករណ៍')]
)
if companies:
user.sudo().write({'groups_id': [(4, g.id) for g in companies]})
_logger.info("Account verified: %s", login)
qcontext['message'] = _("អបអរសាទរ! សូមចុចប៊ូតុង \"ចូល\" ដើម្បីបំពេញពាក្យចុះឈ្មោះ!")
return request.render('web.login', qcontext)
except Exception as e:
_logger.exception("Error activating user %s: %s", login, str(e))
qcontext['error'] = _("មានបញ្ហាក្នុងការផ្ទៀងផ្ទាត់")
else:
_logger.warning("Failed PIN verification: %s", login)
qcontext['error'] = _("PIN Code ឬ អ៊ីម៉ែល មិនត្រឹមត្រូវទេ")
return request.render('ck_signup.verify_pin_code', qcontext)
# ==============================================================================
# PIN REQUEST
# ==============================================================================
@route('/web/request_new_pin_code', type='http', auth='public', website=True, csrf=True)
def sending_new_pin_code(self, *args, **kw):
qcontext = self.get_auth_signup_qcontext()
login = _sanitize_input(kw.get('login', '')).lower().strip()
if not login:
qcontext['error'] = _("សូមបញ្ចូលអ៊ីម៉ែល")
return request.render('ck_signup.view_request_new_pin_code', qcontext)
active_user = request.env["res.users"].sudo().search(
[("login", "=", login), ("active", "=", True)], limit=1
)
if active_user:
qcontext['error'] = _("អ៊ីម៉ែលនេះបានចុះឈ្មោះរួចហើយ សូមចូលប្រើប្រាស់")
return request.render('ck_signup.view_request_new_pin_code', qcontext)
pending_user = request.env["res.users"].sudo().search(
[("login", "=", login), ("active", "=", False)], limit=1
)
if pending_user:
request_count = pending_user.count_request_pin_code or 0
if request_count > 5:
qcontext['message'] = _("សូមអភ័យទោស អ្នកមិនអាចស្នើសុំ PIN Code បានទៀតទេ!")
return request.render('ck_signup.verify_pin_code', qcontext)
try:
result = request.env['res.users'].sudo().generate_and_send_new_pin_code(login)
if result:
qcontext['message'] = _("PIN Code បានផ្ញើចូលអ៊ីម៉ែលរបស់លោកអ្នកដោយជោគជ័យ!")
return request.render('ck_signup.view_request_new_pin_code', qcontext)
except Exception as e:
_logger.error("Error sending PIN to %s: %s", login, str(e))
qcontext['error'] = _("មានបញ្ហាក្នុងការផ្ញើ PIN Code")
else:
qcontext['error'] = _("អ៊ីម៉ែលមិនត្រឹមត្រូវ ឬ មិនមានក្នុងប្រព័ន្ធ")
return request.render('ck_signup.view_request_new_pin_code', qcontext)
# ==============================================================================
# PASSWORD RESET
# ==============================================================================
@route('/web/reset_password', type='http', auth='public', website=True)
def web_auth_reset_password(self, *args, **kw):
qcontext = self.get_auth_signup_qcontext()
if not qcontext.get('token') and not qcontext.get('reset_password_enabled'):
raise werkzeug.exceptions.NotFound()
if 'error' not in qcontext and request.httprequest.method == 'POST':
try:
if qcontext.get('token'):
self.do_signup(qcontext)
return self.web_login(*args, **kw)
else:
login = _sanitize_input(qcontext.get('login', '')).lower()
if not login:
raise UserError(_("សូមបញ្ចូលអ៊ីម៉ែល"))
_logger.info("Password reset requested: %s from %s", login, request.httprequest.remote_addr)
request.env['res.users'].sudo().reset_password(login)
qcontext['message'] = _("សារប្ដូរលេខសំងាត់ត្រូវបានផ្ញើចូលអ៊ីម៉ែលរបស់លោកអ្នក")
except SignupError:
users = request.env['res.users'].sudo().search(
[('login', '=', _sanitize_input(kw.get('login', '')).lower()),
('active', '=', True)], limit=1
)
if not users:
qcontext['message'] = _("សូមពិនិត្យ Pin Code នៅក្នុងអ៊ីម៉ែលរបស់អ្នក")
qcontext['error'] = ""
return request.render('ck_signup.verify_pin_code', qcontext)
else:
qcontext['message'] = _("ពាក្យសម្ងាត់ត្រូវបានផ្លាស់ប្តូរជោគជ័យ")
except Exception as e:
_logger.exception("Password reset error: %s", str(e))
qcontext['error'] = _("មានបញ្ហាក្នុងការប្ដូរពាក្យសម្ងាត់")
return request.render('auth_signup.reset_password', qcontext)
# ==============================================================================
# HELPERS
# ==============================================================================
def do_signup(self, qcontext):
values = {
key: _sanitize_input(qcontext.get(key))
for key in ('login', 'name', 'password')
if qcontext.get(key)
}
if not values.get('login') or not values.get('password'):
raise UserError(_("សូមបំពេញព័ត៌មានដែលត្រូវការ"))
strength_errors = _validate_password_strength(values['password'])
if strength_errors:
raise ValidationError('; '.join(strength_errors))
group_id = qcontext.get('group')
if group_id:
values['groups'] = str(group_id)
supported_lang_codes = [code for code, _ in request.env['res.lang'].sudo().get_installed()]
lang = request.context.get('lang', '').split('_')[0]
if lang in supported_lang_codes:
values['lang'] = lang
self._signup_with_values(qcontext.get("token"), values)
def _signup_with_values(self, token, values):
db, login, password = request.env['res.users'].sudo().signup(values, token)
_logger.info("New user created: %s", login)
+59
View File
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
import logging
from odoo import http
from odoo.http import request
from odoo.addons.web.controllers.home import Home as WebHome
_logger = logging.getLogger(__name__)
class CustomWebLogin(WebHome):
@http.route('/web/login', type='http', auth="none", sitemap=False)
def web_login(self, redirect=None, **kw):
# 1️⃣ Let parent handle GET requests & render login form
response = super().web_login(redirect=redirect, **kw)
# 2️⃣ Handle POST submission
if request.httprequest.method == 'POST':
login = request.params.get('login', '').strip()
password = request.params.get('password', '').strip()
pin_code_input = request.params.get('pin_code', '').strip() # Match your XML field name
if login and password:
# ⚠️ CRITICAL: Use 'odoo_env' NOT 'env' to avoid shadowing request.env
odoo_env = request.env
user = odoo_env['res.users'].sudo().search([('login', '=', login)], limit=1)
if not user:
response.qcontext['error'] = "ឈ្មោះអ្នកប្រើប្រាស់ ឬពាក្យសម្ងាត់មិនត្រឹមត្រូវ។"
return response
# 3️⃣ Handle Inactive Users (PIN Verification)
if not user.active:
if not pin_code_input:
response.qcontext['error'] = "សូមបញ្ចូលលេខកូដ PIN ដើម្បីដំណើរការគណនី។"
return response
if user.pin_code and pin_code_input == user.pin_code:
user.sudo().write({
'active': True,
'pin_code': False, # Clear used PIN
'first_login': True,
})
else:
response.qcontext['error'] = "លេខកូដ PIN មិនត្រឹមត្រូវ។"
return response
# 4️⃣ Authenticate (EXACTLY 3 arguments for Odoo 19)
try:
request.session.authenticate(
request.session.db, # Database name
login, # Login
password # Password
)
# Success: Redirect to dashboard
return request.redirect(redirect or '/web')
except Exception as e:
_logger.warning(f"Login failed for {login}: {e}")
response.qcontext['error'] = "ឈ្មោះអ្នកប្រើប្រាស់ ឬពាក្យសម្ងាត់មិនត្រឹមត្រូវ។"
return response
return response
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!-- Email template for reset password -->
<!-- <record id="ck_reset_password_email" model="mail.template">-->
<!-- <field name="name">Auth Signup: Reset Password</field>-->
<!-- <field name="model_id" ref="base.model_res_users"/>-->
<!-- <field name="email_from">{{ (object.company_id.email_formatted or user.email_formatted) }}></field>-->
<!-- <field name="email_to">{{ object.email_formatted }}</field>-->
<!-- <field name="subject">Password reset</field>-->
<!-- <field name="auto_delete" eval="True"/>-->
<!-- <field name="body_html"><![CDATA[-->
<!-- <div style="padding:0px;width:600px;margin:auto;background: #FFFFFF repeat top /100%;color:#777777">-->
<!-- <h3>ផ្លាស់ប្ដូរលេខសំងាត់</h3>-->
<!-- </div>-->
<!-- <div style="padding:0px;width:600px;margin:auto;background: #FFFFFF repeat top /100%;color:#777777">-->
<!-- <p>សួស្ដី ${object.name},</p>-->
<!-- <p>ដើម្បីប្ដូរលេខសំងាត់របស់អ្នក សូមចុចលើប៊ូតុងខាងក្រោម:</p>-->
<!-- <div style="text-align: center; margin-top: 16px;">-->
<!-- <a href="${object.signup_url}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">Change password</a>-->
<!-- </div>-->
<!-- <p><b>ចំនាំ:</b> លោកអ្នកអាចផ្លាស់ប្ដូរលេខសំងាត់បានក្នុងរយះពេល២៤ម៉ោង គិតចាប់ពីពេលលោកអ្នកស្នើសុំតែប៉ុណ្ណេាះ!</p>-->
<!-- <p>សូមអរគុណ</p>-->
<!-- </div>-->
<!-- ]]></field>-->
<!-- </record>-->
<!-- Email template for new users -->
<record id="ck_set_password_email" model="mail.template">
<field name="name">Auth Signup: Odoo Connection</field>
<field name="model_id" ref="base.model_res_users"/>
<field name="email_from"><![CDATA["${object.company_id.name|safe}" <${(object.company_id.email or user.email)|safe}>]]></field>
<field name="email_to">${object.email|safe}</field>
<field name="subject"><![CDATA[${object.company_id.name}]]></field>
<field name="body_html"><![CDATA[
<div style="padding:0px;width:600px;margin:auto;background: #FFFFFF repeat top /100%;color:#777777">
<p>សួស្ដី ${object.name},</p>
<p>អបអរសាទរ លោកអ្នកត្រូវបានចុះឈ្មេាះជាសមាជិកដោយជោគជ័យ</p>
<p>ដើម្បីចូលទៅកាន់គណនីរបស់លោកអ្នក សូមចុចប៊ូតុងខាងក្រោម</p>
<div style="text-align: center; margin-top: 16px;">
<a href="${object.signup_url}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">Accept invitation to "${object.company_id.name}"</a>
</div>
<p>សូមអរគុណ</p>
</div>
]]>
</field>
</record>
</data>
</odoo>
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="spm_code" model="ir.sequence">
<field name="name">Sequence Code Spm</field>
<field name="code">pin.spm</field>
<field name="prefix">SHS%(min)s</field>
<field name="padding">6</field>
<field name="company_id" eval="False"/>
</record>
<!-- In your module's data XML -->
<record id="security_login_attempts" model="ir.config_parameter">
<field name="key">auth.max_login_attempts</field>
<field name="value">5</field>
</record>
<record id="security_session_timeout" model="ir.config_parameter">
<field name="key">auth.session_timeout_minutes</field>
<field name="value">30</field>
</record>
</data>
</odoo>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record id="sending_pin_code_template" model="mail.template">
<field name="name">Pin Code</field>
<field name="email_from">${object}</field>
<field name="subject">Pin Code ដើម្បីផ្ទៀងផ្ទាត់គណនីចុះឈ្មោះចូលក្នុងប្រព័ន្ធចុះស្នើសុំអាហារូបករណ៍</field>
<field name="email_to">${object}</field>
<field name="model_id" ref="base.model_res_users"/>
<field name="auto_delete" eval="True"/>
<field name="body_html">
</field>
</record>
</data>
</odoo>
+30
View File
@@ -0,0 +1,30 @@
<odoo>
<data>
<!--
<record id="object0" model="ck_signup.ck_signup">
<field name="name">Object 0</field>
<field name="value">0</field>
</record>
<record id="object1" model="ck_signup.ck_signup">
<field name="name">Object 1</field>
<field name="value">10</field>
</record>
<record id="object2" model="ck_signup.ck_signup">
<field name="name">Object 2</field>
<field name="value">20</field>
</record>
<record id="object3" model="ck_signup.ck_signup">
<field name="name">Object 3</field>
<field name="value">30</field>
</record>
<record id="object4" model="ck_signup.ck_signup">
<field name="name">Object 4</field>
<field name="value">40</field>
</record>
-->
</data>
</odoo>
+1
View File
@@ -0,0 +1 @@
from . import models
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
from odoo import api, fields, models, _
class SignupError(Exception):
pass
class ck_res_parter(models.Model):
_name = "res.partner"
_inherit = "res.partner"
_description = "Partner"
@api.model
def _signup_retrieve_partner(self, token, check_validity=False, raise_exception=False):
if self.search(['&', '&', ('signup_token', '=', token), ('signup_expiration', '=', False), ('active', '=', False)]):
partner = self.search(['&', ('signup_token', '=', token), ('active', '=', False)], limit=1)
else:
partner = self.search([('signup_token', '=', token)], limit=1)
if not partner:
if raise_exception:
raise SignupError("Signup token '%s' is not valid" % token)
return False
if check_validity and not partner.signup_valid:
if raise_exception:
raise SignupError("Signup token '%s' is no longer valid" % token)
return False
return partner
+12
View File
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from ast import literal_eval
from odoo import api, fields, models, _
class CkSetting(models.TransientModel):
_inherit = 'res.config.settings'
intern = fields.Boolean(string="Enable Intern")
exam = fields.Boolean(string="Enable Exam")
+323
View File
@@ -0,0 +1,323 @@
# -*- coding: utf-8 -*-
import logging
import random
import base64
from io import BytesIO
from odoo import api, fields, models, _, tools
from odoo.exceptions import UserError, AccessError
from odoo.addons.base.models.ir_mail_server import MailDeliveryException
from odoo.addons.auth_signup.models.res_partner import SignupError
_logger = logging.getLogger(__name__)
try:
import qrcode
from qrcode.constants import ERROR_CORRECT_L
except ImportError:
qrcode = None
ERROR_CORRECT_L = None
class MainPin(models.Model):
_name = 'main.pin'
_description = 'SHS Code'
_rec_name = 'custom_group_label'
_inherit = ['mail.thread', 'mail.activity.mixin']
data_term = fields.Integer(string="ចំនួនប្រតិបត្ដិការ", default=1)
name = fields.Char(string="ផ្សេងៗ")
pin_code_id = fields.One2many('pin.spm', 'main_id', string="តារាងលេខកូដ", readonly=True)
reviewer_code = fields.Many2one('res.users', string="អ្នកទទួលលេខកូដ", domain="[('active', '=', True)]")
groups_id = fields.Many2many('res.groups', string="Access Rights")
custom_group_label = fields.Char(string="Group Label")
link_qr = fields.Binary("QR Link", compute='_generate_qrcode', store=False)
file_name = fields.Char(string="ឈ្មោះឯកសារយោង")
files = fields.Binary(string="ឯកសារយោង")
check_con = fields.Boolean(string="បន្ដការសិក្សាថ្នាក់ឧត្ដមសិក្សា")
selection_data = fields.Selection([
('0', 'ថ្នាក់មហាវិទ្យាល័យ'),
('01', 'ថ្នាក់មត្ដេយ្យ, មធ្យម និងមធ្យមសិក្សាទុតិយភូមិ'),
('1', 'សិស្សបន្ដការសិក្សា(មកពីប្រទេសថៃ)')
], string="កម្រិតអាហារូបករណ៍", default='0')
school_kind = fields.Many2one('univer.univer', string="សាលាដែលទទួលបាន",
domain="[('grade_study','in',('01','1','2','3','4'))]")
pass_percent = fields.Float(string="%")
requester = fields.Char(string="គោលដៅ")
# ✅ FIXED: Removed 'id' dependency. Empty depends() means "compute on access"
@api.depends()
def _generate_qrcode(self):
login_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url',
'https://reg.amt.live') + '/web/login'
for record in self:
record.link_qr = False
if not (qrcode and ERROR_CORRECT_L):
continue
try:
qr = qrcode.QRCode(version=1, error_correction=ERROR_CORRECT_L, box_size=4, border=2)
qr.add_data(login_url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buffered = BytesIO()
img.save(buffered, format="PNG")
record.link_qr = base64.b64encode(buffered.getvalue())
except Exception as e:
_logger.error(f"QR generation failed for {record.id}: {e}")
@api.onchange('data_term')
def _onchange_data_term(self):
default_group = self.env.ref("register_v15.groups_amt", raise_if_not_found=False)
if default_group:
self.groups_id = [(6, 0, [default_group.id])]
self.custom_group_label = 'ផ្គូផ្គង'
@api.constrains('data_term')
def _check_data_term(self):
for rec in self:
if rec.data_term < 1:
raise UserError(_("ចំនួនប្រតិបត្ដិការត្រូវតែយ៉ាងតិច 1"))
if rec.data_term > 1000:
raise UserError(_("ចំនួនប្រតិបត្ដិការអតិបរមាគឺ 1000"))
def compute_code(self):
self.ensure_one()
if not self.data_term or self.data_term < 1:
raise UserError(_("សូមបញ្ចូលចំនួនប្រតិបត្ដិការ"))
target_group = self.env.ref("register_v15.groups_amt", raise_if_not_found=False)
pin_records = []
created_users = []
failed_count = 0
for i in range(1, self.data_term + 1):
pin_code = str(random.randint(100000, 999999))
identifier = f"{self.name or 'PIN'}{pin_code}{i:03d}"
valid_email = f"{identifier.lower()}@reg.amt.live"
pin_records.append((0, 0, {
'name': identifier,
'data_rec': self.name or '',
'main_id': self.id,
}))
user_vals = {
'name': identifier,
'login': identifier,
'email': valid_email,
'password': identifier,
'active': True,
'first_login': True,
'kind': self.selection_data,
'pin_code': pin_code,
'groups': self.custom_group_label or 'ប្រឡង',
}
# ✅ Use standard Odoo field name: groups_id
if target_group:
user_vals['group_ids'] = [(4, target_group.id)]
elif self.groups_id:
user_vals['group_ids'] = [(6, 0, self.groups_id.ids)]
try:
# 🔒 Prevent password reset emails from triggering
user = self.env['res.users'].sudo().with_context(no_reset_password=True).create(user_vals)
# 📝 Optional: Log for verification
_logger.info(f"✅ Created: {user.login} | Password={pin_code} | Groups={user.group_ids.mapped('name')}")
created_users.append(user)
except Exception as e:
failed_count += 1
_logger.error(f"❌ Failed to create '{identifier}': {e}", exc_info=True)
continue
if pin_records:
self.pin_code_id = pin_records
if failed_count > 0:
return {
'type': 'ir.actions.client', 'tag': 'display_notification',
'params': {
'title': _('បញ្ហា!'),
'message': _('បានបង្កើត %d, បរាជ័យ %d។ ពិនិត្យ log!') % (len(created_users), failed_count),
'type': 'warning', 'sticky': True,
}
}
return {
'type': 'ir.actions.client', 'tag': 'display_notification',
'params': {
'title': _('ជោគជ័យ!'),
'message': _('បានបង្កើត %d លេខកូដ និងគណនីដោយជោគជ័យ') % len(created_users),
'type': 'success', 'sticky': False,
}
}
def get_spm_code(self):
target_group = self.env.ref("register_v15.groups_amt", raise_if_not_found=False)
for code in self.pin_code_id.filtered(lambda c: not c.status):
user = self.env['res.users'].sudo().search([('login', '=', code.name), ('active', '=', False)], limit=1)
if not user:
continue
update_vals = {'active': True, 'first_login': True, 'kind': self.selection_data}
if target_group and target_group not in user.groups_id:
update_vals['groups_id'] = [(4, target_group.id)]
user.write(update_vals)
code.write({'status': True})
return True
class PinSPM(models.Model):
_name = "pin.spm"
_description = "Pin Code for Scholarship"
_inherit = ['mail.thread']
name = fields.Char(index=True, default=lambda self: _('New'), string="លេខកូដ", readonly=True)
data = fields.Integer(string="ចំនួនប្រតិបត្ដិការ")
status = fields.Boolean(default=False, string="Used")
reviewer_ids = fields.Many2many('res.users', string="អ្នកត្រួតពិនិត្យលើ")
main_id = fields.Many2one('main.pin', string="Main Reference", ondelete='cascade', required=True)
data_rec = fields.Char(string="ឈ្មោះខេត្ដ/ក្រុង ផ្សេងៗ")
reviewer = fields.Many2one("res.users", string="អ្នកទទួលលេខកូដ")
user_use_code = fields.Many2one('res.users', string="ឈ្មោះអ្នកប្រើប្រាស់", readonly=True)
info_name = fields.Many2one('info.info', string="ឈ្មោះក្នុងទំរងបំពេញពាក្យ")
name_info = fields.Char(related="info_name.name", store=True, readonly=False, string="Name")
email = fields.Char(related="info_name.email", store=True, readonly=True, string="Email")
gender = fields.Many2one(related="info_name.gender", store=True, readonly=False, string="Gender")
eng_name = fields.Char(related="info_name.eng_name", store=True, readonly=False, string="English Name")
dob = fields.Date(related="info_name.dob", store=True, readonly=False, string="DOB")
id_card = fields.Char(related="info_name.id_card", store=True, readonly=False, string="ID Card")
phone = fields.Char(related="info_name.phone", store=True, readonly=False, string="Phone")
study_level = fields.Selection(related="info_name.grade_study", store=True, readonly=False, string="Study Level")
create_uids = fields.Many2one(related="info_name.create_uid", store=True, readonly=False, string="Created By")
group_register = fields.Char(related="info_name.groups", store=True, readonly=False, string="Registration Group")
def check_use_code(self):
active_ids = self.env.context.get('active_ids')
if not active_ids:
return False
success = 0
for code in self.browse(active_ids):
if code.status:
continue
user = self.env['res.users'].sudo().search([('login', '=', code.name), ('active', '=', False)], limit=1)
if not user:
continue
user.write({'active': True, 'x_pin_code_spm': code.id, 'first_login': True})
info_user = self.env['info.info'].sudo().search(
['|', ('create_uid', '=', user.id), ('email', '=', user.login)], limit=1)
code.write({'info_name': info_user.id if info_user else False, 'user_use_code': user.id, 'status': True})
if info_user and not info_user.x_manager and code.reviewer:
info_user.x_manager = code.reviewer
success += 1
return {
'type': 'ir.actions.client', 'tag': 'display_notification',
'params': {'title': _('ជោគជ័យ'), 'message': _('បានផ្ទៀងផ្ទាត់ %d លេខកូដ') % success, 'type': 'success'}
} if success else False
class ReviewGroups(models.Model):
_name = 'rev.groups'
_description = 'Groups Review'
name = fields.Char(string="SHS Name", required=True, translate=True)
active = fields.Boolean(default=True)
class ResUsers(models.Model):
_name = "res.users"
_inherit = ["res.users", "mail.thread", "mail.activity.mixin"]
_description = "Signup User"
pin_code = fields.Char(string='PIN Code', size=6)
is_sending_email = fields.Boolean(string="Email Sent", default=False)
count_request_pin_code = fields.Integer(string="PIN Request Count", default=0)
groups = fields.Char(string="Group Label")
kind = fields.Char(string="Kind/Type")
first_login = fields.Boolean(default=True)
x_pin_code_spm = fields.Many2one('pin.spm', string="Pin Code SPM",
domain="[('name','=',login), ('status','=',False)]")
x_code_name = fields.Char(related='x_pin_code_spm.name', string='Code Name', store=True, readonly=True)
@api.model_create_multi
def create(self, vals_list):
users = super(ResUsers, self).create(vals_list)
template = self.env.ref('ck_signup.sending_pin_code_template', raise_if_not_found=False)
for user in users:
if self._context.get('skip_email_sending') or not template or not user.email:
continue
pin = str(random.randint(100000, 999999))
user.write({'pin_code': pin, 'active': False, 'first_login': True, 'groups': 'ប្រឡង',
'is_sending_email': True})
self._send_pin_email(user, pin, template)
return users
def _send_pin_email(self, user, pin_code, template):
try:
company = self.env.company
mail_from = company.email or tools.config.get('email_from') or 'noreply@localhost'
body = f"""
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: auto; padding: 20px; border: 1px solid #eee; border-radius: 8px;">
<h3 style="color: #2c3e50;">{_('Pin Code ដើម្បីផ្ទៀងផ្ទាត់គណនី')}</h3>
<p>{template.body_html or ''}</p>
<div style="background: #f8f9fa; padding: 15px; border-left: 4px solid #3498db; margin: 20px 0; text-align: center;">
<strong style="font-size: 18px;">លេខកូដផ្ទៀងផ្ទាត់:</strong><br>
<span style="font-size: 28px; letter-spacing: 6px; font-weight: bold; color: #e74c3c;">{pin_code}</span>
</div>
<p>{_('សូមបញ្ចូលលេខកូដខាងលើ រួចចុច ផ្ទៀងផ្ទាត់!')}</p>
</div>"""
self.env['mail.mail'].sudo().create({
'subject': _('Pin Code ដើម្បីផ្ទៀងផ្ទាត់ - %s') % company.name,
'email_from': mail_from, 'email_to': user.email, 'body_html': body, 'auto_delete': True,
}).send()
except Exception as e:
_logger.error(f"Failed to send PIN to {user.email}: {e}")
@api.model
def signup(self, values, token=None):
if token:
partner = self.env['res.partner'].sudo()._signup_retrieve_partner(token, check_validity=True,
raise_exception=True)
partner.write({'signup_token': False, 'signup_type': False, 'signup_expiration': False})
user = self.env['res.users'].sudo().search([('partner_id', '=', partner.id), ('active', '=', False)],
limit=1)
for f in ['city', 'country_id', 'lang', 'tz']: values.pop(f, None)
if user:
values.pop('login', None);
values.pop('name', None)
user.write(values)
return self.env.cr.dbname, user.login, values.get('password')
else:
values.update({'name': partner.name, 'partner_id': partner.id,
'email': values.get('email') or values.get('login')})
if partner.company_id:
values['company_id'] = partner.company_id.id
values['company_ids'] = [(6, 0, [partner.company_id.id])]
return self._signup_create_user(values)
else:
values['email'] = values.get('email') or values.get('login')
return self._signup_create_user(values)
def _signup_create_user(self, values):
values.setdefault('active', False)
values.setdefault('first_login', True)
user = self.with_context(skip_email_sending=True).sudo().create(values)
return self.env.cr.dbname, user.login, values.get('password')
def generate_and_send_new_pin_code(self, login=None):
user = self.env['res.users'].sudo().search([('login', '=', login), ('active', '=', False)],
limit=1) if login else self
if not user or user.count_request_pin_code >= 5: return False
template = self.env.ref('ck_signup.sending_pin_code_template', raise_if_not_found=False)
if not template: return False
new_pin = str(random.randint(100000, 999999))
user.write({'pin_code': new_pin, 'count_request_pin_code': user.count_request_pin_code + 1})
self._send_pin_email(user, new_pin, template)
return True
def verify_pin_code(self, pin_input):
self.ensure_one()
if not self.pin_code or pin_input != self.pin_code:
return False
self.write({'active': True, 'pin_code': False, 'first_login': True})
return True
+8
View File
@@ -0,0 +1,8 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_pin_spm_system_group,Access pin code spm system group,model_pin_spm,base.group_system,1,1,1,1
access_rev_groups_groups,Access rev groups,model_rev_groups,base.group_system,1,1,1,1
access_main_pin_base_system,Access main Pin user,model_main_pin,base.group_system,1,1,1,1
access_pin_code_base_user,Access Pin Code User,model_pin_spm,base.group_user,1,1,1,0
access_rev_groups_user,Access rev groups,model_rev_groups,base.group_user,1,1,1,0
access_system_groups_user,Access user system rev groups,model_rev_groups,base.group_system,1,1,1,1
access_main_pin_base_user,Access main Pin user,model_main_pin,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_pin_spm_system_group Access pin code spm system group model_pin_spm base.group_system 1 1 1 1
3 access_rev_groups_groups Access rev groups model_rev_groups base.group_system 1 1 1 1
4 access_main_pin_base_system Access main Pin user model_main_pin base.group_system 1 1 1 1
5 access_pin_code_base_user Access Pin Code User model_pin_spm base.group_user 1 1 1 0
6 access_rev_groups_user Access rev groups model_rev_groups base.group_user 1 1 1 0
7 access_system_groups_user Access user system rev groups model_rev_groups base.group_system 1 1 1 1
8 access_main_pin_base_user Access main Pin user model_main_pin base.group_user 1 1 1 1
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="ck_config_settings_view_form" model="ir.ui.view">
<field name="name">Inherit config</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='access_rights']" position="after">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="intern" string="Intern"/>
<field name="exam" string="Exam"/>
</div>
<div class="o_setting_right_pane">
<label string="Intern and Exam" for="exam"/>
<div class="text-muted">
Intern in Login Page
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>
@@ -0,0 +1,27 @@
<odoo>
<record id="view_multi_user_creation_wizard" model="ir.ui.view">
<field name="name">multi.user.creation.wizard.form</field>
<field name="model">multi.user</field>
<field name="arch" type="xml">
<form string="Multi User Creation Wizard">
<group>
<field name="user_data" widget="text"/>
</group>
<footer>
<button string="Create Users" type="object" name="action_create_users" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_multi_user_creation_wizard" model="ir.actions.act_window">
<field name="name">Multi User Creation Wizard</field>
<field name="res_model">multi.user</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem id="menu_multi_user_creation_wizard" name="Multi User Creation"
parent="register_v15.menu_root_setting" action="action_multi_user_creation_wizard" groups="base.group_system"/>
</odoo>
+132
View File
@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="inherit_user">
<field name="name">res_user_inherit</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='access_rights']" position="inside">
<group>
<field name="groups" string="តំបន់ត្រួតពិនិត្យ"/>
</group>
</xpath>
</field>
</record>
<record id="tree_code_spm" model="ir.ui.view">
<field name="name">លេខកូដអាហារូបករណ៍</field>
<field name="model">pin.spm</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
<!-- <field name="reciewer"/>-->
<field name="user_use_code"/>
<field name="info_name"/>
<field name="eng_name"/>
<field name="gender"/>
<field name="dob"/>
<field name="id_card"/>
<field name="phone"/>
<field name="study_level"/>
<field name="status"/>
</list>
</field>
</record>
<record id="form_code_spm" model="ir.ui.view">
<field name="name">លេខកូដអាហារូបករណ៍</field>
<field name="model">pin.spm</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name" readonly="1"/>
<field name="name_info"/>
<field name="email"/>
<field name="data_rec" invisible="1"/>
<!-- <field name="reciewer"/>-->
<field name="user_use_code"/>
<field name="info_name"/>
<field name="eng_name"/>
<field name="gender"/>
<field name="dob"/>
<field name="id_card"/>
<field name="phone"/>
<field name="study_level"/>
<field name="create_uids"/>
<field name="status"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="main_tree" model="ir.ui.view">
<field name="name">List View</field>
<field name="model">main.pin</field>
<field name="arch" type="xml">
<list>
<field name="data_term"/>
<field name="requester"/>
<!-- <field name="data_term"/>-->
</list>
</field>
</record>
<record id="main_form" model="ir.ui.view">
<field name="name">Main Pin code</field>
<field name="model">main.pin</field>
<field name="arch" type="xml">
<form>
<header>
<button type="object" name="compute_code" string="Generate Code"/>
</header>
<sheet>
<group>
<field name="data_term"/>
<field name="custom_group_label" invisible="1"/>
<!-- <field name="reciewer_code" domain="[('groups_id','=',groups)]" required="1"/>-->
<field name="requester"/>
<field name="name" string="ឆ្នាំ"/>
<field name="link_qr" widget="image" style="width:100px;height:120px"/>
<field name="selection_data" required="1"/>
<field name="school_kind" invisible="selection_data == '0'" required="selection_data == '01'"/>
<field name="pass_percent" widget="progressbar" options="{'editable': true}" string="ចំនួនភាគរយទទួលបានអាហារូបករណ៍" invisible="selection_data == '0'"/>
<field name="file_name" invisible="1"/>
<field name="files" filename="file_name" colspan="2" class="oe_inline" required="1"/>
</group>
<group>
<field name="pin_code_id" widget="one2many_list">
<list>
<field name="name" string="លេខកូដ"/>
<field name="data_rec" invisible="1" nolabel="1"/>
<!-- <field name="reciewer"/>-->
</list>
</field>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_code_spm" model="ir.actions.act_window">
<field name="name">លេខកូដអាហារូបករណ៍</field>
<field name="res_model">pin.spm</field>
<field name="view_mode">list,form</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_main_spm" model="ir.actions.act_window">
<field name="name">លេខកូដអាហារូបករណ៍</field>
<field name="res_model">main.pin</field>
<field name="view_mode">list,form</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<!-- <record id="action_user_info" model="ir.actions.server">-->
<!-- <field name="name">បង្ហាញឈ្មោះអ្នកប្រើលេខកូដ</field>-->
<!-- <field name="model_id" ref="model_pin_spm"/>-->
<!-- <field name="binding_model_id" ref="ck_signup.model_pin_spm"/>-->
<!-- <field name="binding_view_types">form,list</field>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action=model.sudo().check_use_code()</field>-->
<!-- </record>-->
<menuitem name="កំណត់" id="menu_root_setting" parent="register_v15.menu_root" sequence="8" groups="register_v15.group_register_manager"/>
<menuitem name="Generate Code" id="menu_code_generate" action="action_main_spm" parent="menu_root_setting" sequence="2" groups="register_v15.group_register_manager"/>
<menuitem name="លេខកូដអាហារូបករណ៍" id="menu_code_spm" action="action_code_spm" parent="menu_root_setting" sequence="2" groups="register_v15.group_register_manager"/>
</odoo>
+134
View File
@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="auth_signup.reset_password_email" name="User Reset Password">
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #FFFFFF; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;"><tr><td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="590" style="padding: 16px; background-color: #FFFFFF; color: #454748; border-collapse:separate;">
<tbody>
<!-- HEADER -->
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tr><td valign="middle">
<span style="font-size: 10px;">Your Account</span><br/>
<span style="font-size: 20px; font-weight: bold;">
<t t-out="object.name or ''">Marc Demo</t>
</span>
</td><td valign="middle" align="right" t-if="not object.company_id.uses_default_logo">
<img t-attf-src="/logo.png?company={{ object.company_id.id }}" style="padding: 0px; margin: 0px; height: auto; width: 80px;" t-att-alt="object.company_id.name"/>
</td></tr>
<tr><td colspan="2" style="text-align:center;">
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
</td></tr>
</table>
</td>
</tr>
<!-- CONTENT -->
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tr><td valign="top" style="font-size: 13px;">
<div>
Dear <t t-out="object.name or ''">Marc Demo</t>,<br/><br/>
A password reset was requested for the your account linked to this email.
You may change your password by following this link which will remain valid during 24 hours:<br/>
<div style="margin: 16px 0px 16px 0px;">
<a t-att-href="object.signup_url"
style="background-color: #875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
Change password
</a>
</div>
If you do not expect this, you can safely ignore this email.<br/><br/>
Thanks,
<t t-if="user.signature">
<br/>
<t t-out="user.signature">--<br/>Mitchell Admin</t>
</t>
</div>
</td></tr>
<tr><td style="text-align:center;">
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
</td></tr>
</table>
</td>
</tr>
<!-- FOOTER -->
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tr><td valign="middle" align="left">
<t t-out="object.company_id.name or ''">YourCompany</t>
</td></tr>
<tr><td valign="middle" align="left" style="opacity: 0.7;">
<t t-out="object.company_id.phone or ''">+1 650-123-4567</t>
<t t-if="object.company_id.email">
| <a t-att-href="'mailto:%s' % object.company_id.email" style="text-decoration:none; color: #454748;" t-out="object.company_id.email">info@yourcompany.com</a>
</t>
<t t-if="object.company_id.website">
| <a t-att-href="'%s' % object.company_id.website" style="text-decoration:none; color: #454748;" t-out="object.company_id.website">http://www.example.com</a>
</t>
</td></tr>
</table>
</td>
</tr>
</tbody>
</table>
</td></tr>
<!-- POWERED BY -->
</table>
</template>
<template id="auth_signup.alert_login_new_device" name="Alert Login with new Device">
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #FFFFFF; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;"><tr><td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="590" style="padding: 16px; background-color: #FFFFFF; color: #454748; border-collapse:separate;">
<tbody>
<!-- HEADER -->
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tr><td valign="top" style="font-size: 13px;">
<div>
Dear <t t-out="object.name or ''">Marc Demo</t>,<br/><br/>
A new device was used to sign in to your account. <br/><br/>
Here are some details about the connection:<br/>
<ul>
<li><span style="font-weight: bold;">
Date:</span> <t t-out="format_datetime(login_date, dt_format='long')">day, month dd, yyyy - hh:mm:ss (GMT)</t></li>
<t t-if="location_address">
<li><span style="font-weight: bold;">
Location:</span> <t t-out="location_address">City, Region, Country</t></li>
</t>
<t t-if="useros">
<li><span style="font-weight: bold;">
Platform:</span> <t t-out="useros">OS</t></li>
</t>
<t t-if="browser">
<li><span style="font-weight: bold;">
Browser:</span> <t t-out="browser">Browser</t></li>
</t>
<li><span style="font-weight: bold;">
IP Address:</span> <t t-out="ip_address">111.222.333.444</t></li>
</ul>
If you don't recognize it, you should change your password immediately via this link:<br/>
<div style="margin: 16px 0px 16px 0px;">
<a t-attf-href="{{ object.get_base_url() }}/web/reset_password"
style="background-color: #875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
Reset Password
</a>
</div>
Otherwise, you can safely ignore this email.
</div>
</td></tr>
<tr><td style="text-align:center;">
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
</td></tr>
</table>
</td>
</tr>
</tbody>
</table>
</td></tr>
</table>
</template>
</odoo>
+48
View File
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="ck_signup.signup" name="Sign up login">
<t t-call="web.login_layout">
<form class="oe_signup_form" role="form" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<t t-call="auth_signup.fields">
<t t-set="only_passwords" t-value="bool(token)"/>
</t>
<div t-if="intern" class="form-group field-group" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">
<label style="color:#15599a;" for="group" class="control-label">ជ្រើសរើស</label>
<select class="form-control" name="group" id="group">
<t t-foreach="groups" t-as="groups">
<option class="form-control" t-att-value="groups.id" name="group"><span t-esc="groups.name"/></option>
</t>
</select>
</div><br/>
<div t-if="intern" class="form-group field-group" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">
<label style="color:#15599a;" for="country_id" class="control-label">ស្នើសុំទៅប្រទេស</label>
<select class="form-control" id="country" name="country">
<t t-foreach="request.env['res.country'].sudo().search([('id','in',(48,116))])"
t-as="countries" t-key="countries.id">
<option t-att-value="countries.id">
<t t-esc="countries.name"/>
</option>
</t>
</select>
</div>
<p class="alert alert-success" t-if="message">
<t t-esc="message"/>
</p>
<p class="alert alert-danger" t-if="error">
<t t-esc="error"/>
</p>
<input type="hidden" name="redirect" t-att-value="redirect"/>
<input type="hidden" name="token" t-att-value="token"/>
<input type="hidden" name="active" t-att-value="active"/>
<div class="clearfix oe_login_buttons" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">
<a t-attf-href="/web/login?{{ keep_query() }}" class="btn btn-link pull-right" style="color:#15599a;font-size:17px;margin-top:10px;font-weight: 800;">ត្រឡប់ក្រោយ</a>
<a t-attf-href="/web/pin_code?{{ keep_query() }}" class="btn btn-link pull-right" style="color:#15599a;font-size:17px;margin-top:10px;font-weight: 800;">ផ្ទៀងផ្ទាត់PIN CODE</a>
<button type="submit" class="btn btn-primary pull-right" style="background-color: rgba(255,255,255,0.5); color:#15599a;margin-top:15px;">ចុះឈ្មោះ</button>
</div>
</form>
</t>
</template>
</odoo>
+147
View File
@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="auth_signup.fields" name="Auth Signup/ResetPassword form fields">
<div t-attf-class="text-center" style="margin:5px 0 33px 0;">
<b style="font-family:Khmer OS Muol Light; src:url(../fonts/KhmerOSmuollight.ttf) ;format('truetype'); font-size:17px;color:#15599a">បង្កើតគណនី</b>
</div>
<div class="form-group field-login" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;margin-bottom:18px;">
<label for="login" class="control-label" style="color:#15599a;">អ៊ីម៉ែល ឬលេខកូដអាហារូបករណ៍</label>
<input type="text" name="login" t-att-value="login" id="login" class="form-control" autofocus="autofocus"
autocapitalize="off" required="required" placeholder="អុីម៉ែល" t-att-readonly="'readonly' if only_passwords else None"/>
</div>
<div class="form-group field-name" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;margin-bottom:18x;">
<label for="name" class="control-label" style="color:#15599a;">ឈ្មេាះរបស់អ្នក</label>
<input type="text" name="name" t-att-value="name" id="name" class="form-control" placeholder="ឧទាហរណ៍:ជ័យ មង្គល"
required="required" t-att-readonly="'readonly' if only_passwords else None"
t-att-autofocus="'autofocus' if login and not only_passwords else None" />
</div>
<div class="form-group field-password" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;margin-bottom:15px;margin-top:18px;">
<label for="password" class="control-label" style="color:#15599a;">ពាក្យសម្ងាត់</label>
<input type="password" name="password" id="password" class="form-control" placeholder="ពាក្យសម្ងាត់"
required="required" t-att-autofocus="'autofocus' if only_passwords else None"/>
</div>
<div class="form-group field-confirm_password" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;margin-bottom:18px;">
<label for="confirm_password" class="control-label" style="color:#15599a;">បញ្ជាក់ពាក្យសម្ងាត់</label>
<input type="password" name="confirm_password" placeholder="បញ្ជាក់ពាក្យសម្ងាត់" id="confirm_password" class="form-control" required="required"/>
</div>
</template>
<!-- <template id="inherit_auth_signup" inherit_id="auth_signup.login">-->
<!-- <xpath expr="//div[hasclass('justify-content-between','mt-2','d-flex','small')]" position="replace">-->
<!-- <div class="justify-content-between mt-3 d-flex small" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:12px;">-->
<!-- <a t-if="signup_enabled" style="font-size:15px;color:#15599a;text-decoration: underline;" t-attf-href="/web/signup?{{ keep_query() }}">បង្កើតគណនី</a>-->
<!-- <a t-if="reset_password_enabled" style="font-size:15px;margin-right:7%;color:#15599a;text-decoration: underline;" t-attf-href="/web/reset_password?{{ keep_query() }}">ភ្លេចពាក្យសម្ងាត់</a>-->
<!-- <a t-attf-href="/web/preview_doc?{{ keep_query() }}" class="pull-right" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;;margin:0 0 0 10px;color:#15599a;text-decoration: underline;">ការណែនាំ</a>-->
<!-- </div>-->
<!-- </xpath>-->
<!-- </template>-->
<!-- <template id="homepage_extend1" inherit_id="custom_template_amt.inherit_auth_signup">-->
<!-- <xpath expr="//a[@id='reset']" position='after'>-->
<!-- <a t-attf-href="/web/preview_doc?{{ keep_query() }}" class="pull-right mt-3" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;;margin:7px 0 0 25px;color:#0b83c5;text-decoration: underline;">ការណែនាំ</a>-->
<!-- </xpath>-->
<!-- </template>-->
<template id="ck_signup.verify_pin_code" name="verify_pin_code">
<t t-call="web.login_layout">
<form class="oe_reset_password_form" role="form" t-attf-action="/web/verify_pin_code" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="form-group field-login">
<label for="login" class="control-label" style="color:#15599a;font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">អ៊ីម៉ែល</label>
<input type="text" name="login" t-att-value="login" id="login" class="form-control"
autofocus="autofocus" required="required" autocapitalize="off"/>
</div>
<div class="form-group field-Pin_code">
<label for="Pin_code" class="control-label" style="color:#15599a;font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">Pin Code From Email</label>
<input type="text" name="pin_code" t-att-value="pin_code" id="pin_code" class="form-control"
autofocus="autofocus" required="required" autocapitalize="off"/>
</div>
<!-- <div class="form-group field-x_code_name">-->
<!-- <label for="Pin_code" class="control-label" style="color:#F3F2F2;font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">Scholarship Code</label>-->
<!-- <input type="text" name="x_code_name" t-att-value="x_code_name" id="x_code_name" class="form-control"-->
<!-- autofocus="autofocus" required="required" autocapitalize="off"/>-->
<!-- </div>-->
<p class="alert alert-success" t-if="message">
<t t-esc="message"/>
</p>
<p class="alert alert-danger" t-if="error">
<t t-esc="error"/>
</p>
<div class="clearfix oe_login_buttons" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">
<button type="submit" class="btn btn-primary pull-left" style="background-color:#330066;margin-top:5%;">ផ្ទៀងផ្ទាត់គណនី</button>
<a t-attf-href="/web/view_request_pin_code?{{ keep_query() }}" class="btn btn-link pull-right" style="color:#15599a;margin-top:5%;">ស្នើសុំ PIN CODE ថ្មី</a>
</div>
</form>
</t>
</template>
<template id="ck_signup.view_request_new_pin_code" name="view_request_new_pin_code">
<t t-call="web.login_layout">
<p class="alert alert-success" t-if="message">
<t t-esc="message"/>
<p><a t-attf-href="/web/pin_code?{{ keep_query() }}" class="btn btn-link pull-right" style="color:#15599a;">ផ្ទៀងផ្ទាត់PIN CODE</a></p>
</p>
<form class="oe_reset_password_form" role="form" t-attf-action="/web/request_new_pin_code" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="form-group field-login" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">
<label for="login" class="control-label" style="color:#F3F2F2;">អ៊ីម៉ែល</label>
<input type="text" name="login" t-att-value="login" id="login" class="form-control"
autofocus="autofocus" required="required" autocapitalize="off"/>
</div>
<p class="alert alert-success" t-if="message">
<t t-esc="message"/>
</p>
<p class="alert alert-danger" t-if="error">
<t t-esc="error"/>
</p>
<div class="clearfix oe_login_buttons" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">
<button type="submit" class="btn btn-primary pull-left" style="color:#ffff;margin-top:5%;">ស្នើប្ដូរ PIN Code</button>
<a t-attf-href="/web/login?{{ keep_query() }}" class="btn btn-link pull-right" style="color:#15599a;margin-top:5%;">ត្រឡប់ក្រោយ</a>
</div>
</form>
</t>
</template>
<!-- Change Password template-->
<template id="ck_signup.reset_password_direct" name="Reset password">
<t t-call="web.login_layout">
<form class="oe_reset_password_form" role="form" action="/web/reset_password/submit" method="post">
<div class="form-group field-name" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;margin-bottom:15x;">
<label for="name" class="control-label" style="color:#f2f5f2;">ឈ្មេាះរបស់អ្នក</label>
<input type="text" name="name" t-att-value="name" id="name" class="form-control" placeholder="ឧទាហរណ៍:ជ័យ មង្គល"
required="required" t-att-readonly="'readonly' if only_passwords else None"
t-att-autofocus="'autofocus' if login and not only_passwords else None" autocapitalize="off"/>
</div>
<div class="form-group field-login" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">
<label for="user_name" class="col-form-label">អ៊ីម៉ែល ឬលេខកូដអាហារូបករណ៍</label>
<input type="text" name="user_name" t-att-value="username" id="user_name" class="form-control" autofocus="autofocus" required="required" autocapitalize="off"/>
</div>
<div class="form-group field-login" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">
<label for="old_password" class="col-form-label">ពាក្យសម្ងាត់ចាស់</label>
<input type="password" name="old_password" t-att-value="old_password" id="old_password"
class="form-control"
autofocus="autofocus" required="required" autocapitalize="off"/>
</div>
<div class="form-group field-login" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">
<label for="new_password" class="col-form-label">ពាក្យសម្ងាត់ថ្មី</label>
<input type="password" name="new_password" t-att-value="new_password" id="new_password"
class="form-control"
autofocus="autofocus" required="required" autocapitalize="off"/>
</div>
<div class="form-group field-login" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">
<label for="confirm_new_password" class="col-form-label">បញ្ជាក់ពាក្យសម្ងាត់ថ្មី</label>
<input type="password" name="confirm_new_password" t-att-value="confirm_new_password"
id="confirm_new_password" class="form-control"
autofocus="autofocus" required="required" autocapitalize="off"/>
</div>
<p class="alert alert-danger" t-if="error" role="alert">
<t t-esc="error"/>
</p>
<br/>
<div class="clearfix oe_login_buttons" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:15px;">
<button type="submit" style="width:100%;" class="btn btn-primary btn-block">យល់ព្រម</button>
<div class="d-flex justify-content-between align-items-center small mt-2">
<a t-attf-href="/web/session/logout?redirect=/">ត្រឡប់ក្រោយមកទំព័រដើម</a>
</div>
</div>
</form>
</t>
</template>
</odoo>
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- <template id="ck_signup.view_preview_doc" name="view_preview_doc" >-->
<!-- <object data="/ck_signup/static/src/doc/AMT_Registration.pdf" type="application/pdf" width="100%" height="90%">-->
<!-- <a href="/ck_signup/static/src/doc/AMT_Registration.pdf">AMT_Registration.pdf</a>-->
<!-- </object>-->
<!-- <div class="clearfix oe_login_buttons" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:12px;">-->
<!-- <a t-attf-href="/web/login?{{ keep_query() }}" class="btn btn-link" style="margin-left:45%;color:color:#15599a;">ត្រឡប់ក្រោយទៅភ្ជាប់ចូល ឬក៌បង្កើតគណនី</a>-->
<!-- </div>-->
<!-- </template>-->
<template id="ck_signup.back_to_verify" name="back_to_verify">
<p style="text-align:center;">PIN Codeបានផ្ញើចូលអ៊ីម៉ែលរបស់លោកអ្នកដោយជោគជ័យ!<br/>
សូមចុចត្រឡប់ក្រោយទៅVerify Pin Code and Scholarship Code(តំណរភ្ជាប់ខាងក្រោម) ដើម្បីដាក់Email, Pin Code and Scholarship Code<br/>
រួចចុចផ្ទៀងផ្ទាត់គណនីរបស់លោកអ្នក!
</p>
<div class="clearfix oe_login_buttons" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:12px;">
<a t-attf-href="/web/pin_code?{{ keep_query() }}" class="btn btn-link" style="margin-left:45%;color:color:#15599a;">ត្រឡប់ក្រោយទៅ Verify Pin Code and Scholarship Code</a>
</div>
</template>
<template id="ck_signup.back_to_signup" name="back_to_signup">
<p style="text-align:center;">អ្នកបានស្នើសុំPin Code លើសពីការកំណត់ហើយ!<br/>
សូមចុចត្រឡប់ក្រោយដើម្បីដាក់អ៊ីម៉ែលថ្មីសម្រាប់ទទួលបានPin Code ថ្មី!
</p>
<div class="clearfix oe_login_buttons" style="font-family:Khmer OS content; src:url(../font/Khmer_OS_Siemreap.ttf) ;format('truetype'); font-size:12px;">
<a t-attf-href="/web/signup?{{ keep_query() }}" class="btn btn-link" style="margin-left:45%;color:color:#15599a;">ត្រឡប់ក្រោយ</a>
</div>
</template>
</odoo>
+2
View File
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import multi_user_creation_wizard
@@ -0,0 +1,24 @@
from odoo import api, fields, models, _
class MultiUserCreationWizard(models.TransientModel):
_name = 'multi.user'
_description = 'Multi User Creation Wizard'
user_data = fields.Text(string='User Data', required=True, help="Enter user data in the format: name,email,password")
@api.model
def create_users(self, user_data):
user_model = self.env['res.users']
for line in user_data.split('\n'):
name, email, password = line.split(',')
user_model.create({
'name': name,
'login': email,
'email': email,
'password': password,
})
def action_create_users(self):
self.create_users(self.user_data)
return {'type': 'ir.actions.act_window_close'}