first push message
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# from . import controllers
|
||||
from . import models
|
||||
# from . import wizard
|
||||
@@ -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',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
from . import controllers
|
||||
# from . import login_controller
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
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 |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import multi_user_creation_wizard
|
||||
Binary file not shown.
Binary file not shown.
@@ -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'}
|
||||
Reference in New Issue
Block a user