2016-06-15 13:44:28 +08:00
#coding=utf-8
2015-11-26 09:57:53 +08:00
require 'elasticsearch/model'
2014-10-23 11:30:34 +08:00
class Course < ActiveRecord :: Base
include Redmine :: SafeAttributes
2016-07-04 16:19:05 +08:00
include CoursesHelper
2014-10-23 11:30:34 +08:00
STATUS_ACTIVE = 1
STATUS_CLOSED = 5
STATUS_ARCHIVED = 9
2015-11-26 09:57:53 +08:00
#elasticsearch
include Elasticsearch :: Model
#elasticsearch kaminari init
Kaminari :: Hooks . init
Elasticsearch :: Model :: Response :: Response . __send__ :include , Elasticsearch :: Model :: Response :: Pagination :: Kaminari
2015-12-01 10:33:57 +08:00
settings index : { number_of_shards : 5 } do
2015-11-26 09:57:53 +08:00
mappings dynamic : 'false' do
indexes :name , analyzer : 'smartcn' , index_options : 'offsets'
indexes :description , analyzer : 'smartcn' , index_options : 'offsets'
2015-12-09 12:57:25 +08:00
indexes :updated_at , index : " not_analyzed " , type : " date "
2015-11-26 09:57:53 +08:00
end
end
2014-10-23 11:30:34 +08:00
2016-10-09 16:11:34 +08:00
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
2014-12-02 16:25:49 +08:00
#belongs_to :project, :class_name => 'Course', :foreign_key => :extra, primary_key: :identifier
2014-10-23 11:30:34 +08:00
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表
2016-06-14 10:38:11 +08:00
belongs_to :syllabus
2015-05-19 09:49:26 +08:00
# has_many :bid
2014-10-23 11:30:34 +08:00
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
2015-12-02 17:38:41 +08:00
has_many :org_courses , :dependent = > :destroy
2015-11-17 17:34:50 +08:00
has_many :organizations , :through = > :org_courses
2015-05-19 09:49:26 +08:00
# has_many :homeworks, :through => :homework_for_courses, :source => :bid, :dependent => :destroy
2014-10-23 11:30:34 +08:00
has_many :journals_for_messages , :as = > :jour , :dependent = > :destroy
2015-05-19 17:12:43 +08:00
# has_many :homework_for_courses, :dependent => :destroy
2014-10-23 11:30:34 +08:00
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
2016-11-11 16:50:25 +08:00
has_many :student_work_projects , :dependent = > :destroy
2014-10-23 11:30:34 +08:00
2015-05-19 09:49:26 +08:00
has_many :homework_commons , :dependent = > :destroy
2015-05-19 17:12:43 +08:00
has_many :student_works , :through = > :homework_commons , :dependent = > :destroy
2015-05-19 09:49:26 +08:00
2014-12-02 14:41:36 +08:00
has_many :course_groups , :dependent = > :destroy
2015-08-11 17:11:10 +08:00
# 课程动态
has_many :course_acts , :class_name = > 'CourseActivity' , :as = > :course_act , :dependent = > :destroy
2014-12-02 14:41:36 +08:00
2015-08-12 16:24:27 +08:00
has_many :course_activities
2015-08-29 11:50:08 +08:00
# 课程消息
2015-11-25 15:16:33 +08:00
has_many :course_messages , :class_name = > 'CourseMessage' , :as = > :course_message , :dependent = > :destroy
2015-11-13 16:40:39 +08:00
has_many :exercises , :dependent = > :destroy
2015-11-25 15:16:33 +08:00
# 课程贡献榜
has_many :course_contributor_scores , :dependent = > :destroy
2015-08-12 16:24:27 +08:00
2014-10-23 11:30:34 +08:00
acts_as_taggable
acts_as_nested_set :order = > 'name' , :dependent = > :destroy
2014-12-02 16:25:49 +08:00
acts_as_attachable :view_permission = > :view_course_files ,
2014-10-23 11:30:34 +08:00
:delete_permission = > :manage_files
2015-01-09 19:09:19 +08:00
2016-06-24 15:33:45 +08:00
validates_presence_of :term , :name
2014-11-13 09:59:20 +08:00
validates_format_of :class_period , :with = > / ^[1-9] \ d*$ /
2016-07-04 16:19:05 +08:00
validates_format_of :time , :with = > / ^ \ d{4}$ /
2016-07-08 17:04:17 +08:00
validates_format_of :name , :with = > / ^[^ ]+[a-zA-Z0-9_ \ u4e00- \ u9fa5 \ s \ S]*$ /
2014-11-13 09:59:20 +08:00
validates_length_of :description , :maximum = > 10000
2016-07-04 16:19:05 +08:00
2014-10-23 11:30:34 +08:00
before_save :self_validate
2015-10-20 11:03:47 +08:00
# 公开课程变成私有课程,所有资源都变成私有
2016-01-15 10:29:55 +08:00
after_update :update_files_public , :update_course_ealasticsearch_index
2016-01-20 16:32:47 +08:00
after_create :create_board_sync , :act_as_course_activity , :act_as_course_message , :create_course_ealasticsearch_index
2015-11-26 09:57:53 +08:00
before_destroy :delete_all_members , :delete_course_ealasticsearch_index
2016-08-11 15:58:49 +08:00
after_save :log_infor
2014-10-23 11:30:34 +08:00
safe_attributes 'extra' ,
'time' ,
'name' ,
'extra' ,
'code' ,
'location' ,
'tea_id' ,
'password' ,
'term' ,
'is_public' ,
'description' ,
2014-11-03 17:00:01 +08:00
'class_period' ,
2015-12-08 16:45:26 +08:00
'open_student' ,
2016-06-29 10:45:56 +08:00
'is_delete' ,
2016-10-09 16:11:34 +08:00
'syllabus_id' ,
'end_time' ,
'end_term'
2014-10-23 11:30:34 +08:00
acts_as_customizable
2016-06-27 16:03:07 +08:00
scope :not_deleted , lambda { where ( is_delete : 0 ) }
2014-10-23 11:30:34 +08:00
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 ) }
2015-12-08 16:45:26 +08:00
scope :visible , lambda { | * args | where ( Course . where ( " is_delete =? " , 0 ) . visible_condition ( args . shift || User . current , * args ) ) }
2014-10-23 11:30:34 +08:00
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
}
2015-12-10 10:00:14 +08:00
scope :indexable , lambda { where ( 'is_public = 1 and is_delete = 0' ) }
2015-11-26 09:57:53 +08:00
def self . search ( query )
__elasticsearch__ . search (
{
query : {
multi_match : {
query : query ,
2015-11-26 16:43:10 +08:00
type : " most_fields " ,
operator : " or " ,
fields : [ 'name' , 'description^0.5' ]
2015-11-26 09:57:53 +08:00
}
} ,
2015-11-26 16:43:10 +08:00
sort : {
2015-11-27 15:17:17 +08:00
_score : { order : " desc " } ,
updated_at : { order : " desc " }
2015-11-26 16:43:10 +08:00
} ,
2015-11-26 09:57:53 +08:00
highlight : {
pre_tags : [ '<span class="c_red">' ] ,
post_tags : [ '</span>' ] ,
fields : {
name : { } ,
description : { }
}
}
}
)
end
2014-10-23 11:30:34 +08:00
2016-01-20 15:43:33 +08:00
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
2016-07-04 17:04:06 +08:00
def delete!
update_attribute ( :is_delete , true )
end
2014-10-23 11:30:34 +08:00
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 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 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
2015-10-20 14:24:18 +08:00
2015-10-20 11:03:47 +08:00
def update_files_public
unless self . is_public?
self . attachments . each do | a |
a . update_attributes ( :is_public = > false )
end
end
end
2016-07-04 16:19:05 +08:00
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
2014-10-23 11:30:34 +08:00
# 创建课程讨论区
def create_board_sync
@board = self . boards . build
2015-01-05 16:58:11 +08:00
#self.name=" #{l(:label_borad_course) }"
@board . name = " #{ l ( :label_borad_course ) } " #self.name
2014-10-23 11:30:34 +08:00
@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
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 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
2015-08-11 17:11:10 +08:00
#课程动态公共表记录
def act_as_course_activity
self . course_acts << CourseActivity . new ( :user_id = > self . tea_id , :course_id = > self . id )
end
2015-10-19 15:45:24 +08:00
#创建课程后,给该用户发送消息
def act_as_course_message
self . course_messages << CourseMessage . new ( :user_id = > self . tea_id , :course_id = > self . id , :viewed = > false )
end
2016-01-18 15:48:26 +08:00
2016-06-15 13:44:28 +08:00
2014-10-23 11:30:34 +08:00
#项目与课程分离后,很多课程的名称等信息为空,这些数据信息存储在项目表中!!就是数据兼容的问题
#def name
# read_attribute('name') || Project.find_by_identifier(self.extra).try(:name)
#end
2015-11-26 09:57:53 +08:00
# 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
2016-06-24 15:28:44 +08:00
return if Rails . env . development?
2015-12-10 10:00:14 +08:00
if self . is_public == 1 and self . is_delete == 0 #公开 和 没有被删除的课程才被索引
2015-11-27 18:47:19 +08:00
self . __elasticsearch__ . index_document
2015-11-27 15:17:17 +08:00
end
2015-11-26 09:57:53 +08:00
end
2016-01-18 15:48:26 +08:00
2015-11-26 09:57:53 +08:00
def update_course_ealasticsearch_index
2016-06-24 15:28:44 +08:00
return if Rails . env . development?
2015-12-10 10:00:14 +08:00
if self . is_public == 1 and self . is_delete == 0 #如果是初次更新成为公开或者恢复被删除的情况,会报错,那么这条记录尚未被索引过。没有报错就是更新的其他属性
2015-11-27 18:47:19 +08:00
begin
self . __elasticsearch__ . update_document
rescue = > e
self . __elasticsearch__ . index_document
end
else #如果是更新成为私有的,那么索引就要被删除
begin
self . __elasticsearch__ . delete_document
rescue = > e
end
2015-11-27 15:17:17 +08:00
end
2015-11-26 09:57:53 +08:00
end
2015-11-27 18:47:19 +08:00
2015-11-26 09:57:53 +08:00
def delete_course_ealasticsearch_index
2016-06-24 15:28:44 +08:00
return if Rails . env . development?
2015-11-27 18:47:19 +08:00
begin
self . __elasticsearch__ . delete_document
rescue = > e
2015-11-27 15:17:17 +08:00
2015-11-27 18:47:19 +08:00
end
2015-11-26 09:57:53 +08:00
end
2016-01-18 15:48:26 +08:00
2016-06-27 11:09:25 +08:00
# 延迟生成邀请码
def invite_code
2016-06-27 16:03:07 +08:00
return generate_invite_code
2016-06-27 11:09:25 +08:00
end
2016-06-15 13:44:28 +08:00
# 生成邀请码
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
2016-06-27 16:03:07 +08:00
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?
2016-06-28 19:19:59 +08:00
update_attribute ( :invite_code , code )
2016-06-15 13:44:28 +08:00
end
code
end
2016-06-24 14:59:50 +08:00
def generate_qrcode
ticket = self . qrcode
2016-09-18 10:50:40 +08:00
if ! ticket || ticket . size < 10 || ( Time . now . to_i > self . qrcode_expiretime )
2016-07-08 17:18:09 +08:00
response = Wechat . api . qrcode_create_scene ( invite_code , 2592000 )
2016-06-24 14:59:50 +08:00
logger . debug " response = #{ response } "
self . qrcode = response [ 'ticket' ]
2016-09-18 10:50:40 +08:00
self . qrcode_expiretime = Time . now . to_i + response [ 'expire_seconds' ]
2016-06-24 14:59:50 +08:00
save! && reload
ticket = qrcode
end
ticket
end
2016-01-18 15:48:26 +08:00
2016-08-11 15:58:49 +08:00
def log_infor
2016-08-12 10:20:58 +08:00
Rails . logger . info " # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # course's syllabus_id is #{ self . syllabus_id } . "
2016-08-11 15:58:49 +08:00
end
2014-10-23 11:30:34 +08:00
end
2016-01-18 15:48:26 +08:00
2014-11-13 09:59:20 +08:00
2015-11-26 09:57:53 +08:00
# 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
2015-11-27 10:40:58 +08:00
#Course.where('is_public = 1').import :force=>true
2015-11-26 09:57:53 +08:00
2014-11-13 09:59:20 +08:00