#coding=utf-8
require 'elasticsearch/model'
class Course < ActiveRecord :: Base
include Redmine :: SafeAttributes
include CoursesHelper
STATUS_ACTIVE = 1
STATUS_CLOSED = 5
STATUS_ARCHIVED = 9
# elasticsearch
include Elasticsearch :: Model
#elasticsearch kaminari init
Kaminari :: Hooks . init
Elasticsearch :: Model :: Response :: Response . __send__ :include , Elasticsearch :: Model :: Response :: Pagination :: Kaminari
settings index : { number_of_shards : 5 } do
mappings dynamic : 'false' do
indexes :name , analyzer : 'smartcn' , index_options : 'offsets'
indexes :description , analyzer : 'smartcn' , index_options : 'offsets'
indexes :updated_at , index : " not_analyzed " , type : " date "
end
end
attr_accessible :code , :extra , :name , :state , :tea_id , :time , :location , :state , :term , :password , :is_public ,
:description , :class_period , :open_student , :enterprise_name , :is_delete , :syllabus_id , :end_time , :end_term ,
:choose_group_allow , :is_end , :homepage_show , :course_list_id , :teacher_list , :student_list , :is_hidden
#belongs_to :project, :class_name => 'Course', :foreign_key => :extra, primary_key: :identifier
belongs_to :teacher , :class_name = > 'User' , :foreign_key = > :tea_id # 定义一个方法teacher, 该方法通过tea_id来调用User表
belongs_to :school , :class_name = > 'School' , :foreign_key = > :school_id #定义一个方法school, 该方法通过school_id来调用School表
belongs_to :syllabus
belongs_to :course_list
# has_many :bid
has_many :course_members , dependent : :destroy
has_many :members , :include = > [ :principal , :roles ] , :conditions = > " #{ Principal . table_name } .type='User' AND #{ Principal . table_name } .status= #{ Principal :: STATUS_ACTIVE } "
has_many :memberships , :class_name = > 'Member'
has_many :member_principals , :class_name = > 'Member' ,
:include = > :principal ,
:conditions = > " #{ Principal . table_name } .type='Group' OR ( #{ Principal . table_name } .type='User' AND #{ Principal . table_name } .status= #{ Principal :: STATUS_ACTIVE } ) "
has_many :principals , :through = > :member_principals , :source = > :principal
has_many :users , :through = > :members
has_many :org_courses , :dependent = > :destroy
has_many :organizations , :through = > :org_courses
# has_many :homeworks, :through => :homework_for_courses, :source => :bid, :dependent => :destroy
has_many :journals_for_messages , :as = > :jour , :dependent = > :destroy
# has_many :homework_for_courses, :dependent => :destroy
has_many :student , :class_name = > 'StudentsForCourse' , :source = > :user
has_many :course_infos , :class_name = > 'CourseInfos' , :dependent = > :destroy
has_many :enabled_modules , :dependent = > :delete_all
has_many :boards , :dependent = > :destroy , :order = > " position ASC "
#has_many :course_journals_for_messages, :class_name => 'CourseJournalsForMessage', :as => :jour, :dependent => :destroy
has_many :news , :dependent = > :destroy , :include = > :author
has_one :course_status , :class_name = > " CourseStatus " , :dependent = > :destroy
has_many :student_work_projects , :dependent = > :destroy
has_many :course_homework_categories , :dependent = > :destroy , :order = > " CONVERT(name USING gbk) COLLATE gbk_chinese_ci ASC "
has_many :homework_commons , :dependent = > :destroy
has_many :shixun_homework_commons , class_name : 'HomeworkCommon' , conditions : 'homework_type = 4'
has_many :other_homework_commons , class_name : 'HomeworkCommon' , conditions : 'homework_type IN (1, 3)'
has_many :homework_group_settings , :dependent = > :destroy
has_many :student_works , :through = > :homework_commons , :dependent = > :destroy
# 毕设选题
has_many :graduation_topics , :dependent = > :destroy
has_many :student_graduation_topics , :dependent = > :destroy
has_many :graduation_groups , :dependent = > :destroy , :order = > " CONVERT(name USING gbk) COLLATE gbk_chinese_ci ASC "
# 毕设任务
has_many :graduation_tasks , :dependent = > :destroy
has_many :graduation_works , :dependent = > :destroy
has_many :course_groups , :dependent = > :destroy , :order = > " CONVERT(name USING gbk) COLLATE gbk_chinese_ci ASC "
has_many :teacher_course_groups , :dependent = > :destroy
has_many :course_homework_statisticss , :dependent = > :destroy
# 课程动态
has_many :course_acts , :class_name = > 'CourseActivity' , :as = > :course_act , :dependent = > :destroy
# 工程认证
has_many :ec_courses , :through = > :ec_major_courses
has_many :ec_major_courses , :dependent = > :destroy
has_many :course_activities
# 课程消息
has_many :course_messages , :class_name = > 'CourseMessage' , :as = > :course_message , :dependent = > :destroy
has_many :exercises , :dependent = > :destroy
has_many :exercise_group_settings , :dependent = > :destroy
has_many :polls , :dependent = > :destroy
has_many :poll_group_settings , :dependent = > :destroy
# 课程贡献榜
has_many :course_contributor_scores , :dependent = > :destroy
has_many :attachment_group_settings , :dependent = > :destroy
#课程模块
has_many :course_modules , :dependent = > :destroy
#导入的学生信息
has_many :import_course_users , :dependent = > :destroy
has_many :tidings , :as = > :container , :dependent = > :destroy
acts_as_taggable
acts_as_nested_set :order = > 'name' , :dependent = > :destroy
acts_as_attachable :view_permission = > :view_course_files ,
:delete_permission = > :manage_files
validates_presence_of :name
#validates_format_of :class_period, :with =>/^[1-9]\d*$/
#validates_format_of :time, :with => /^\d{4}$/
validates_format_of :name , :with = > / ^[^ ]+[a-zA-Z0-9_ \ u4e00- \ u9fa5 \ s \ S]*$ /
validates_length_of :description , :maximum = > 10000
before_save :self_validate
# 公开课程变成私有课程,所有资源都变成私有
after_update :update_files_public
after_create :create_board_sync , :act_as_course_activity , :send_tiding , :create_course_modules
before_destroy :delete_all_members
safe_attributes 'extra' ,
'time' ,
'name' ,
'extra' ,
'code' ,
'location' ,
'tea_id' ,
'password' ,
'term' ,
'is_public' ,
'description' ,
'class_period' ,
'open_student' ,
'is_delete' ,
'syllabus_id' ,
'end_time' ,
'end_term' ,
'os_allow' ,
'choose_group_allow' ,
'credit' ,
'homepage_show' ,
'show_unit'
acts_as_customizable
scope :not_deleted , lambda { where ( is_delete : 0 ) }
scope :not_deleted_not_end , lambda { where ( is_delete : 0 , is_end : 0 ) }
scope :not_deleted_but_is_end , lambda { where ( is_end : 1 , is_delete : 0 ) }
scope :all_course
scope :active , lambda { where ( :status = > STATUS_ACTIVE ) }
scope :status , lambda { | arg | where ( arg . blank? ? nil : { :status = > arg . to_i } ) }
scope :all_public , lambda { where ( :is_public = > true ) }
scope :visible , lambda { | * args | where ( Course . where ( " is_delete =? " , 0 ) . visible_condition ( args . shift || User . current , * args ) ) }
scope :allowed_to , lambda { | * args |
user = User . current
permission = nil
if args . first . is_a? ( Symbol )
permission = args . shift
else
user = args . shift
permission = args . shift
end
where ( Course . allowed_to_condition ( user , permission , * args ) )
}
scope :like , lambda { | arg |
if arg . blank?
where ( nil )
else
pattern = " % #{ arg . to_s . strip . downcase } % "
where ( " LOWER(name) LIKE :p " , :p = > pattern )
end
}
scope :indexable , lambda { where ( 'is_public = 1 and is_delete = 0' ) }
def attachment_count
Attachment . find_by_sql ( " select count(id) as count from attachments where container_id= #{ self . id } and container_type='Course' " ) . first . try ( :count ) . to_i
end
def member_count
CourseMember . find_by_sql ( " select count(id) as count from course_members where course_id= #{ self . id } " ) . first . try ( :count ) . to_i
end
def self . search ( query )
__elasticsearch__ . search (
{
query : {
multi_match : {
query : query ,
type : " most_fields " ,
operator : " or " ,
fields : [ 'name' , 'description^0.5' ]
}
} ,
sort : {
_score : { order : " desc " } ,
updated_at : { order : " desc " }
} ,
highlight : {
pre_tags : [ '<span class="c_red">' ] ,
post_tags : [ '</span>' ] ,
fields : {
name : { } ,
description : { }
}
}
}
)
end
def self . e_search ( query )
__elasticsearch__ . search (
{
query : {
multi_match : {
query : query ,
type : " most_fields " ,
operator : " or " ,
fields : [ 'name' , 'description^0.5' ]
}
} ,
sort : {
_score : { order : " desc " } ,
updated_at : { order : " desc " }
}
}
)
end
def delete!
update_attribute ( :is_delete , true )
end
def visible? ( user = User . current )
user . allowed_to? ( :view_course , self )
end
def parent_id_changed?
false
end
# Returns the mail adresses of users that should be always notified on project events
def recipients
notified_users . collect { | user | user . mail }
end
# Returns the users that should be notified on project events
def notified_users
# TODO: User part should be extracted to User#notify_about?
members . select { | m | m . principal . present? && ( m . mail_notification? || m . principal . mail_notification == 'all' ) } . collect { | m | m . principal }
end
def course_list_name
self . course_list ? self . course_list . name : " "
end
# 选出教师数不为0的答辩组
def course_graduation_group
self . graduation_groups . select { | group | group . course_members . count > 0 }
end
# 课程的短描述信息
def short_description ( length = 255 )
description . gsub ( / < \/ ?.*?> / , " " ) . html_safe if description
#description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
end
# 课程的短名称信息
def short_name ( length = 8 )
name . gsub ( / < \/ ?.*?> / , " " ) . html_safe if name
end
def strip_html ( html )
return html if html . empty? || ! html . include? ( '<' )
output = " "
tokenizer = HTML :: Tokenizer . new ( html )
while token = tokenizer . next
node = HTML :: Node . parse ( nil , 0 , 0 , token , false )
output += token unless ( node . kind_of? HTML :: Tag ) or ( token =~ / ^<! / )
end
return output
end
def extra_frozen?
errors [ :extra ] . blank? && ! ( new_record? || extra . blank? )
end
def archived?
self . status == STATUS_ARCHIVED
end
# 课堂的老师不包括助教
def course_teachers
self . course_members . where ( :role = > [ 1 , 2 ] )
end
# 课堂的老师包括助教
def teachers
self . course_members . where ( :role = > [ 1 , 2 , 3 ] )
end
def students
self . course_members . where ( :role = > [ 4 ] )
end
def self . visible_condition ( user , options = { } )
allowed_to_condition ( user , :view_course , options )
end
# 获取课程的资源类型列表
def attachmenttypes
@attachmenttypes = Attachmentstype . find ( :all , :conditions = > [ " #{ Attachmentstype . table_name } .typeId= ? " , self . attachmenttype ] )
end
# 获取资源后缀名列表
def contenttypes
attachmenttypes
if @attachmenttypes . length > 0
@attachmenttypes . last ( ) . suffixArr
end
end
def active?
self . status == STATUS_ACTIVE
end
#课程权限判断
def allows_to? ( action )
if archived?
# No action allowed on archived projects
return false
end
unless active? || Redmine :: AccessControl . read_action? ( action )
# No write action allowed on closed projects
return false
end
# No action allowed on disabled modules
if action . is_a? Hash
allowed_actions . include? " #{ action [ :controller ] } / #{ action [ :action ] } "
else
allowed_permissions . include? action
end
end
# 课程允许的权限集合
def allowed_permissions
@allowed_permissions || = begin
module_names = enabled_modules . all ( :select = > :name ) . collect { | m | m . name }
Redmine :: AccessControl . modules_permissions ( module_names ) . collect { | p | p . name }
end
end
# 课程允许的动作集合
def allowed_actions
@actions_allowed || = allowed_permissions . inject ( [ ] ) { | actions , permission | actions += Redmine :: AccessControl . allowed_actions ( permission ) } . flatten
end
# 返回用户组可以访问的课程
def users_by_role
members . includes ( :user , :roles ) . all . inject ( { } ) do | h , m |
m . roles . each do | r |
h [ r ] || = [ ]
h [ r ] << m . user
end
h
end
end
#自定义验证
def self_validate
end
def update_files_public
unless self . is_public?
self . attachments . each do | a |
a . update_attributes ( :is_public = > false )
end
end
end
def is_public?
self . is_public == 1
end
def update_default_value
self . time = Time . now . year unless time
self . term = cur_course_term unless term
self . class_period = 10 unless class_period
end
# 创建课程讨论区
def create_board_sync
@board = self . boards . build
#self.name=" #{l(:label_borad_course) }"
@board . name = " #{ l ( :label_borad_course ) } " #self.name
@board . description = self . name . to_s
@board . project_id = - 1
if @board . save
logger . debug " [Course Model] ===> #{ @board . to_json } "
else
logger . error " [Course Model] ===> Auto create board when course saved, because #{ @board . full_messages } "
end
end
# 新增课程留言
# add by nwb
def self . add_new_jour ( user , notes , id , options = { } )
course = Course . find ( id )
if options . count == 0
pjfm = course . journals_for_messages . build ( :user_id = > user . id , :notes = > notes , :reply_id = > 0 )
else
pjfm = course . journals_for_messages . build ( options )
end
pjfm . save
pjfm
end
# 删除课程所有成员
def delete_all_members
CourseMember . delete_all ( [ 'course_id = ?' , id ] )
# if self.members && self.members.count > 0
# me, mr = Member.table_name, MemberRole.table_name
# connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.course_id = #{id})")
# Member.delete_all(['course_id = ?', id])
# end
end
# 创建课程模块
def create_course_modules
mod = self . course_modules
if mod . blank?
type = [ " activity " , " shixun_homework " , " common_homework " , " group_homework " , " graduation " , " exercise " , " poll " , " attachment " , " board " , " course_group " ]
name = [ " 动态 " , " 实训作业 " , " 普通作业 " , " 分组作业 " , " 毕业设计 " , " 试卷 " , " 问卷 " , " 资源 " , " 讨论 " , " 分班 " ]
for i in 0 .. 9
CourseModule . create ( :course_id = > self . id , :module_type = > type [ i ] , :position = > i + 1 , :hidden = > ( i == 4 ? 1 : 0 ) , :module_name = > name [ i ] )
end
end
end
def get_endup_time
begin
end_time = Time . parse ( self . endup_time )
rescue Exception = > e
end_time = Time . parse ( " 3000-01-01 " )
Rails . logger . error " [Error] course endup_time error. ===> #{ e } "
ensure
return end_time
end
end
def get_time
begin
time = Date . new ( self . time ) . to_time
rescue Exception = > e
time = Time . parse ( " 3000-01-01 " )
Rails . logger . error " [Error] course time error. ===> #{ e } "
ensure
return time
end
end
def self . allowed_to_condition ( user , permission , options = { } )
perm = Redmine :: AccessControl . permission ( permission )
base_statement = ( perm && perm . read? ? " #{ Course . table_name } .status <> #{ Course :: STATUS_ARCHIVED } " : " #{ Course . table_name } .status = #{ Course :: STATUS_ACTIVE } " )
if perm && perm . course_module
base_statement << " AND #{ Course . table_name } .id IN (SELECT em.course_id FROM #{ EnabledModule . table_name } em WHERE em.name=' #{ perm . course_module } ') "
end
if options [ :course ]
course_statement = " #{ Course . table_name } .id = #{ options [ :course ] . id } "
course_statement << " OR ( #{ Course . table_name } .lft > #{ options [ :course ] . lft } AND #{ Course . table_name } .rgt < #{ options [ :course ] . rgt } ) " if options [ :with_subcourses ]
base_statement = " ( #{ course_statement } ) AND ( #{ base_statement } ) "
end
if user . admin?
base_statement
else
statement_by_role = { }
unless options [ :member ]
role = user . logged? ? Role . non_member : Role . anonymous
if role . allowed_to? ( permission )
statement_by_role [ role ] = " #{ Course . table_name } .is_public = #{ connection . quoted_true } "
end
end
if user . logged?
user . courses_by_role . each do | role , courses |
if role . allowed_to? ( permission ) && courses . any?
statement_by_role [ role ] = " #{ Course . table_name } .id IN ( #{ courses . collect ( & :id ) . join ( ',' ) } ) "
end
end
end
if statement_by_role . empty?
" 1=0 "
else
if block_given?
statement_by_role . each do | role , statement |
if s = yield ( role , user )
statement_by_role [ role ] = " ( #{ statement } AND ( #{ s } )) "
end
end
end
" (( #{ base_statement } ) AND ( #{ statement_by_role . values . join ( ' OR ' ) } )) "
end
end
end
# 课堂实训作业的评测次数
def evaluate_count
course_user_ids = self . students . map ( & :user_id )
shixun_ids = self . homework_commons . joins ( :homework_commons_shixuns ) . where ( homework_type : 4 ) . pluck ( :shixun_id )
return 0 if shixun_ids . blank?
Game . joins ( :challenge ) . where ( challenges : { shixun_id : shixun_ids } , games : { user_id : course_user_ids } ) . sum ( :evaluate_count )
end
#课程动态公共表记录
def act_as_course_activity
self . course_acts << CourseActivity . new ( :user_id = > self . tea_id , :course_id = > self . id )
end
#创建课程后,给该用户发送消息
def send_tiding
self . tidings << Tiding . new ( :user_id = > self . tea_id , :trigger_user_id = > self . tea_id , :belong_container_id = > self . id , :belong_container_type = > 'Course' , :tiding_type = > " System " )
end
#项目与课程分离后,很多课程的名称等信息为空,这些数据信息存储在项目表中!!就是数据兼容的问题
#def name
# read_attribute('name') || Project.find_by_identifier(self.extra).try(:name)
#end
# after_commit on: [:create] do
# __elasticsearch__.index_document
# end
#
# after_commit on: [:update] do
# __elasticsearch__.update_document
# end
#
# after_commit on: [:destroy] do
# __elasticsearch__.delete_document
# end
def create_course_ealasticsearch_index
return if Rails . env . development?
if self . is_public == 1 and self . is_delete == 0 #公开 和 没有被删除的课程才被索引
self . __elasticsearch__ . index_document
end
end
def update_course_ealasticsearch_index
return if Rails . env . development?
if self . is_public == 1 and self . is_delete == 0 #如果是初次更新成为公开或者恢复被删除的情况,会报错,那么这条记录尚未被索引过。没有报错就是更新的其他属性
begin
self . __elasticsearch__ . update_document
rescue = > e
self . __elasticsearch__ . index_document
end
else #如果是更新成为私有的,那么索引就要被删除
begin
self . __elasticsearch__ . delete_document
rescue = > e
end
end
end
def delete_course_ealasticsearch_index
return if Rails . env . development?
begin
self . __elasticsearch__ . delete_document
rescue = > e
end
end
# 延迟生成邀请码
def invite_code
return generate_invite_code
end
# 生成邀请码
CODES = %W( 2 3 4 5 6 7 8 9 A B C D E F G H J K L N M O P Q R S T U V W X Y Z )
def generate_invite_code
code = read_attribute ( :invite_code )
if ! code || code . size < 5
code = CODES . sample ( 5 ) . join
return generate_invite_code if Course . where ( invite_code : code ) . present?
update_attribute ( :invite_code , code )
end
code
end
def generate_qrcode
ticket = self . qrcode
if ! ticket || ticket . size < 10 || ( Time . now . to_i > self . qrcode_expiretime )
response = Wechat . api . qrcode_create_scene ( invite_code , 2592000 )
logger . debug " response = #{ response } "
self . qrcode = response [ 'ticket' ]
self . qrcode_expiretime = Time . now . to_i + response [ 'expire_seconds' ]
save! && reload
ticket = qrcode
end
ticket
end
end
# Delete the previous articles index in Elasticsearch
# Course.__elasticsearch__.client.indices.delete index: Course.index_name rescue nil
#
# # Create the new index with the new mapping
# Course.__elasticsearch__.client.indices.create \
# index: Course.index_name,
# body: { settings: Course.settings.to_hash, mappings: Course.mappings.to_hash }
# Index all article records from the DB to Elasticsearch
#Course.where('is_public = 1').import :force=>true