Description:
start update to 5.2
Commit status:
[Not Reviewed]
References:
Diff options:
Comments:
0 Commit comments
0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
r751:01b657de42d9 - - 29 files changed: 765 inserted, 140 deleted
@@ -0,0 +1,14 | |||
|
1 | + %h1 New announcement | |
|
2 | + = error_messages_for :announcement | |
|
3 | + = simple_form_for(@announcement) do |f| | |
|
4 | + .row | |
|
5 | + .col-md-6 | |
|
6 | + = f.input :title | |
|
7 | + = f.input :notes, label: 'Notes (shown internally, used to organize announcements)' | |
|
8 | + = f.input :body | |
|
9 | + = f.input :author | |
|
10 | + = f.input :published | |
|
11 | + = f.input :frontpage, label: 'Display in the front page only?' | |
|
12 | + = f.input :contest_only, label: 'Display in contest only?' | |
|
13 | + = f.button :submit, "Create", class: 'btn btn-primary' | |
|
14 | + = link_to 'Back', announcements_path, class: 'btn btn-default' |
@@ -0,0 +1,24 | |||
|
1 | + %p | |
|
2 | + %b Author: | |
|
3 | + = h @announcement.author | |
|
4 | + %p | |
|
5 | + %b Title: | |
|
6 | + = h @announcement.title | |
|
7 | + %p | |
|
8 | + %b Notes: | |
|
9 | + = h @announcement.notes | |
|
10 | + %p | |
|
11 | + %b Body: | |
|
12 | + = h markdown(@announcement.body) | |
|
13 | + %p | |
|
14 | + %b Published: | |
|
15 | + = h @announcement.published | |
|
16 | + %p | |
|
17 | + %b Show on front page: | |
|
18 | + = h @announcement.frontpage | |
|
19 | + %p | |
|
20 | + %b Show only in contest: | |
|
21 | + = h @announcement.contest_only | |
|
22 | + = link_to 'Edit', edit_announcement_path(@announcement) | |
|
23 | + | | |
|
24 | + = link_to 'Back', announcements_path |
@@ -0,0 +1,179 | |||
|
1 | + # frozen_string_literal: true | |
|
2 | + # | |
|
3 | + # Uncomment this and change the path if necessary to include your own | |
|
4 | + # components. | |
|
5 | + # See https://github.com/plataformatec/simple_form#custom-components to know | |
|
6 | + # more about custom components. | |
|
7 | + # Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f } | |
|
8 | + # | |
|
9 | + # Use this setup block to configure all options available in SimpleForm. | |
|
10 | + SimpleForm.setup do |config| | |
|
11 | + # Wrappers are used by the form builder to generate a | |
|
12 | + # complete input. You can remove any component from the | |
|
13 | + # wrapper, change the order or even add your own to the | |
|
14 | + # stack. The options given below are used to wrap the | |
|
15 | + # whole input. | |
|
16 | + config.wrappers :default, class: :input, | |
|
17 | + hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b| | |
|
18 | + ## Extensions enabled by default | |
|
19 | + # Any of these extensions can be disabled for a | |
|
20 | + # given input by passing: `f.input EXTENSION_NAME => false`. | |
|
21 | + # You can make any of these extensions optional by | |
|
22 | + # renaming `b.use` to `b.optional`. | |
|
23 | + | |
|
24 | + # Determines whether to use HTML5 (:email, :url, ...) | |
|
25 | + # and required attributes | |
|
26 | + b.use :html5 | |
|
27 | + | |
|
28 | + # Calculates placeholders automatically from I18n | |
|
29 | + # You can also pass a string as f.input placeholder: "Placeholder" | |
|
30 | + b.use :placeholder | |
|
31 | + | |
|
32 | + ## Optional extensions | |
|
33 | + # They are disabled unless you pass `f.input EXTENSION_NAME => true` | |
|
34 | + # to the input. If so, they will retrieve the values from the model | |
|
35 | + # if any exists. If you want to enable any of those | |
|
36 | + # extensions by default, you can change `b.optional` to `b.use`. | |
|
37 | + | |
|
38 | + # Calculates maxlength from length validations for string inputs | |
|
39 | + # and/or database column lengths | |
|
40 | + b.optional :maxlength | |
|
41 | + | |
|
42 | + # Calculate minlength from length validations for string inputs | |
|
43 | + b.optional :minlength | |
|
44 | + | |
|
45 | + # Calculates pattern from format validations for string inputs | |
|
46 | + b.optional :pattern | |
|
47 | + | |
|
48 | + # Calculates min and max from length validations for numeric inputs | |
|
49 | + b.optional :min_max | |
|
50 | + | |
|
51 | + # Calculates readonly automatically from readonly attributes | |
|
52 | + b.optional :readonly | |
|
53 | + | |
|
54 | + ## Inputs | |
|
55 | + # b.use :input, class: 'input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
56 | + b.use :label_input | |
|
57 | + b.use :hint, wrap_with: { tag: :span, class: :hint } | |
|
58 | + b.use :error, wrap_with: { tag: :span, class: :error } | |
|
59 | + | |
|
60 | + ## full_messages_for | |
|
61 | + # If you want to display the full error message for the attribute, you can | |
|
62 | + # use the component :full_error, like: | |
|
63 | + # | |
|
64 | + # b.use :full_error, wrap_with: { tag: :span, class: :error } | |
|
65 | + end | |
|
66 | + | |
|
67 | + # The default wrapper to be used by the FormBuilder. | |
|
68 | + config.default_wrapper = :default | |
|
69 | + | |
|
70 | + # Define the way to render check boxes / radio buttons with labels. | |
|
71 | + # Defaults to :nested for bootstrap config. | |
|
72 | + # inline: input + label | |
|
73 | + # nested: label > input | |
|
74 | + config.boolean_style = :nested | |
|
75 | + | |
|
76 | + # Default class for buttons | |
|
77 | + config.button_class = 'btn' | |
|
78 | + | |
|
79 | + # Method used to tidy up errors. Specify any Rails Array method. | |
|
80 | + # :first lists the first message for each field. | |
|
81 | + # Use :to_sentence to list all errors for each field. | |
|
82 | + # config.error_method = :first | |
|
83 | + | |
|
84 | + # Default tag used for error notification helper. | |
|
85 | + config.error_notification_tag = :div | |
|
86 | + | |
|
87 | + # CSS class to add for error notification helper. | |
|
88 | + config.error_notification_class = 'error_notification' | |
|
89 | + | |
|
90 | + # Series of attempts to detect a default label method for collection. | |
|
91 | + # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] | |
|
92 | + | |
|
93 | + # Series of attempts to detect a default value method for collection. | |
|
94 | + # config.collection_value_methods = [ :id, :to_s ] | |
|
95 | + | |
|
96 | + # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. | |
|
97 | + # config.collection_wrapper_tag = nil | |
|
98 | + | |
|
99 | + # You can define the class to use on all collection wrappers. Defaulting to none. | |
|
100 | + # config.collection_wrapper_class = nil | |
|
101 | + | |
|
102 | + # You can wrap each item in a collection of radio/check boxes with a tag, | |
|
103 | + # defaulting to :span. | |
|
104 | + # config.item_wrapper_tag = :span | |
|
105 | + | |
|
106 | + # You can define a class to use in all item wrappers. Defaulting to none. | |
|
107 | + # config.item_wrapper_class = nil | |
|
108 | + | |
|
109 | + # How the label text should be generated altogether with the required text. | |
|
110 | + # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" } | |
|
111 | + | |
|
112 | + # You can define the class to use on all labels. Default is nil. | |
|
113 | + # config.label_class = nil | |
|
114 | + | |
|
115 | + # You can define the default class to be used on forms. Can be overriden | |
|
116 | + # with `html: { :class }`. Defaulting to none. | |
|
117 | + # config.default_form_class = nil | |
|
118 | + | |
|
119 | + # You can define which elements should obtain additional classes | |
|
120 | + # config.generate_additional_classes_for = [:wrapper, :label, :input] | |
|
121 | + | |
|
122 | + # Whether attributes are required by default (or not). Default is true. | |
|
123 | + # config.required_by_default = true | |
|
124 | + | |
|
125 | + # Tell browsers whether to use the native HTML5 validations (novalidate form option). | |
|
126 | + # These validations are enabled in SimpleForm's internal config but disabled by default | |
|
127 | + # in this configuration, which is recommended due to some quirks from different browsers. | |
|
128 | + # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, | |
|
129 | + # change this configuration to true. | |
|
130 | + config.browser_validations = false | |
|
131 | + | |
|
132 | + # Collection of methods to detect if a file type was given. | |
|
133 | + # config.file_methods = [ :mounted_as, :file?, :public_filename, :attached? ] | |
|
134 | + | |
|
135 | + # Custom mappings for input types. This should be a hash containing a regexp | |
|
136 | + # to match as key, and the input type that will be used when the field name | |
|
137 | + # matches the regexp as value. | |
|
138 | + # config.input_mappings = { /count/ => :integer } | |
|
139 | + | |
|
140 | + # Custom wrappers for input types. This should be a hash containing an input | |
|
141 | + # type as key and the wrapper that will be used for all inputs with specified type. | |
|
142 | + # config.wrapper_mappings = { string: :prepend } | |
|
143 | + | |
|
144 | + # Namespaces where SimpleForm should look for custom input classes that | |
|
145 | + # override default inputs. | |
|
146 | + # config.custom_inputs_namespaces << "CustomInputs" | |
|
147 | + | |
|
148 | + # Default priority for time_zone inputs. | |
|
149 | + # config.time_zone_priority = nil | |
|
150 | + | |
|
151 | + # Default priority for country inputs. | |
|
152 | + # config.country_priority = nil | |
|
153 | + | |
|
154 | + # When false, do not use translations for labels. | |
|
155 | + # config.translate_labels = true | |
|
156 | + | |
|
157 | + # Automatically discover new inputs in Rails' autoload path. | |
|
158 | + # config.inputs_discovery = true | |
|
159 | + | |
|
160 | + # Cache SimpleForm inputs discovery | |
|
161 | + # config.cache_discovery = !Rails.env.development? | |
|
162 | + | |
|
163 | + # Default class for inputs | |
|
164 | + # config.input_class = nil | |
|
165 | + | |
|
166 | + # Define the default class of the input wrapper of the boolean input. | |
|
167 | + config.boolean_label_class = 'checkbox' | |
|
168 | + | |
|
169 | + # Defines if the default input wrapper class should be included in radio | |
|
170 | + # collection wrappers. | |
|
171 | + # config.include_default_input_wrapper_class = true | |
|
172 | + | |
|
173 | + # Defines which i18n scope will be used in Simple Form. | |
|
174 | + # config.i18n_scope = 'simple_form' | |
|
175 | + | |
|
176 | + # Defines validation classes to the input_field. By default it's nil. | |
|
177 | + # config.input_field_valid_class = 'is-valid' | |
|
178 | + # config.input_field_error_class = 'is-invalid' | |
|
179 | + end |
@@ -0,0 +1,439 | |||
|
1 | + # frozen_string_literal: true | |
|
2 | + | |
|
3 | + # Please do not make direct changes to this file! | |
|
4 | + # This generator is maintained by the community around simple_form-bootstrap: | |
|
5 | + # https://github.com/rafaelfranca/simple_form-bootstrap | |
|
6 | + # All future development, tests, and organization should happen there. | |
|
7 | + # Background history: https://github.com/plataformatec/simple_form/issues/1561 | |
|
8 | + | |
|
9 | + # Uncomment this and change the path if necessary to include your own | |
|
10 | + # components. | |
|
11 | + # See https://github.com/plataformatec/simple_form#custom-components | |
|
12 | + # to know more about custom components. | |
|
13 | + # Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f } | |
|
14 | + | |
|
15 | + # Use this setup block to configure all options available in SimpleForm. | |
|
16 | + SimpleForm.setup do |config| | |
|
17 | + # Default class for buttons | |
|
18 | + config.button_class = 'btn' | |
|
19 | + | |
|
20 | + # Define the default class of the input wrapper of the boolean input. | |
|
21 | + config.boolean_label_class = 'form-check-label' | |
|
22 | + | |
|
23 | + # How the label text should be generated altogether with the required text. | |
|
24 | + config.label_text = lambda { |label, required, explicit_label| "#{label} #{required}" } | |
|
25 | + | |
|
26 | + # Define the way to render check boxes / radio buttons with labels. | |
|
27 | + config.boolean_style = :inline | |
|
28 | + | |
|
29 | + # You can wrap each item in a collection of radio/check boxes with a tag | |
|
30 | + config.item_wrapper_tag = :div | |
|
31 | + | |
|
32 | + # Defines if the default input wrapper class should be included in radio | |
|
33 | + # collection wrappers. | |
|
34 | + config.include_default_input_wrapper_class = false | |
|
35 | + | |
|
36 | + # CSS class to add for error notification helper. | |
|
37 | + config.error_notification_class = 'alert alert-danger' | |
|
38 | + | |
|
39 | + # Method used to tidy up errors. Specify any Rails Array method. | |
|
40 | + # :first lists the first message for each field. | |
|
41 | + # :to_sentence to list all errors for each field. | |
|
42 | + config.error_method = :to_sentence | |
|
43 | + | |
|
44 | + # add validation classes to `input_field` | |
|
45 | + config.input_field_error_class = 'is-invalid' | |
|
46 | + config.input_field_valid_class = 'is-valid' | |
|
47 | + | |
|
48 | + | |
|
49 | + # vertical forms | |
|
50 | + # | |
|
51 | + # vertical default_wrapper | |
|
52 | + config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
53 | + b.use :html5 | |
|
54 | + b.use :placeholder | |
|
55 | + b.optional :maxlength | |
|
56 | + b.optional :minlength | |
|
57 | + b.optional :pattern | |
|
58 | + b.optional :min_max | |
|
59 | + b.optional :readonly | |
|
60 | + b.use :label, class: 'form-control-label' | |
|
61 | + b.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
62 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
63 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
64 | + end | |
|
65 | + | |
|
66 | + # vertical input for boolean | |
|
67 | + config.wrappers :vertical_boolean, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
68 | + b.use :html5 | |
|
69 | + b.optional :readonly | |
|
70 | + b.wrapper :form_check_wrapper, tag: 'div', class: 'form-check' do |bb| | |
|
71 | + bb.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
72 | + bb.use :label, class: 'form-check-label' | |
|
73 | + bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
74 | + bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
75 | + end | |
|
76 | + end | |
|
77 | + | |
|
78 | + # vertical input for radio buttons and check boxes | |
|
79 | + config.wrappers :vertical_collection, item_wrapper_class: 'form-check', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
80 | + b.use :html5 | |
|
81 | + b.optional :readonly | |
|
82 | + b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba| | |
|
83 | + ba.use :label_text | |
|
84 | + end | |
|
85 | + b.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
86 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
87 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
88 | + end | |
|
89 | + | |
|
90 | + # vertical input for inline radio buttons and check boxes | |
|
91 | + config.wrappers :vertical_collection_inline, item_wrapper_class: 'form-check form-check-inline', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
92 | + b.use :html5 | |
|
93 | + b.optional :readonly | |
|
94 | + b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba| | |
|
95 | + ba.use :label_text | |
|
96 | + end | |
|
97 | + b.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
98 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
99 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
100 | + end | |
|
101 | + | |
|
102 | + # vertical file input | |
|
103 | + config.wrappers :vertical_file, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
104 | + b.use :html5 | |
|
105 | + b.use :placeholder | |
|
106 | + b.optional :maxlength | |
|
107 | + b.optional :minlength | |
|
108 | + b.optional :readonly | |
|
109 | + b.use :label | |
|
110 | + b.use :input, class: 'form-control-file', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
111 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
112 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
113 | + end | |
|
114 | + | |
|
115 | + # vertical multi select | |
|
116 | + config.wrappers :vertical_multi_select, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
117 | + b.use :html5 | |
|
118 | + b.optional :readonly | |
|
119 | + b.use :label, class: 'form-control-label' | |
|
120 | + b.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |ba| | |
|
121 | + ba.use :input, class: 'form-control mx-1', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
122 | + end | |
|
123 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
124 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
125 | + end | |
|
126 | + | |
|
127 | + # vertical range input | |
|
128 | + config.wrappers :vertical_range, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
129 | + b.use :html5 | |
|
130 | + b.use :placeholder | |
|
131 | + b.optional :readonly | |
|
132 | + b.optional :step | |
|
133 | + b.use :label | |
|
134 | + b.use :input, class: 'form-control-range', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
135 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
136 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
137 | + end | |
|
138 | + | |
|
139 | + | |
|
140 | + # horizontal forms | |
|
141 | + # | |
|
142 | + # horizontal default_wrapper | |
|
143 | + config.wrappers :horizontal_form, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
144 | + b.use :html5 | |
|
145 | + b.use :placeholder | |
|
146 | + b.optional :maxlength | |
|
147 | + b.optional :minlength | |
|
148 | + b.optional :pattern | |
|
149 | + b.optional :min_max | |
|
150 | + b.optional :readonly | |
|
151 | + b.use :label, class: 'col-sm-3 col-form-label' | |
|
152 | + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| | |
|
153 | + ba.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
154 | + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
155 | + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
156 | + end | |
|
157 | + end | |
|
158 | + | |
|
159 | + # horizontal input for boolean | |
|
160 | + config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
161 | + b.use :html5 | |
|
162 | + b.optional :readonly | |
|
163 | + b.wrapper tag: 'label', class: 'col-sm-3' do |ba| | |
|
164 | + ba.use :label_text | |
|
165 | + end | |
|
166 | + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |wr| | |
|
167 | + wr.wrapper :form_check_wrapper, tag: 'div', class: 'form-check' do |bb| | |
|
168 | + bb.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
169 | + bb.use :label, class: 'form-check-label' | |
|
170 | + bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
171 | + bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
172 | + end | |
|
173 | + end | |
|
174 | + end | |
|
175 | + | |
|
176 | + # horizontal input for radio buttons and check boxes | |
|
177 | + config.wrappers :horizontal_collection, item_wrapper_class: 'form-check', tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
178 | + b.use :html5 | |
|
179 | + b.optional :readonly | |
|
180 | + b.use :label, class: 'col-sm-3 form-control-label' | |
|
181 | + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| | |
|
182 | + ba.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
183 | + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
184 | + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
185 | + end | |
|
186 | + end | |
|
187 | + | |
|
188 | + # horizontal input for inline radio buttons and check boxes | |
|
189 | + config.wrappers :horizontal_collection_inline, item_wrapper_class: 'form-check form-check-inline', tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
190 | + b.use :html5 | |
|
191 | + b.optional :readonly | |
|
192 | + b.use :label, class: 'col-sm-3 form-control-label' | |
|
193 | + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| | |
|
194 | + ba.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
195 | + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
196 | + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
197 | + end | |
|
198 | + end | |
|
199 | + | |
|
200 | + # horizontal file input | |
|
201 | + config.wrappers :horizontal_file, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
202 | + b.use :html5 | |
|
203 | + b.use :placeholder | |
|
204 | + b.optional :maxlength | |
|
205 | + b.optional :minlength | |
|
206 | + b.optional :readonly | |
|
207 | + b.use :label, class: 'col-sm-3 form-control-label' | |
|
208 | + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| | |
|
209 | + ba.use :input, error_class: 'is-invalid', valid_class: 'is-valid' | |
|
210 | + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
211 | + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
212 | + end | |
|
213 | + end | |
|
214 | + | |
|
215 | + # horizontal multi select | |
|
216 | + config.wrappers :horizontal_multi_select, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
217 | + b.use :html5 | |
|
218 | + b.optional :readonly | |
|
219 | + b.use :label, class: 'col-sm-3 control-label' | |
|
220 | + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| | |
|
221 | + ba.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |bb| | |
|
222 | + bb.use :input, class: 'form-control mx-1', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
223 | + end | |
|
224 | + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
225 | + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
226 | + end | |
|
227 | + end | |
|
228 | + | |
|
229 | + # horizontal range input | |
|
230 | + config.wrappers :horizontal_range, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
231 | + b.use :html5 | |
|
232 | + b.use :placeholder | |
|
233 | + b.optional :readonly | |
|
234 | + b.optional :step | |
|
235 | + b.use :label, class: 'col-sm-3 form-control-label' | |
|
236 | + b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba| | |
|
237 | + ba.use :input, class: 'form-control-range', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
238 | + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
239 | + ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
240 | + end | |
|
241 | + end | |
|
242 | + | |
|
243 | + | |
|
244 | + # inline forms | |
|
245 | + # | |
|
246 | + # inline default_wrapper | |
|
247 | + config.wrappers :inline_form, tag: 'span', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
248 | + b.use :html5 | |
|
249 | + b.use :placeholder | |
|
250 | + b.optional :maxlength | |
|
251 | + b.optional :minlength | |
|
252 | + b.optional :pattern | |
|
253 | + b.optional :min_max | |
|
254 | + b.optional :readonly | |
|
255 | + b.use :label, class: 'sr-only' | |
|
256 | + | |
|
257 | + b.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
258 | + b.use :error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
259 | + b.optional :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
260 | + end | |
|
261 | + | |
|
262 | + # inline input for boolean | |
|
263 | + config.wrappers :inline_boolean, tag: 'span', class: 'form-check flex-wrap justify-content-start mr-sm-2', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
264 | + b.use :html5 | |
|
265 | + b.optional :readonly | |
|
266 | + b.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
267 | + b.use :label, class: 'form-check-label' | |
|
268 | + b.use :error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
269 | + b.optional :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
270 | + end | |
|
271 | + | |
|
272 | + | |
|
273 | + # bootstrap custom forms | |
|
274 | + # | |
|
275 | + # custom input for boolean | |
|
276 | + config.wrappers :custom_boolean, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
277 | + b.use :html5 | |
|
278 | + b.optional :readonly | |
|
279 | + b.wrapper :form_check_wrapper, tag: 'div', class: 'custom-control custom-checkbox' do |bb| | |
|
280 | + bb.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
281 | + bb.use :label, class: 'custom-control-label' | |
|
282 | + bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
283 | + bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
284 | + end | |
|
285 | + end | |
|
286 | + | |
|
287 | + config.wrappers :custom_boolean_switch, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
288 | + b.use :html5 | |
|
289 | + b.optional :readonly | |
|
290 | + b.wrapper :form_check_wrapper, tag: 'div', class: 'custom-control custom-checkbox-switch' do |bb| | |
|
291 | + bb.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
292 | + bb.use :label, class: 'custom-control-label' | |
|
293 | + bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
294 | + bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
295 | + end | |
|
296 | + end | |
|
297 | + | |
|
298 | + # custom input for radio buttons and check boxes | |
|
299 | + config.wrappers :custom_collection, item_wrapper_class: 'custom-control', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
300 | + b.use :html5 | |
|
301 | + b.optional :readonly | |
|
302 | + b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba| | |
|
303 | + ba.use :label_text | |
|
304 | + end | |
|
305 | + b.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
306 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
307 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
308 | + end | |
|
309 | + | |
|
310 | + # custom input for inline radio buttons and check boxes | |
|
311 | + config.wrappers :custom_collection_inline, item_wrapper_class: 'custom-control custom-control-inline', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
312 | + b.use :html5 | |
|
313 | + b.optional :readonly | |
|
314 | + b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba| | |
|
315 | + ba.use :label_text | |
|
316 | + end | |
|
317 | + b.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
318 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
319 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
320 | + end | |
|
321 | + | |
|
322 | + # custom file input | |
|
323 | + config.wrappers :custom_file, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
324 | + b.use :html5 | |
|
325 | + b.use :placeholder | |
|
326 | + b.optional :maxlength | |
|
327 | + b.optional :minlength | |
|
328 | + b.optional :readonly | |
|
329 | + b.use :label, class: 'form-control-label' | |
|
330 | + b.wrapper :custom_file_wrapper, tag: 'div', class: 'custom-file' do |ba| | |
|
331 | + ba.use :input, class: 'custom-file-input', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
332 | + ba.use :label, class: 'custom-file-label' | |
|
333 | + ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
334 | + end | |
|
335 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
336 | + end | |
|
337 | + | |
|
338 | + # custom multi select | |
|
339 | + config.wrappers :custom_multi_select, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
340 | + b.use :html5 | |
|
341 | + b.optional :readonly | |
|
342 | + b.use :label, class: 'form-control-label' | |
|
343 | + b.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |ba| | |
|
344 | + ba.use :input, class: 'custom-select mx-1', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
345 | + end | |
|
346 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
347 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
348 | + end | |
|
349 | + | |
|
350 | + # custom range input | |
|
351 | + config.wrappers :custom_range, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
352 | + b.use :html5 | |
|
353 | + b.use :placeholder | |
|
354 | + b.optional :readonly | |
|
355 | + b.optional :step | |
|
356 | + b.use :label, class: 'form-control-label' | |
|
357 | + b.use :input, class: 'custom-range', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
358 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
359 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
360 | + end | |
|
361 | + | |
|
362 | + | |
|
363 | + # Input Group - custom component | |
|
364 | + # see example app and config at https://github.com/rafaelfranca/simple_form-bootstrap | |
|
365 | + # config.wrappers :input_group, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
366 | + # b.use :html5 | |
|
367 | + # b.use :placeholder | |
|
368 | + # b.optional :maxlength | |
|
369 | + # b.optional :minlength | |
|
370 | + # b.optional :pattern | |
|
371 | + # b.optional :min_max | |
|
372 | + # b.optional :readonly | |
|
373 | + # b.use :label, class: 'form-control-label' | |
|
374 | + # b.wrapper :input_group_tag, tag: 'div', class: 'input-group' do |ba| | |
|
375 | + # ba.optional :prepend | |
|
376 | + # ba.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
377 | + # ba.optional :append | |
|
378 | + # end | |
|
379 | + # b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' } | |
|
380 | + # b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
381 | + # end | |
|
382 | + | |
|
383 | + | |
|
384 | + # Floating Labels form | |
|
385 | + # | |
|
386 | + # floating labels default_wrapper | |
|
387 | + config.wrappers :floating_labels_form, tag: 'div', class: 'form-label-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
388 | + b.use :html5 | |
|
389 | + b.use :placeholder | |
|
390 | + b.optional :maxlength | |
|
391 | + b.optional :minlength | |
|
392 | + b.optional :pattern | |
|
393 | + b.optional :min_max | |
|
394 | + b.optional :readonly | |
|
395 | + b.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
396 | + b.use :label, class: 'form-control-label' | |
|
397 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
398 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
399 | + end | |
|
400 | + | |
|
401 | + # custom multi select | |
|
402 | + config.wrappers :floating_labels_select, tag: 'div', class: 'form-label-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b| | |
|
403 | + b.use :html5 | |
|
404 | + b.optional :readonly | |
|
405 | + b.use :input, class: 'custom-select custom-select-lg', error_class: 'is-invalid', valid_class: 'is-valid' | |
|
406 | + b.use :label, class: 'form-control-label' | |
|
407 | + b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' } | |
|
408 | + b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' } | |
|
409 | + end | |
|
410 | + | |
|
411 | + | |
|
412 | + # The default wrapper to be used by the FormBuilder. | |
|
413 | + config.default_wrapper = :vertical_form | |
|
414 | + | |
|
415 | + # Custom wrappers for input types. This should be a hash containing an input | |
|
416 | + # type as key and the wrapper that will be used for all inputs with specified type. | |
|
417 | + config.wrapper_mappings = { | |
|
418 | + boolean: :vertical_boolean, | |
|
419 | + check_boxes: :vertical_collection, | |
|
420 | + date: :vertical_multi_select, | |
|
421 | + datetime: :vertical_multi_select, | |
|
422 | + file: :vertical_file, | |
|
423 | + radio_buttons: :vertical_collection, | |
|
424 | + range: :vertical_range, | |
|
425 | + time: :vertical_multi_select | |
|
426 | + } | |
|
427 | + | |
|
428 | + # enable custom form wrappers | |
|
429 | + # config.wrapper_mappings = { | |
|
430 | + # boolean: :custom_boolean, | |
|
431 | + # check_boxes: :custom_collection, | |
|
432 | + # date: :custom_multi_select, | |
|
433 | + # datetime: :custom_multi_select, | |
|
434 | + # file: :custom_file, | |
|
435 | + # radio_buttons: :custom_collection, | |
|
436 | + # range: :custom_range, | |
|
437 | + # time: :custom_multi_select | |
|
438 | + # } | |
|
439 | + end |
@@ -0,0 +1,31 | |||
|
1 | + en: | |
|
2 | + simple_form: | |
|
3 | + "yes": 'Yes' | |
|
4 | + "no": 'No' | |
|
5 | + required: | |
|
6 | + text: 'required' | |
|
7 | + mark: '*' | |
|
8 | + # You can uncomment the line below if you need to overwrite the whole required html. | |
|
9 | + # When using html, text and mark won't be used. | |
|
10 | + # html: '<abbr title="required">*</abbr>' | |
|
11 | + error_notification: | |
|
12 | + default_message: "Please review the problems below:" | |
|
13 | + # Examples | |
|
14 | + # labels: | |
|
15 | + # defaults: | |
|
16 | + # password: 'Password' | |
|
17 | + # user: | |
|
18 | + # new: | |
|
19 | + # email: 'E-mail to sign in.' | |
|
20 | + # edit: | |
|
21 | + # email: 'E-mail.' | |
|
22 | + # hints: | |
|
23 | + # defaults: | |
|
24 | + # username: 'User name to sign in.' | |
|
25 | + # password: 'No special characters, please.' | |
|
26 | + # include_blanks: | |
|
27 | + # defaults: | |
|
28 | + # age: 'Rather not say' | |
|
29 | + # prompts: | |
|
30 | + # defaults: | |
|
31 | + # age: 'Select your age' |
@@ -0,0 +1,12 | |||
|
1 | + -# frozen_string_literal: true | |
|
2 | + = simple_form_for(@<%= singular_table_name %>) do |f| | |
|
3 | + = f.error_notification | |
|
4 | + = f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? | |
|
5 | + | |
|
6 | + .form-inputs | |
|
7 | + <%- attributes.each do |attribute| -%> | |
|
8 | + = f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> | |
|
9 | + <%- end -%> | |
|
10 | + | |
|
11 | + .form-actions | |
|
12 | + = f.button :submit |
@@ -0,0 +1,5 | |||
|
1 | + require "test_helper" | |
|
2 | + | |
|
3 | + class ApplicationSystemTestCase < ActionDispatch::SystemTestCase | |
|
4 | + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] | |
|
5 | + end |
@@ -0,0 +1,37 | |||
|
1 | + require "application_system_test_case" | |
|
2 | + | |
|
3 | + class AnnouncementsTest < ApplicationSystemTestCase | |
|
4 | + test "add new announcement" do | |
|
5 | + visit root_path | |
|
6 | + fill_in "Login", with: "admin" | |
|
7 | + fill_in "Password", with: "admin" | |
|
8 | + click_on "Login" | |
|
9 | + | |
|
10 | + assert_text "MAIN" | |
|
11 | + assert_text "Submission" | |
|
12 | + | |
|
13 | + within :css, 'header' do | |
|
14 | + click_on "Manage" | |
|
15 | + click_on "Announcements" | |
|
16 | + end | |
|
17 | + assert_text "+ Add announcement" | |
|
18 | + | |
|
19 | + click_on "Add announcement", match: :first | |
|
20 | + | |
|
21 | + fill_in 'Title', with: 'test' | |
|
22 | + fill_in 'Body', with: 'test body 12345' | |
|
23 | + check 'Published' | |
|
24 | + | |
|
25 | + click_on 'Create' | |
|
26 | + | |
|
27 | + visit list_main_path | |
|
28 | + | |
|
29 | + assert_text "test body 12345" | |
|
30 | + | |
|
31 | + end | |
|
32 | + # test "visiting the index" do | |
|
33 | + # visit announcements_url | |
|
34 | + # | |
|
35 | + # assert_selector "h1", text: "Announcement" | |
|
36 | + # end | |
|
37 | + end |
@@ -1,115 +1,116 | |||
|
1 | 1 | source 'https://rubygems.org' |
|
2 | 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } |
|
3 | 3 | |
|
4 | 4 | #rails |
|
5 | 5 | gem 'rails', '~>5.2' |
|
6 | 6 | gem 'activerecord-session_store' |
|
7 | 7 | gem 'puma' |
|
8 | 8 | |
|
9 | 9 | # Reduces boot times through caching; required in config/boot.rb |
|
10 | 10 | gem 'bootsnap', '>= 1.1.0', require: false |
|
11 | 11 | |
|
12 | 12 | # Bundle edge Rails instead: |
|
13 | 13 | # gem 'rails', :git => 'git://github.com/rails/rails.git' |
|
14 | 14 | |
|
15 | 15 | #---------------- database --------------------- |
|
16 | 16 | #the database |
|
17 | 17 | gem 'mysql2' |
|
18 | 18 | #for testing |
|
19 | 19 | gem 'sqlite3' |
|
20 | 20 | gem 'rails-controller-testing' |
|
21 | 21 | #for dumping database into yaml |
|
22 | 22 | gem 'yaml_db' |
|
23 | 23 | |
|
24 | 24 | |
|
25 | 25 | #------------- assset pipeline ----------------- |
|
26 | 26 | # Gems used only for assets and not required |
|
27 | 27 | # in production environments by default. |
|
28 | 28 | #sass-rails is depricated |
|
29 | 29 | #gem 'sass-rails' |
|
30 | 30 | gem 'sassc-rails' |
|
31 | 31 | gem 'coffee-rails' |
|
32 | 32 | |
|
33 | 33 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes |
|
34 | 34 | # gem 'therubyracer', :platforms => :ruby |
|
35 | 35 | |
|
36 | 36 | gem 'uglifier' |
|
37 | 37 | |
|
38 | 38 | gem 'haml' |
|
39 | 39 | gem 'haml-rails' |
|
40 | 40 | |
|
41 | 41 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks |
|
42 | 42 | #gem 'turbolinks', '~> 5' |
|
43 | 43 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder |
|
44 | 44 | gem 'jbuilder', '~> 2.5' |
|
45 | 45 | |
|
46 | 46 | |
|
47 | 47 | #in-place editor |
|
48 | 48 | gem 'best_in_place', '~> 3.0.1' |
|
49 | 49 | |
|
50 | 50 | # jquery addition |
|
51 | 51 | gem 'jquery-rails' |
|
52 | 52 | gem 'jquery-ui-rails' |
|
53 | 53 | gem 'jquery-timepicker-addon-rails' |
|
54 | 54 | gem 'jquery-tablesorter' |
|
55 | 55 | gem 'jquery-countdown-rails' |
|
56 | 56 | |
|
57 | 57 | #syntax highlighter |
|
58 | 58 | gem 'rouge' |
|
59 | 59 | |
|
60 | 60 | #bootstrap add-ons |
|
61 | 61 | gem 'bootstrap-sass', '~> 3.4.1' |
|
62 | 62 | gem 'bootstrap-switch-rails' |
|
63 | 63 | gem 'bootstrap-toggle-rails' |
|
64 | 64 | gem 'autoprefixer-rails' |
|
65 | 65 | gem 'momentjs-rails' |
|
66 | 66 | gem 'rails_bootstrap_sortable' |
|
67 | 67 | gem 'bootstrap-datepicker-rails' |
|
68 | 68 | gem 'bootstrap3-datetimepicker-rails' |
|
69 | 69 | gem 'jquery-datatables-rails' |
|
70 | 70 | |
|
71 | 71 | #----------- user interface ----------------- |
|
72 | + gem 'simple_form' | |
|
72 | 73 | #select 2 |
|
73 | 74 | gem 'select2-rails' |
|
74 | 75 | #ace editor |
|
75 | 76 | gem 'ace-rails-ap' |
|
76 | 77 | #paginator |
|
77 | 78 | gem 'will_paginate', '~> 3.0.7' |
|
78 | 79 | |
|
79 | 80 | gem 'mail' |
|
80 | 81 | gem 'rdiscount' |
|
81 | 82 | gem 'dynamic_form' |
|
82 | 83 | gem 'in_place_editing' |
|
83 | 84 | #gem 'verification', :git => 'https://github.com/sikachu/verification.git' |
|
84 | 85 | |
|
85 | 86 | |
|
86 | 87 | #---------------- testiing ----------------------- |
|
87 | 88 | gem 'minitest-reporters' |
|
88 | 89 | |
|
89 | 90 | #---------------- for console -------------------- |
|
90 | 91 | gem 'fuzzy-string-match' |
|
91 | 92 | |
|
92 | 93 | |
|
93 | 94 | group :development, :test do |
|
94 | 95 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console |
|
95 | 96 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] |
|
96 | 97 | end |
|
97 | 98 | |
|
98 | 99 | group :development do |
|
99 | 100 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. |
|
100 | 101 | gem 'web-console', '>= 3.3.0' |
|
101 | 102 | gem 'listen', '>= 3.0.5', '< 3.2' |
|
102 | 103 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring |
|
103 | 104 | gem 'spring' |
|
104 | 105 | gem 'spring-watcher-listen', '~> 2.0.0' |
|
105 | 106 | end |
|
106 | 107 | |
|
107 | 108 | group :test do |
|
108 | 109 | # Adds support for Capybara system testing and selenium driver |
|
109 | 110 | gem 'capybara', '>= 2.15' |
|
110 | 111 | gem 'selenium-webdriver' |
|
111 | 112 | # Easy installation and use of chromedriver to run system tests with Chrome |
|
112 | 113 | #gem 'chromedriver-helper' |
|
113 | 114 | gem 'webdriver' |
|
114 | 115 | end |
|
115 | 116 |
@@ -1,333 +1,337 | |||
|
1 | 1 | GEM |
|
2 | 2 | remote: https://rubygems.org/ |
|
3 | 3 | specs: |
|
4 | 4 | RubyInline (3.12.4) |
|
5 | 5 | ZenTest (~> 4.3) |
|
6 | 6 | ZenTest (4.11.2) |
|
7 | 7 | ace-rails-ap (4.2) |
|
8 | 8 | actioncable (5.2.3) |
|
9 | 9 | actionpack (= 5.2.3) |
|
10 | 10 | nio4r (~> 2.0) |
|
11 | 11 | websocket-driver (>= 0.6.1) |
|
12 | 12 | actionmailer (5.2.3) |
|
13 | 13 | actionpack (= 5.2.3) |
|
14 | 14 | actionview (= 5.2.3) |
|
15 | 15 | activejob (= 5.2.3) |
|
16 | 16 | mail (~> 2.5, >= 2.5.4) |
|
17 | 17 | rails-dom-testing (~> 2.0) |
|
18 | 18 | actionpack (5.2.3) |
|
19 | 19 | actionview (= 5.2.3) |
|
20 | 20 | activesupport (= 5.2.3) |
|
21 | 21 | rack (~> 2.0) |
|
22 | 22 | rack-test (>= 0.6.3) |
|
23 | 23 | rails-dom-testing (~> 2.0) |
|
24 | 24 | rails-html-sanitizer (~> 1.0, >= 1.0.2) |
|
25 | 25 | actionview (5.2.3) |
|
26 | 26 | activesupport (= 5.2.3) |
|
27 | 27 | builder (~> 3.1) |
|
28 | 28 | erubi (~> 1.4) |
|
29 | 29 | rails-dom-testing (~> 2.0) |
|
30 | 30 | rails-html-sanitizer (~> 1.0, >= 1.0.3) |
|
31 | 31 | activejob (5.2.3) |
|
32 | 32 | activesupport (= 5.2.3) |
|
33 | 33 | globalid (>= 0.3.6) |
|
34 | 34 | activemodel (5.2.3) |
|
35 | 35 | activesupport (= 5.2.3) |
|
36 | 36 | activerecord (5.2.3) |
|
37 | 37 | activemodel (= 5.2.3) |
|
38 | 38 | activesupport (= 5.2.3) |
|
39 | 39 | arel (>= 9.0) |
|
40 | 40 | activerecord-session_store (1.1.3) |
|
41 | 41 | actionpack (>= 4.0) |
|
42 | 42 | activerecord (>= 4.0) |
|
43 | 43 | multi_json (~> 1.11, >= 1.11.2) |
|
44 | 44 | rack (>= 1.5.2, < 3) |
|
45 | 45 | railties (>= 4.0) |
|
46 | 46 | activestorage (5.2.3) |
|
47 | 47 | actionpack (= 5.2.3) |
|
48 | 48 | activerecord (= 5.2.3) |
|
49 | 49 | marcel (~> 0.3.1) |
|
50 | 50 | activesupport (5.2.3) |
|
51 | 51 | concurrent-ruby (~> 1.0, >= 1.0.2) |
|
52 | 52 | i18n (>= 0.7, < 2) |
|
53 | 53 | minitest (~> 5.1) |
|
54 | 54 | tzinfo (~> 1.1) |
|
55 | 55 | addressable (2.6.0) |
|
56 | 56 | public_suffix (>= 2.0.2, < 4.0) |
|
57 | 57 | ansi (1.5.0) |
|
58 | 58 | arel (9.0.0) |
|
59 | 59 | autoprefixer-rails (9.5.1.1) |
|
60 | 60 | execjs |
|
61 | 61 | best_in_place (3.0.3) |
|
62 | 62 | actionpack (>= 3.2) |
|
63 | 63 | railties (>= 3.2) |
|
64 | 64 | bindex (0.7.0) |
|
65 | 65 | bootsnap (1.4.4) |
|
66 | 66 | msgpack (~> 1.0) |
|
67 | 67 | bootstrap-datepicker-rails (1.8.0.1) |
|
68 | 68 | railties (>= 3.0) |
|
69 | 69 | bootstrap-sass (3.4.1) |
|
70 | 70 | autoprefixer-rails (>= 5.2.1) |
|
71 | 71 | sassc (>= 2.0.0) |
|
72 | 72 | bootstrap-switch-rails (3.3.4) |
|
73 | 73 | bootstrap-toggle-rails (2.2.1.0) |
|
74 | 74 | bootstrap3-datetimepicker-rails (4.17.47) |
|
75 | 75 | momentjs-rails (>= 2.8.1) |
|
76 | 76 | builder (3.2.3) |
|
77 | 77 | byebug (11.0.1) |
|
78 | 78 | capybara (3.25.0) |
|
79 | 79 | addressable |
|
80 | 80 | mini_mime (>= 0.1.3) |
|
81 | 81 | nokogiri (~> 1.8) |
|
82 | 82 | rack (>= 1.6.0) |
|
83 | 83 | rack-test (>= 0.6.3) |
|
84 | 84 | regexp_parser (~> 1.5) |
|
85 | 85 | xpath (~> 3.2) |
|
86 | 86 | childprocess (1.0.1) |
|
87 | 87 | rake (< 13.0) |
|
88 | 88 | coffee-rails (4.2.2) |
|
89 | 89 | coffee-script (>= 2.2.0) |
|
90 | 90 | railties (>= 4.0.0) |
|
91 | 91 | coffee-script (2.4.1) |
|
92 | 92 | coffee-script-source |
|
93 | 93 | execjs |
|
94 | 94 | coffee-script-source (1.12.2) |
|
95 | 95 | concurrent-ruby (1.1.5) |
|
96 | 96 | crass (1.0.4) |
|
97 | 97 | dynamic_form (1.1.4) |
|
98 | 98 | erubi (1.8.0) |
|
99 | 99 | erubis (2.7.0) |
|
100 | 100 | execjs (2.7.0) |
|
101 | 101 | ffi (1.11.1) |
|
102 | 102 | fuzzy-string-match (1.0.1) |
|
103 | 103 | RubyInline (>= 3.8.6) |
|
104 | 104 | globalid (0.4.2) |
|
105 | 105 | activesupport (>= 4.2.0) |
|
106 | 106 | haml (5.1.0) |
|
107 | 107 | temple (>= 0.8.0) |
|
108 | 108 | tilt |
|
109 | 109 | haml-rails (1.0.0) |
|
110 | 110 | actionpack (>= 4.0.1) |
|
111 | 111 | activesupport (>= 4.0.1) |
|
112 | 112 | haml (>= 4.0.6, < 6.0) |
|
113 | 113 | html2haml (>= 1.0.1) |
|
114 | 114 | railties (>= 4.0.1) |
|
115 | 115 | html2haml (2.2.0) |
|
116 | 116 | erubis (~> 2.7.0) |
|
117 | 117 | haml (>= 4.0, < 6) |
|
118 | 118 | nokogiri (>= 1.6.0) |
|
119 | 119 | ruby_parser (~> 3.5) |
|
120 | 120 | i18n (1.6.0) |
|
121 | 121 | concurrent-ruby (~> 1.0) |
|
122 | 122 | in_place_editing (1.2.0) |
|
123 | 123 | jbuilder (2.9.1) |
|
124 | 124 | activesupport (>= 4.2.0) |
|
125 | 125 | jquery-countdown-rails (2.0.2) |
|
126 | 126 | jquery-datatables-rails (3.4.0) |
|
127 | 127 | actionpack (>= 3.1) |
|
128 | 128 | jquery-rails |
|
129 | 129 | railties (>= 3.1) |
|
130 | 130 | sass-rails |
|
131 | 131 | jquery-rails (4.3.3) |
|
132 | 132 | rails-dom-testing (>= 1, < 3) |
|
133 | 133 | railties (>= 4.2.0) |
|
134 | 134 | thor (>= 0.14, < 2.0) |
|
135 | 135 | jquery-tablesorter (1.26.1) |
|
136 | 136 | railties (>= 3.2, < 6) |
|
137 | 137 | jquery-timepicker-addon-rails (1.4.1) |
|
138 | 138 | railties (>= 3.1) |
|
139 | 139 | jquery-ui-rails (6.0.1) |
|
140 | 140 | railties (>= 3.2.16) |
|
141 | 141 | listen (3.1.5) |
|
142 | 142 | rb-fsevent (~> 0.9, >= 0.9.4) |
|
143 | 143 | rb-inotify (~> 0.9, >= 0.9.7) |
|
144 | 144 | ruby_dep (~> 1.2) |
|
145 | 145 | loofah (2.2.3) |
|
146 | 146 | crass (~> 1.0.2) |
|
147 | 147 | nokogiri (>= 1.5.9) |
|
148 | 148 | mail (2.7.1) |
|
149 | 149 | mini_mime (>= 0.1.1) |
|
150 | 150 | marcel (0.3.3) |
|
151 | 151 | mimemagic (~> 0.3.2) |
|
152 | 152 | method_source (0.9.2) |
|
153 | 153 | mimemagic (0.3.3) |
|
154 | 154 | mini_mime (1.0.1) |
|
155 | 155 | mini_portile2 (2.4.0) |
|
156 | 156 | minitest (5.11.3) |
|
157 | 157 | minitest-reporters (1.3.6) |
|
158 | 158 | ansi |
|
159 | 159 | builder |
|
160 | 160 | minitest (>= 5.0) |
|
161 | 161 | ruby-progressbar |
|
162 | 162 | momentjs-rails (2.20.1) |
|
163 | 163 | railties (>= 3.1) |
|
164 | 164 | msgpack (1.3.0) |
|
165 | 165 | multi_json (1.13.1) |
|
166 | 166 | mysql2 (0.5.2) |
|
167 | 167 | nio4r (2.3.1) |
|
168 | 168 | nokogiri (1.10.3) |
|
169 | 169 | mini_portile2 (~> 2.4.0) |
|
170 | 170 | public_suffix (3.1.1) |
|
171 | 171 | puma (4.0.0) |
|
172 | 172 | nio4r (~> 2.0) |
|
173 | 173 | rack (2.0.7) |
|
174 | 174 | rack-test (1.1.0) |
|
175 | 175 | rack (>= 1.0, < 3) |
|
176 | 176 | rails (5.2.3) |
|
177 | 177 | actioncable (= 5.2.3) |
|
178 | 178 | actionmailer (= 5.2.3) |
|
179 | 179 | actionpack (= 5.2.3) |
|
180 | 180 | actionview (= 5.2.3) |
|
181 | 181 | activejob (= 5.2.3) |
|
182 | 182 | activemodel (= 5.2.3) |
|
183 | 183 | activerecord (= 5.2.3) |
|
184 | 184 | activestorage (= 5.2.3) |
|
185 | 185 | activesupport (= 5.2.3) |
|
186 | 186 | bundler (>= 1.3.0) |
|
187 | 187 | railties (= 5.2.3) |
|
188 | 188 | sprockets-rails (>= 2.0.0) |
|
189 | 189 | rails-controller-testing (1.0.4) |
|
190 | 190 | actionpack (>= 5.0.1.x) |
|
191 | 191 | actionview (>= 5.0.1.x) |
|
192 | 192 | activesupport (>= 5.0.1.x) |
|
193 | 193 | rails-dom-testing (2.0.3) |
|
194 | 194 | activesupport (>= 4.2.0) |
|
195 | 195 | nokogiri (>= 1.6) |
|
196 | 196 | rails-html-sanitizer (1.0.4) |
|
197 | 197 | loofah (~> 2.2, >= 2.2.2) |
|
198 | 198 | rails_bootstrap_sortable (2.0.6) |
|
199 | 199 | momentjs-rails (>= 2.8.3) |
|
200 | 200 | railties (5.2.3) |
|
201 | 201 | actionpack (= 5.2.3) |
|
202 | 202 | activesupport (= 5.2.3) |
|
203 | 203 | method_source |
|
204 | 204 | rake (>= 0.8.7) |
|
205 | 205 | thor (>= 0.19.0, < 2.0) |
|
206 | 206 | rake (12.3.2) |
|
207 | 207 | rb-fsevent (0.10.3) |
|
208 | 208 | rb-inotify (0.10.0) |
|
209 | 209 | ffi (~> 1.0) |
|
210 | 210 | rdiscount (2.2.0.1) |
|
211 | 211 | regexp_parser (1.5.1) |
|
212 | 212 | rouge (3.3.0) |
|
213 | 213 | ruby-progressbar (1.10.0) |
|
214 | 214 | ruby_dep (1.5.0) |
|
215 | 215 | ruby_parser (3.13.1) |
|
216 | 216 | sexp_processor (~> 4.9) |
|
217 | 217 | rubyzip (1.2.3) |
|
218 | 218 | sass (3.7.4) |
|
219 | 219 | sass-listen (~> 4.0.0) |
|
220 | 220 | sass-listen (4.0.0) |
|
221 | 221 | rb-fsevent (~> 0.9, >= 0.9.4) |
|
222 | 222 | rb-inotify (~> 0.9, >= 0.9.7) |
|
223 | 223 | sass-rails (5.0.7) |
|
224 | 224 | railties (>= 4.0.0, < 6) |
|
225 | 225 | sass (~> 3.1) |
|
226 | 226 | sprockets (>= 2.8, < 4.0) |
|
227 | 227 | sprockets-rails (>= 2.0, < 4.0) |
|
228 | 228 | tilt (>= 1.1, < 3) |
|
229 | 229 | sassc (2.0.1) |
|
230 | 230 | ffi (~> 1.9) |
|
231 | 231 | rake |
|
232 | 232 | sassc-rails (2.1.1) |
|
233 | 233 | railties (>= 4.0.0) |
|
234 | 234 | sassc (>= 2.0) |
|
235 | 235 | sprockets (> 3.0) |
|
236 | 236 | sprockets-rails |
|
237 | 237 | tilt |
|
238 | 238 | select2-rails (4.0.3) |
|
239 | 239 | thor (~> 0.14) |
|
240 | 240 | selenium-webdriver (3.142.3) |
|
241 | 241 | childprocess (>= 0.5, < 2.0) |
|
242 | 242 | rubyzip (~> 1.2, >= 1.2.2) |
|
243 | 243 | sexp_processor (4.12.0) |
|
244 | + simple_form (4.1.0) | |
|
245 | + actionpack (>= 5.0) | |
|
246 | + activemodel (>= 5.0) | |
|
244 | 247 | spring (2.1.0) |
|
245 | 248 | spring-watcher-listen (2.0.1) |
|
246 | 249 | listen (>= 2.7, < 4.0) |
|
247 | 250 | spring (>= 1.2, < 3.0) |
|
248 | 251 | sprockets (3.7.2) |
|
249 | 252 | concurrent-ruby (~> 1.0) |
|
250 | 253 | rack (> 1, < 3) |
|
251 | 254 | sprockets-rails (3.2.1) |
|
252 | 255 | actionpack (>= 4.0) |
|
253 | 256 | activesupport (>= 4.0) |
|
254 | 257 | sprockets (>= 3.0.0) |
|
255 | 258 | sqlite3 (1.4.1) |
|
256 | 259 | temple (0.8.1) |
|
257 | 260 | thor (0.20.3) |
|
258 | 261 | thread_safe (0.3.6) |
|
259 | 262 | tilt (2.0.9) |
|
260 | 263 | tzinfo (1.2.5) |
|
261 | 264 | thread_safe (~> 0.1) |
|
262 | 265 | uglifier (4.1.20) |
|
263 | 266 | execjs (>= 0.3.0, < 3) |
|
264 | 267 | web-console (3.7.0) |
|
265 | 268 | actionview (>= 5.0) |
|
266 | 269 | activemodel (>= 5.0) |
|
267 | 270 | bindex (>= 0.4.0) |
|
268 | 271 | railties (>= 5.0) |
|
269 | 272 | webdriver (0.1.0) |
|
270 | 273 | websocket-driver (0.7.1) |
|
271 | 274 | websocket-extensions (>= 0.1.0) |
|
272 | 275 | websocket-extensions (0.1.4) |
|
273 | 276 | will_paginate (3.0.12) |
|
274 | 277 | xpath (3.2.0) |
|
275 | 278 | nokogiri (~> 1.8) |
|
276 | 279 | yaml_db (0.7.0) |
|
277 | 280 | rails (>= 3.0) |
|
278 | 281 | rake (>= 0.8.7) |
|
279 | 282 | |
|
280 | 283 | PLATFORMS |
|
281 | 284 | ruby |
|
282 | 285 | |
|
283 | 286 | DEPENDENCIES |
|
284 | 287 | ace-rails-ap |
|
285 | 288 | activerecord-session_store |
|
286 | 289 | autoprefixer-rails |
|
287 | 290 | best_in_place (~> 3.0.1) |
|
288 | 291 | bootsnap (>= 1.1.0) |
|
289 | 292 | bootstrap-datepicker-rails |
|
290 | 293 | bootstrap-sass (~> 3.4.1) |
|
291 | 294 | bootstrap-switch-rails |
|
292 | 295 | bootstrap-toggle-rails |
|
293 | 296 | bootstrap3-datetimepicker-rails |
|
294 | 297 | byebug |
|
295 | 298 | capybara (>= 2.15) |
|
296 | 299 | coffee-rails |
|
297 | 300 | dynamic_form |
|
298 | 301 | fuzzy-string-match |
|
299 | 302 | haml |
|
300 | 303 | haml-rails |
|
301 | 304 | in_place_editing |
|
302 | 305 | jbuilder (~> 2.5) |
|
303 | 306 | jquery-countdown-rails |
|
304 | 307 | jquery-datatables-rails |
|
305 | 308 | jquery-rails |
|
306 | 309 | jquery-tablesorter |
|
307 | 310 | jquery-timepicker-addon-rails |
|
308 | 311 | jquery-ui-rails |
|
309 | 312 | listen (>= 3.0.5, < 3.2) |
|
310 | 313 | |
|
311 | 314 | minitest-reporters |
|
312 | 315 | momentjs-rails |
|
313 | 316 | mysql2 |
|
314 | 317 | puma |
|
315 | 318 | rails (~> 5.2) |
|
316 | 319 | rails-controller-testing |
|
317 | 320 | rails_bootstrap_sortable |
|
318 | 321 | rdiscount |
|
319 | 322 | rouge |
|
320 | 323 | sassc-rails |
|
321 | 324 | select2-rails |
|
322 | 325 | selenium-webdriver |
|
326 | + simple_form | |
|
323 | 327 | spring |
|
324 | 328 | spring-watcher-listen (~> 2.0.0) |
|
325 | 329 | sqlite3 |
|
326 | 330 | uglifier |
|
327 | 331 | web-console (>= 3.3.0) |
|
328 | 332 | webdriver |
|
329 | 333 | will_paginate (~> 3.0.7) |
|
330 | 334 | yaml_db |
|
331 | 335 | |
|
332 | 336 | BUNDLED WITH |
|
333 | 337 | 1.17.2 |
@@ -1,93 +1,86 | |||
|
1 | 1 | class GradersController < ApplicationController |
|
2 | 2 | |
|
3 | 3 | before_action :admin_authorization |
|
4 | 4 | |
|
5 | - verify :method => :post, :only => ['clear_all', | |
|
6 | - 'start_exam', | |
|
7 | - 'start_grading', | |
|
8 | - 'stop_all', | |
|
9 | - 'clear_terminated'], | |
|
10 | - :redirect_to => {:action => 'index'} | |
|
11 | - | |
|
12 | 5 | def index |
|
13 | 6 | redirect_to :action => 'list' |
|
14 | 7 | end |
|
15 | 8 | |
|
16 | 9 | def list |
|
17 | 10 | @grader_processes = GraderProcess.find_running_graders |
|
18 | 11 | @stalled_processes = GraderProcess.find_stalled_process |
|
19 | 12 | |
|
20 | 13 | @terminated_processes = GraderProcess.find_terminated_graders |
|
21 | 14 | |
|
22 | 15 | @last_task = Task.last |
|
23 | 16 | @last_test_request = TestRequest.last |
|
24 | 17 | @submission = Submission.order("id desc").limit(20) |
|
25 | 18 | @backlog_submission = Submission.where('graded_at is null') |
|
26 | 19 | end |
|
27 | 20 | |
|
28 | 21 | def clear |
|
29 | 22 | grader_proc = GraderProcess.find(params[:id]) |
|
30 | 23 | grader_proc.destroy if grader_proc!=nil |
|
31 | 24 | redirect_to :action => 'list' |
|
32 | 25 | end |
|
33 | 26 | |
|
34 | 27 | def clear_terminated |
|
35 | 28 | GraderProcess.find_terminated_graders.each do |p| |
|
36 | 29 | p.destroy |
|
37 | 30 | end |
|
38 | 31 | redirect_to :action => 'list' |
|
39 | 32 | end |
|
40 | 33 | |
|
41 | 34 | def clear_all |
|
42 | 35 | GraderProcess.all.each do |p| |
|
43 | 36 | p.destroy |
|
44 | 37 | end |
|
45 | 38 | redirect_to :action => 'list' |
|
46 | 39 | end |
|
47 | 40 | |
|
48 | 41 | def view |
|
49 | 42 | if params[:type]=='Task' |
|
50 | 43 | redirect_to :action => 'task', :id => params[:id] |
|
51 | 44 | else |
|
52 | 45 | redirect_to :action => 'test_request', :id => params[:id] |
|
53 | 46 | end |
|
54 | 47 | end |
|
55 | 48 | |
|
56 | 49 | def test_request |
|
57 | 50 | @test_request = TestRequest.find(params[:id]) |
|
58 | 51 | end |
|
59 | 52 | |
|
60 | 53 | def task |
|
61 | 54 | @task = Task.find(params[:id]) |
|
62 | 55 | end |
|
63 | 56 | |
|
64 | 57 | |
|
65 | 58 | # various grader controls |
|
66 | 59 | |
|
67 | 60 | def stop |
|
68 | 61 | grader_proc = GraderProcess.find(params[:id]) |
|
69 | 62 | GraderScript.stop_grader(grader_proc.pid) |
|
70 | 63 | flash[:notice] = 'Grader stopped. It may not disappear now, but it should disappear shortly.' |
|
71 | 64 | redirect_to :action => 'list' |
|
72 | 65 | end |
|
73 | 66 | |
|
74 | 67 | def stop_all |
|
75 | 68 | GraderScript.stop_graders(GraderProcess.find_running_graders + |
|
76 | 69 | GraderProcess.find_stalled_process) |
|
77 | 70 | flash[:notice] = 'Graders stopped. They may not disappear now, but they should disappear shortly.' |
|
78 | 71 | redirect_to :action => 'list' |
|
79 | 72 | end |
|
80 | 73 | |
|
81 | 74 | def start_grading |
|
82 | 75 | GraderScript.start_grader('grading') |
|
83 | 76 | flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request' |
|
84 | 77 | redirect_to :action => 'list' |
|
85 | 78 | end |
|
86 | 79 | |
|
87 | 80 | def start_exam |
|
88 | 81 | GraderScript.start_grader('exam') |
|
89 | 82 | flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request' |
|
90 | 83 | redirect_to :action => 'list' |
|
91 | 84 | end |
|
92 | 85 | |
|
93 | 86 | end |
@@ -1,376 +1,367 | |||
|
1 | 1 | class MainController < ApplicationController |
|
2 | 2 | |
|
3 | 3 | before_action :authenticate, :except => [:index, :login] |
|
4 | 4 | before_action :check_viewability, :except => [:index, :login] |
|
5 | 5 | |
|
6 | 6 | append_before_action :confirm_and_update_start_time, |
|
7 | 7 | :except => [:index, |
|
8 | 8 | :login, |
|
9 | 9 | :confirm_contest_start] |
|
10 | 10 | |
|
11 | 11 | # to prevent log in box to be shown when user logged out of the |
|
12 | 12 | # system only in some tab |
|
13 | 13 | prepend_before_action :reject_announcement_refresh_when_logged_out, |
|
14 | 14 | :only => [:announcements] |
|
15 | 15 | |
|
16 | 16 | before_action :authenticate_by_ip_address, :only => [:list] |
|
17 | 17 | |
|
18 | - # COMMENTED OUT: filter in each action instead | |
|
19 | - # before_filter :verify_time_limit, :only => [:submit] | |
|
20 | - | |
|
21 | - verify :method => :post, :only => [:submit], | |
|
22 | - :redirect_to => { :action => :index } | |
|
23 | - | |
|
24 | - # COMMENT OUT: only need when having high load | |
|
25 | - # caches_action :index, :login | |
|
26 | - | |
|
27 | 18 | # NOTE: This method is not actually needed, 'config/routes.rb' has |
|
28 | 19 | # assigned action login as a default action. |
|
29 | 20 | def index |
|
30 | 21 | redirect_to :action => 'login' |
|
31 | 22 | end |
|
32 | 23 | |
|
33 | 24 | def login |
|
34 | 25 | saved_notice = flash[:notice] |
|
35 | 26 | reset_session |
|
36 | 27 | flash.now[:notice] = saved_notice |
|
37 | 28 | |
|
38 | 29 | # EXPERIMENT: |
|
39 | 30 | # Hide login if in single user mode and the url does not |
|
40 | 31 | # explicitly specify /login |
|
41 | 32 | # |
|
42 | 33 | # logger.info "PATH: #{request.path}" |
|
43 | 34 | # if GraderConfiguration['system.single_user_mode'] and |
|
44 | 35 | # request.path!='/main/login' |
|
45 | 36 | # @hidelogin = true |
|
46 | 37 | # end |
|
47 | 38 | |
|
48 | 39 | @announcements = Announcement.frontpage |
|
49 | 40 | render :action => 'login', :layout => 'empty' |
|
50 | 41 | end |
|
51 | 42 | |
|
52 | 43 | def list |
|
53 | 44 | prepare_list_information |
|
54 | 45 | end |
|
55 | 46 | |
|
56 | 47 | def help |
|
57 | 48 | @user = User.find(session[:user_id]) |
|
58 | 49 | end |
|
59 | 50 | |
|
60 | 51 | def submit |
|
61 | 52 | user = User.find(session[:user_id]) |
|
62 | 53 | |
|
63 | 54 | @submission = Submission.new |
|
64 | 55 | @submission.problem_id = params[:submission][:problem_id] |
|
65 | 56 | @submission.user = user |
|
66 | 57 | @submission.language_id = 0 |
|
67 | 58 | if (params['file']) and (params['file']!='') |
|
68 | 59 | @submission.source = File.open(params['file'].path,'r:UTF-8',&:read) |
|
69 | 60 | @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '') |
|
70 | 61 | @submission.source_filename = params['file'].original_filename |
|
71 | 62 | end |
|
72 | 63 | |
|
73 | 64 | if (params[:editor_text]) |
|
74 | 65 | language = Language.find_by_id(params[:language_id]) |
|
75 | 66 | @submission.source = params[:editor_text] |
|
76 | 67 | @submission.source_filename = "live_edit.#{language.ext}" |
|
77 | 68 | @submission.language = language |
|
78 | 69 | end |
|
79 | 70 | |
|
80 | 71 | @submission.submitted_at = Time.new.gmtime |
|
81 | 72 | @submission.ip_address = request.remote_ip |
|
82 | 73 | |
|
83 | 74 | if GraderConfiguration.time_limit_mode? and user.contest_finished? |
|
84 | 75 | @submission.errors.add(:base,"The contest is over.") |
|
85 | 76 | prepare_list_information |
|
86 | 77 | render :action => 'list' and return |
|
87 | 78 | end |
|
88 | 79 | |
|
89 | 80 | if @submission.valid?(@current_user) |
|
90 | 81 | if @submission.save == false |
|
91 | 82 | flash[:notice] = 'Error saving your submission' |
|
92 | 83 | elsif Task.create(:submission_id => @submission.id, |
|
93 | 84 | :status => Task::STATUS_INQUEUE) == false |
|
94 | 85 | flash[:notice] = 'Error adding your submission to task queue' |
|
95 | 86 | end |
|
96 | 87 | else |
|
97 | 88 | prepare_list_information |
|
98 | 89 | render :action => 'list' and return |
|
99 | 90 | end |
|
100 | 91 | redirect_to edit_submission_path(@submission) |
|
101 | 92 | end |
|
102 | 93 | |
|
103 | 94 | def source |
|
104 | 95 | submission = Submission.find(params[:id]) |
|
105 | 96 | if ((submission.user_id == session[:user_id]) and |
|
106 | 97 | (submission.problem != nil) and |
|
107 | 98 | (submission.problem.available)) |
|
108 | 99 | send_data(submission.source, |
|
109 | 100 | {:filename => submission.download_filename, |
|
110 | 101 | :type => 'text/plain'}) |
|
111 | 102 | else |
|
112 | 103 | flash[:notice] = 'Error viewing source' |
|
113 | 104 | redirect_to :action => 'list' |
|
114 | 105 | end |
|
115 | 106 | end |
|
116 | 107 | |
|
117 | 108 | def compiler_msg |
|
118 | 109 | @submission = Submission.find(params[:id]) |
|
119 | 110 | if @submission.user_id == session[:user_id] |
|
120 | 111 | render :action => 'compiler_msg', :layout => 'empty' |
|
121 | 112 | else |
|
122 | 113 | flash[:notice] = 'Error viewing source' |
|
123 | 114 | redirect_to :action => 'list' |
|
124 | 115 | end |
|
125 | 116 | end |
|
126 | 117 | |
|
127 | 118 | def result |
|
128 | 119 | if !GraderConfiguration.show_grading_result |
|
129 | 120 | redirect_to :action => 'list' and return |
|
130 | 121 | end |
|
131 | 122 | @user = User.find(session[:user_id]) |
|
132 | 123 | @submission = Submission.find(params[:id]) |
|
133 | 124 | if @submission.user!=@user |
|
134 | 125 | flash[:notice] = 'You are not allowed to view result of other users.' |
|
135 | 126 | redirect_to :action => 'list' and return |
|
136 | 127 | end |
|
137 | 128 | prepare_grading_result(@submission) |
|
138 | 129 | end |
|
139 | 130 | |
|
140 | 131 | def load_output |
|
141 | 132 | if !GraderConfiguration.show_grading_result or params[:num]==nil |
|
142 | 133 | redirect_to :action => 'list' and return |
|
143 | 134 | end |
|
144 | 135 | @user = User.find(session[:user_id]) |
|
145 | 136 | @submission = Submission.find(params[:id]) |
|
146 | 137 | if @submission.user!=@user |
|
147 | 138 | flash[:notice] = 'You are not allowed to view result of other users.' |
|
148 | 139 | redirect_to :action => 'list' and return |
|
149 | 140 | end |
|
150 | 141 | case_num = params[:num].to_i |
|
151 | 142 | out_filename = output_filename(@user.login, |
|
152 | 143 | @submission.problem.name, |
|
153 | 144 | @submission.id, |
|
154 | 145 | case_num) |
|
155 | 146 | if !FileTest.exists?(out_filename) |
|
156 | 147 | flash[:notice] = 'Output not found.' |
|
157 | 148 | redirect_to :action => 'list' and return |
|
158 | 149 | end |
|
159 | 150 | |
|
160 | 151 | if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE |
|
161 | 152 | response.headers['Content-Type'] = "application/force-download" |
|
162 | 153 | response.headers['Content-Disposition'] = "attachment; filename=\"output-#{case_num}.txt\"" |
|
163 | 154 | response.headers["X-Sendfile"] = out_filename |
|
164 | 155 | response.headers['Content-length'] = File.size(out_filename) |
|
165 | 156 | render :nothing => true |
|
166 | 157 | else |
|
167 | 158 | send_file out_filename, :stream => false, :filename => "output-#{case_num}.txt", :type => "text/plain" |
|
168 | 159 | end |
|
169 | 160 | end |
|
170 | 161 | |
|
171 | 162 | def error |
|
172 | 163 | @user = User.find(session[:user_id]) |
|
173 | 164 | end |
|
174 | 165 | |
|
175 | 166 | # announcement refreshing and hiding methods |
|
176 | 167 | |
|
177 | 168 | def announcements |
|
178 | 169 | if params.has_key? 'recent' |
|
179 | 170 | prepare_announcements(params[:recent]) |
|
180 | 171 | else |
|
181 | 172 | prepare_announcements |
|
182 | 173 | end |
|
183 | 174 | render(:partial => 'announcement', |
|
184 | 175 | :collection => @announcements, |
|
185 | 176 | :locals => {:announcement_effect => true}) |
|
186 | 177 | end |
|
187 | 178 | |
|
188 | 179 | def confirm_contest_start |
|
189 | 180 | user = User.find(session[:user_id]) |
|
190 | 181 | if request.method == 'POST' |
|
191 | 182 | user.update_start_time |
|
192 | 183 | redirect_to :action => 'list' |
|
193 | 184 | else |
|
194 | 185 | @contests = user.contests |
|
195 | 186 | @user = user |
|
196 | 187 | end |
|
197 | 188 | end |
|
198 | 189 | |
|
199 | 190 | protected |
|
200 | 191 | |
|
201 | 192 | def prepare_announcements(recent=nil) |
|
202 | 193 | if GraderConfiguration.show_tasks_to?(@user) |
|
203 | 194 | @announcements = Announcement.published(true) |
|
204 | 195 | else |
|
205 | 196 | @announcements = Announcement.published |
|
206 | 197 | end |
|
207 | 198 | if recent!=nil |
|
208 | 199 | recent_id = recent.to_i |
|
209 | 200 | @announcements = @announcements.find_all { |a| a.id > recent_id } |
|
210 | 201 | end |
|
211 | 202 | end |
|
212 | 203 | |
|
213 | 204 | def prepare_list_information |
|
214 | 205 | @user = User.find(session[:user_id]) |
|
215 | 206 | if not GraderConfiguration.multicontests? |
|
216 | 207 | @problems = @user.available_problems |
|
217 | 208 | else |
|
218 | 209 | @contest_problems = @user.available_problems_group_by_contests |
|
219 | 210 | @problems = @user.available_problems |
|
220 | 211 | end |
|
221 | 212 | @prob_submissions = {} |
|
222 | 213 | @problems.each do |p| |
|
223 | 214 | sub = Submission.find_last_by_user_and_problem(@user.id,p.id) |
|
224 | 215 | if sub!=nil |
|
225 | 216 | @prob_submissions[p.id] = { :count => sub.number, :submission => sub } |
|
226 | 217 | else |
|
227 | 218 | @prob_submissions[p.id] = { :count => 0, :submission => nil } |
|
228 | 219 | end |
|
229 | 220 | end |
|
230 | 221 | prepare_announcements |
|
231 | 222 | end |
|
232 | 223 | |
|
233 | 224 | def check_viewability |
|
234 | 225 | @user = User.find(session[:user_id]) |
|
235 | 226 | if (!GraderConfiguration.show_tasks_to?(@user)) and |
|
236 | 227 | ((action_name=='submission') or (action_name=='submit')) |
|
237 | 228 | redirect_to :action => 'list' and return |
|
238 | 229 | end |
|
239 | 230 | end |
|
240 | 231 | |
|
241 | 232 | def prepare_grading_result(submission) |
|
242 | 233 | if GraderConfiguration.task_grading_info.has_key? submission.problem.name |
|
243 | 234 | grading_info = GraderConfiguration.task_grading_info[submission.problem.name] |
|
244 | 235 | else |
|
245 | 236 | # guess task info from problem.full_score |
|
246 | 237 | cases = submission.problem.full_score / 10 |
|
247 | 238 | grading_info = { |
|
248 | 239 | 'testruns' => cases, |
|
249 | 240 | 'testcases' => cases |
|
250 | 241 | } |
|
251 | 242 | end |
|
252 | 243 | @test_runs = [] |
|
253 | 244 | if grading_info['testruns'].is_a? Integer |
|
254 | 245 | trun_count = grading_info['testruns'] |
|
255 | 246 | trun_count.times do |i| |
|
256 | 247 | @test_runs << [ read_grading_result(@user.login, |
|
257 | 248 | submission.problem.name, |
|
258 | 249 | submission.id, |
|
259 | 250 | i+1) ] |
|
260 | 251 | end |
|
261 | 252 | else |
|
262 | 253 | grading_info['testruns'].keys.sort.each do |num| |
|
263 | 254 | run = [] |
|
264 | 255 | testrun = grading_info['testruns'][num] |
|
265 | 256 | testrun.each do |c| |
|
266 | 257 | run << read_grading_result(@user.login, |
|
267 | 258 | submission.problem.name, |
|
268 | 259 | submission.id, |
|
269 | 260 | c) |
|
270 | 261 | end |
|
271 | 262 | @test_runs << run |
|
272 | 263 | end |
|
273 | 264 | end |
|
274 | 265 | end |
|
275 | 266 | |
|
276 | 267 | def grading_result_dir(user_name, problem_name, submission_id, case_num) |
|
277 | 268 | return "#{GRADING_RESULT_DIR}/#{user_name}/#{problem_name}/#{submission_id}/test-result/#{case_num}" |
|
278 | 269 | end |
|
279 | 270 | |
|
280 | 271 | def output_filename(user_name, problem_name, submission_id, case_num) |
|
281 | 272 | dir = grading_result_dir(user_name,problem_name, submission_id, case_num) |
|
282 | 273 | return "#{dir}/output.txt" |
|
283 | 274 | end |
|
284 | 275 | |
|
285 | 276 | def read_grading_result(user_name, problem_name, submission_id, case_num) |
|
286 | 277 | dir = grading_result_dir(user_name,problem_name, submission_id, case_num) |
|
287 | 278 | result_file_name = "#{dir}/result" |
|
288 | 279 | if !FileTest.exists?(result_file_name) |
|
289 | 280 | return {:num => case_num, :msg => 'program did not run'} |
|
290 | 281 | else |
|
291 | 282 | results = File.open(result_file_name).readlines |
|
292 | 283 | run_stat = extract_running_stat(results) |
|
293 | 284 | output_filename = "#{dir}/output.txt" |
|
294 | 285 | if FileTest.exists?(output_filename) |
|
295 | 286 | output_file = true |
|
296 | 287 | output_size = File.size(output_filename) |
|
297 | 288 | else |
|
298 | 289 | output_file = false |
|
299 | 290 | output_size = 0 |
|
300 | 291 | end |
|
301 | 292 | |
|
302 | 293 | return { |
|
303 | 294 | :num => case_num, |
|
304 | 295 | :msg => results[0], |
|
305 | 296 | :run_stat => run_stat, |
|
306 | 297 | :output => output_file, |
|
307 | 298 | :output_size => output_size |
|
308 | 299 | } |
|
309 | 300 | end |
|
310 | 301 | end |
|
311 | 302 | |
|
312 | 303 | # copied from grader/script/lib/test_request_helper.rb |
|
313 | 304 | def extract_running_stat(results) |
|
314 | 305 | running_stat_line = results[-1] |
|
315 | 306 | |
|
316 | 307 | # extract exit status line |
|
317 | 308 | run_stat = "" |
|
318 | 309 | if !(/[Cc]orrect/.match(results[0])) |
|
319 | 310 | run_stat = results[0].chomp |
|
320 | 311 | else |
|
321 | 312 | run_stat = 'Program exited normally' |
|
322 | 313 | end |
|
323 | 314 | |
|
324 | 315 | logger.info "Stat line: #{running_stat_line}" |
|
325 | 316 | |
|
326 | 317 | # extract running time |
|
327 | 318 | if res = /r(.*)u(.*)s/.match(running_stat_line) |
|
328 | 319 | seconds = (res[1].to_f + res[2].to_f) |
|
329 | 320 | time_stat = "Time used: #{seconds} sec." |
|
330 | 321 | else |
|
331 | 322 | seconds = nil |
|
332 | 323 | time_stat = "Time used: n/a sec." |
|
333 | 324 | end |
|
334 | 325 | |
|
335 | 326 | # extract memory usage |
|
336 | 327 | if res = /s(.*)m/.match(running_stat_line) |
|
337 | 328 | memory_used = res[1].to_i |
|
338 | 329 | else |
|
339 | 330 | memory_used = -1 |
|
340 | 331 | end |
|
341 | 332 | |
|
342 | 333 | return { |
|
343 | 334 | :msg => "#{run_stat}\n#{time_stat}", |
|
344 | 335 | :running_time => seconds, |
|
345 | 336 | :exit_status => run_stat, |
|
346 | 337 | :memory_usage => memory_used |
|
347 | 338 | } |
|
348 | 339 | end |
|
349 | 340 | |
|
350 | 341 | def confirm_and_update_start_time |
|
351 | 342 | user = User.find(session[:user_id]) |
|
352 | 343 | if (GraderConfiguration.indv_contest_mode? and |
|
353 | 344 | GraderConfiguration['contest.confirm_indv_contest_start'] and |
|
354 | 345 | !user.contest_started?) |
|
355 | 346 | redirect_to :action => 'confirm_contest_start' and return |
|
356 | 347 | end |
|
357 | 348 | if not GraderConfiguration.analysis_mode? |
|
358 | 349 | user.update_start_time |
|
359 | 350 | end |
|
360 | 351 | end |
|
361 | 352 | |
|
362 | 353 | def reject_announcement_refresh_when_logged_out |
|
363 | 354 | if not session[:user_id] |
|
364 | 355 | render :text => 'Access forbidden', :status => 403 |
|
365 | 356 | end |
|
366 | 357 | |
|
367 | 358 | if GraderConfiguration.multicontests? |
|
368 | 359 | user = User.find(session[:user_id]) |
|
369 | 360 | if user.contest_stat.forced_logout |
|
370 | 361 | render :text => 'Access forbidden', :status => 403 |
|
371 | 362 | end |
|
372 | 363 | end |
|
373 | 364 | end |
|
374 | 365 | |
|
375 | 366 | end |
|
376 | 367 |
@@ -1,86 +1,84 | |||
|
1 | 1 | class MessagesController < ApplicationController |
|
2 | 2 | |
|
3 | 3 | before_action :authenticate |
|
4 | 4 | |
|
5 | - verify :method => :post, :only => ['create'], | |
|
6 | - :redirect_to => { :action => 'list' } | |
|
7 | 5 | |
|
8 | 6 | before_filter :admin_authorization, :only => ['console','show', |
|
9 | 7 | 'reply','hide','list_all'] |
|
10 | 8 | |
|
11 | 9 | def list |
|
12 | 10 | @user = User.find(session[:user_id]) |
|
13 | 11 | @messages = Message.find_all_sent_by_user(@user) |
|
14 | 12 | end |
|
15 | 13 | |
|
16 | 14 | def console |
|
17 | 15 | @user = User.find(session[:user_id]) |
|
18 | 16 | @messages = Message.find_all_system_unreplied_messages |
|
19 | 17 | end |
|
20 | 18 | |
|
21 | 19 | def show |
|
22 | 20 | @message = Message.find(params[:id]) |
|
23 | 21 | end |
|
24 | 22 | |
|
25 | 23 | def list_all |
|
26 | 24 | @user = User.find(session[:user_id]) |
|
27 | 25 | @messages = Message.where(receiver_id: nil).order(:created_at) |
|
28 | 26 | end |
|
29 | 27 | |
|
30 | 28 | def create |
|
31 | 29 | user = User.find(session[:user_id]) |
|
32 | 30 | @message = Message.new(params[:message]) |
|
33 | 31 | @message.sender = user |
|
34 | 32 | if @message.body == '' or !@message.save |
|
35 | 33 | flash[:notice] = 'An error occurred' |
|
36 | 34 | else |
|
37 | 35 | flash[:notice] = 'New message posted' |
|
38 | 36 | end |
|
39 | 37 | redirect_to :action => 'list' |
|
40 | 38 | end |
|
41 | 39 | |
|
42 | 40 | def reply |
|
43 | 41 | user = User.find(session[:user_id]) |
|
44 | 42 | @message = Message.new(params[:r_message]) |
|
45 | 43 | @message.sender = user |
|
46 | 44 | if @message.body == '' or !@message.save |
|
47 | 45 | flash[:notice] = 'An error occurred' |
|
48 | 46 | redirect_to :action => 'show', :id => @message.replying_message_id |
|
49 | 47 | else |
|
50 | 48 | flash[:notice] = 'Message replied' |
|
51 | 49 | rep_msg = @message.replying_message |
|
52 | 50 | rep_msg.replied = true |
|
53 | 51 | rep_msg.save |
|
54 | 52 | redirect_to :action => 'console' |
|
55 | 53 | end |
|
56 | 54 | end |
|
57 | 55 | |
|
58 | 56 | def hide |
|
59 | 57 | message = Message.find(params[:id]) |
|
60 | 58 | message.replied = true |
|
61 | 59 | message.save |
|
62 | 60 | flash[:notice] = 'Message hidden (just marked replied)' |
|
63 | 61 | redirect_to :action => 'console' |
|
64 | 62 | end |
|
65 | 63 | |
|
66 | 64 | protected |
|
67 | 65 | def build_replying_message_hierarchy(user) |
|
68 | 66 | @all_messages = {} |
|
69 | 67 | |
|
70 | 68 | |
|
71 | 69 | # manually build replies hierarchy (to improve efficiency) |
|
72 | 70 | [@messages, @replied_messages].each do |collection| |
|
73 | 71 | collection.each do |m| |
|
74 | 72 | @all_messages[m.id] = {:msg => m, :replies => []} |
|
75 | 73 | end |
|
76 | 74 | end |
|
77 | 75 | |
|
78 | 76 | @all_messages.each do |m| |
|
79 | 77 | rep_id = m.replying_message_id |
|
80 | 78 | if @all_messages[rep_id]!=nil |
|
81 | 79 | @all_messages[rep_id][:replies] << m |
|
82 | 80 | end |
|
83 | 81 | end |
|
84 | 82 | end |
|
85 | 83 | |
|
86 | 84 | end |
@@ -1,310 +1,304 | |||
|
1 | 1 | class ProblemsController < ApplicationController |
|
2 | 2 | |
|
3 | 3 | before_action :authenticate, :authorization |
|
4 | 4 | before_action :testcase_authorization, only: [:show_testcase] |
|
5 | 5 | |
|
6 | 6 | in_place_edit_for :problem, :name |
|
7 | 7 | in_place_edit_for :problem, :full_name |
|
8 | 8 | in_place_edit_for :problem, :full_score |
|
9 | 9 | |
|
10 | 10 | def index |
|
11 | 11 | @problems = Problem.order(date_added: :desc) |
|
12 | 12 | end |
|
13 | 13 | |
|
14 | - # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) | |
|
15 | - verify :method => :post, :only => [ :create, :quick_create, | |
|
16 | - :do_manage, | |
|
17 | - :do_import, | |
|
18 | - ], | |
|
19 | - :redirect_to => { :action => :index } | |
|
20 | 14 | |
|
21 | 15 | def show |
|
22 | 16 | @problem = Problem.find(params[:id]) |
|
23 | 17 | end |
|
24 | 18 | |
|
25 | 19 | def new |
|
26 | 20 | @problem = Problem.new |
|
27 | 21 | @description = nil |
|
28 | 22 | end |
|
29 | 23 | |
|
30 | 24 | def create |
|
31 | 25 | @problem = Problem.new(problem_params) |
|
32 | - @description = Description.new(params[:description]) | |
|
26 | + @description = Description.new(problem_params[:description]) | |
|
33 | 27 | if @description.body!='' |
|
34 | 28 | if !@description.save |
|
35 | 29 | render :action => new and return |
|
36 | 30 | end |
|
37 | 31 | else |
|
38 | 32 | @description = nil |
|
39 | 33 | end |
|
40 | 34 | @problem.description = @description |
|
41 | 35 | if @problem.save |
|
42 | 36 | flash[:notice] = 'Problem was successfully created.' |
|
43 | 37 | redirect_to action: :index |
|
44 | 38 | else |
|
45 | 39 | render :action => 'new' |
|
46 | 40 | end |
|
47 | 41 | end |
|
48 | 42 | |
|
49 | 43 | def quick_create |
|
50 | 44 | @problem = Problem.new(problem_params) |
|
51 | 45 | @problem.full_name = @problem.name if @problem.full_name == '' |
|
52 | 46 | @problem.full_score = 100 |
|
53 | 47 | @problem.available = false |
|
54 | 48 | @problem.test_allowed = true |
|
55 | 49 | @problem.output_only = false |
|
56 | 50 | @problem.date_added = Time.new |
|
57 | 51 | if @problem.save |
|
58 | 52 | flash[:notice] = 'Problem was successfully created.' |
|
59 | 53 | redirect_to action: :index |
|
60 | 54 | else |
|
61 | 55 | flash[:notice] = 'Error saving problem' |
|
62 | 56 | redirect_to action: :index |
|
63 | 57 | end |
|
64 | 58 | end |
|
65 | 59 | |
|
66 | 60 | def edit |
|
67 | 61 | @problem = Problem.find(params[:id]) |
|
68 | 62 | @description = @problem.description |
|
69 | 63 | end |
|
70 | 64 | |
|
71 | 65 | def update |
|
72 | 66 | @problem = Problem.find(params[:id]) |
|
73 | 67 | @description = @problem.description |
|
74 | 68 | if @description.nil? and params[:description][:body]!='' |
|
75 | 69 | @description = Description.new(params[:description]) |
|
76 | 70 | if !@description.save |
|
77 | 71 | flash[:notice] = 'Error saving description' |
|
78 | 72 | render :action => 'edit' and return |
|
79 | 73 | end |
|
80 | 74 | @problem.description = @description |
|
81 | 75 | elsif @description |
|
82 | 76 | if !@description.update_attributes(params[:description]) |
|
83 | 77 | flash[:notice] = 'Error saving description' |
|
84 | 78 | render :action => 'edit' and return |
|
85 | 79 | end |
|
86 | 80 | end |
|
87 | 81 | if params[:file] and params[:file].content_type != 'application/pdf' |
|
88 | 82 | flash[:notice] = 'Error: Uploaded file is not PDF' |
|
89 | 83 | render :action => 'edit' and return |
|
90 | 84 | end |
|
91 | 85 | if @problem.update_attributes(problem_params) |
|
92 | 86 | flash[:notice] = 'Problem was successfully updated.' |
|
93 | 87 | unless params[:file] == nil or params[:file] == '' |
|
94 | 88 | flash[:notice] = 'Problem was successfully updated and a new PDF file is uploaded.' |
|
95 | 89 | out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}" |
|
96 | 90 | if not FileTest.exists? out_dirname |
|
97 | 91 | Dir.mkdir out_dirname |
|
98 | 92 | end |
|
99 | 93 | |
|
100 | 94 | out_filename = "#{out_dirname}/#{@problem.name}.pdf" |
|
101 | 95 | if FileTest.exists? out_filename |
|
102 | 96 | File.delete out_filename |
|
103 | 97 | end |
|
104 | 98 | |
|
105 | 99 | File.open(out_filename,"wb") do |file| |
|
106 | 100 | file.write(params[:file].read) |
|
107 | 101 | end |
|
108 | 102 | @problem.description_filename = "#{@problem.name}.pdf" |
|
109 | 103 | @problem.save |
|
110 | 104 | end |
|
111 | 105 | redirect_to :action => 'show', :id => @problem |
|
112 | 106 | else |
|
113 | 107 | render :action => 'edit' |
|
114 | 108 | end |
|
115 | 109 | end |
|
116 | 110 | |
|
117 | 111 | def destroy |
|
118 | 112 | p = Problem.find(params[:id]).destroy |
|
119 | 113 | redirect_to action: :index |
|
120 | 114 | end |
|
121 | 115 | |
|
122 | 116 | def toggle |
|
123 | 117 | @problem = Problem.find(params[:id]) |
|
124 | 118 | @problem.update_attributes(available: !(@problem.available) ) |
|
125 | 119 | respond_to do |format| |
|
126 | 120 | format.js { } |
|
127 | 121 | end |
|
128 | 122 | end |
|
129 | 123 | |
|
130 | 124 | def toggle_test |
|
131 | 125 | @problem = Problem.find(params[:id]) |
|
132 | 126 | @problem.update_attributes(test_allowed: !(@problem.test_allowed?) ) |
|
133 | 127 | respond_to do |format| |
|
134 | 128 | format.js { } |
|
135 | 129 | end |
|
136 | 130 | end |
|
137 | 131 | |
|
138 | 132 | def toggle_view_testcase |
|
139 | 133 | @problem = Problem.find(params[:id]) |
|
140 | 134 | @problem.update_attributes(view_testcase: !(@problem.view_testcase?) ) |
|
141 | 135 | respond_to do |format| |
|
142 | 136 | format.js { } |
|
143 | 137 | end |
|
144 | 138 | end |
|
145 | 139 | |
|
146 | 140 | def turn_all_off |
|
147 | 141 | Problem.available.all.each do |problem| |
|
148 | 142 | problem.available = false |
|
149 | 143 | problem.save |
|
150 | 144 | end |
|
151 | 145 | redirect_to action: :index |
|
152 | 146 | end |
|
153 | 147 | |
|
154 | 148 | def turn_all_on |
|
155 | 149 | Problem.where.not(available: true).each do |problem| |
|
156 | 150 | problem.available = true |
|
157 | 151 | problem.save |
|
158 | 152 | end |
|
159 | 153 | redirect_to action: :index |
|
160 | 154 | end |
|
161 | 155 | |
|
162 | 156 | def stat |
|
163 | 157 | @problem = Problem.find(params[:id]) |
|
164 | 158 | unless @problem.available or session[:admin] |
|
165 | 159 | redirect_to :controller => 'main', :action => 'list' |
|
166 | 160 | return |
|
167 | 161 | end |
|
168 | 162 | @submissions = Submission.includes(:user).includes(:language).where(problem_id: params[:id]).order(:user_id,:id) |
|
169 | 163 | |
|
170 | 164 | #stat summary |
|
171 | 165 | range =65 |
|
172 | 166 | @histogram = { data: Array.new(range,0), summary: {} } |
|
173 | 167 | user = Hash.new(0) |
|
174 | 168 | @submissions.find_each do |sub| |
|
175 | 169 | d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60 |
|
176 | 170 | @histogram[:data][d.to_i] += 1 if d < range |
|
177 | 171 | user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max |
|
178 | 172 | end |
|
179 | 173 | @histogram[:summary][:max] = [@histogram[:data].max,1].max |
|
180 | 174 | |
|
181 | 175 | @summary = { attempt: user.count, solve: 0 } |
|
182 | 176 | user.each_value { |v| @summary[:solve] += 1 if v == 1 } |
|
183 | 177 | end |
|
184 | 178 | |
|
185 | 179 | def manage |
|
186 | 180 | @problems = Problem.order(date_added: :desc) |
|
187 | 181 | end |
|
188 | 182 | |
|
189 | 183 | def do_manage |
|
190 | 184 | if params.has_key? 'change_date_added' and params[:date_added].strip.empty? == false |
|
191 | 185 | change_date_added |
|
192 | 186 | elsif params.has_key? 'add_to_contest' |
|
193 | 187 | add_to_contest |
|
194 | 188 | elsif params.has_key? 'enable_problem' |
|
195 | 189 | set_available(true) |
|
196 | 190 | elsif params.has_key? 'disable_problem' |
|
197 | 191 | set_available(false) |
|
198 | 192 | elsif params.has_key? 'add_group' |
|
199 | 193 | group = Group.find(params[:group_id]) |
|
200 | 194 | ok = [] |
|
201 | 195 | failed = [] |
|
202 | 196 | get_problems_from_params.each do |p| |
|
203 | 197 | begin |
|
204 | 198 | group.problems << p |
|
205 | 199 | ok << p.full_name |
|
206 | 200 | rescue => e |
|
207 | 201 | failed << p.full_name |
|
208 | 202 | end |
|
209 | 203 | end |
|
210 | 204 | flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0 |
|
211 | 205 | flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0 |
|
212 | 206 | elsif params.has_key? 'add_tags' |
|
213 | 207 | get_problems_from_params.each do |p| |
|
214 | 208 | p.tag_ids += params[:tag_ids] |
|
215 | 209 | end |
|
216 | 210 | end |
|
217 | 211 | |
|
218 | 212 | redirect_to :action => 'manage' |
|
219 | 213 | end |
|
220 | 214 | |
|
221 | 215 | def import |
|
222 | 216 | @allow_test_pair_import = allow_test_pair_import? |
|
223 | 217 | end |
|
224 | 218 | |
|
225 | 219 | def do_import |
|
226 | 220 | old_problem = Problem.find_by_name(params[:name]) |
|
227 | 221 | if !allow_test_pair_import? and params.has_key? :import_to_db |
|
228 | 222 | params.delete :import_to_db |
|
229 | 223 | end |
|
230 | 224 | @problem, import_log = Problem.create_from_import_form_params(params, |
|
231 | 225 | old_problem) |
|
232 | 226 | |
|
233 | 227 | if !@problem.errors.empty? |
|
234 | 228 | render :action => 'import' and return |
|
235 | 229 | end |
|
236 | 230 | |
|
237 | 231 | if old_problem!=nil |
|
238 | 232 | flash[:notice] = "The test data has been replaced for problem #{@problem.name}" |
|
239 | 233 | end |
|
240 | 234 | @log = import_log |
|
241 | 235 | end |
|
242 | 236 | |
|
243 | 237 | def remove_contest |
|
244 | 238 | problem = Problem.find(params[:id]) |
|
245 | 239 | contest = Contest.find(params[:contest_id]) |
|
246 | 240 | if problem!=nil and contest!=nil |
|
247 | 241 | problem.contests.delete(contest) |
|
248 | 242 | end |
|
249 | 243 | redirect_to :action => 'manage' |
|
250 | 244 | end |
|
251 | 245 | |
|
252 | 246 | ################################## |
|
253 | 247 | protected |
|
254 | 248 | |
|
255 | 249 | def allow_test_pair_import? |
|
256 | 250 | if defined? ALLOW_TEST_PAIR_IMPORT |
|
257 | 251 | return ALLOW_TEST_PAIR_IMPORT |
|
258 | 252 | else |
|
259 | 253 | return false |
|
260 | 254 | end |
|
261 | 255 | end |
|
262 | 256 | |
|
263 | 257 | def change_date_added |
|
264 | 258 | problems = get_problems_from_params |
|
265 | 259 | date = Date.parse(params[:date_added]) |
|
266 | 260 | problems.each do |p| |
|
267 | 261 | p.date_added = date |
|
268 | 262 | p.save |
|
269 | 263 | end |
|
270 | 264 | end |
|
271 | 265 | |
|
272 | 266 | def add_to_contest |
|
273 | 267 | problems = get_problems_from_params |
|
274 | 268 | contest = Contest.find(params[:contest][:id]) |
|
275 | 269 | if contest!=nil and contest.enabled |
|
276 | 270 | problems.each do |p| |
|
277 | 271 | p.contests << contest |
|
278 | 272 | end |
|
279 | 273 | end |
|
280 | 274 | end |
|
281 | 275 | |
|
282 | 276 | def set_available(avail) |
|
283 | 277 | problems = get_problems_from_params |
|
284 | 278 | problems.each do |p| |
|
285 | 279 | p.available = avail |
|
286 | 280 | p.save |
|
287 | 281 | end |
|
288 | 282 | end |
|
289 | 283 | |
|
290 | 284 | def get_problems_from_params |
|
291 | 285 | problems = [] |
|
292 | 286 | params.keys.each do |k| |
|
293 | 287 | if k.index('prob-')==0 |
|
294 | 288 | name, id, order = k.split('-') |
|
295 | 289 | problems << Problem.find(id) |
|
296 | 290 | end |
|
297 | 291 | end |
|
298 | 292 | problems |
|
299 | 293 | end |
|
300 | 294 | |
|
301 | 295 | def get_problems_stat |
|
302 | 296 | end |
|
303 | 297 | |
|
304 | 298 | private |
|
305 | 299 | |
|
306 | 300 | def problem_params |
|
307 | 301 | params.require(:problem).permit(:name, :full_name, :full_score, :change_date_added, :date_added, :available, :test_allowed,:output_only, :url, :description, tag_ids:[]) |
|
308 | 302 | end |
|
309 | 303 | |
|
310 | 304 | end |
@@ -1,118 +1,111 | |||
|
1 | 1 | class TestController < ApplicationController |
|
2 | 2 | |
|
3 | 3 | before_action :authenticate, :check_viewability |
|
4 | 4 | |
|
5 | - # | |
|
6 | - # COMMENT OUT: filter in each action instead | |
|
7 | - # | |
|
8 | - # before_filter :verify_time_limit, :only => [:submit] | |
|
9 | - | |
|
10 | - verify :method => :post, :only => [:submit], | |
|
11 | - :redirect_to => { :action => :index } | |
|
12 | 5 | |
|
13 | 6 | def index |
|
14 | 7 | prepare_index_information |
|
15 | 8 | end |
|
16 | 9 | |
|
17 | 10 | def submit |
|
18 | 11 | @user = User.find(session[:user_id]) |
|
19 | 12 | |
|
20 | 13 | @submitted_test_request = TestRequest.new_from_form_params(@user,params[:test_request]) |
|
21 | 14 | |
|
22 | 15 | if ! @submitted_test_request.errors.empty? |
|
23 | 16 | prepare_index_information |
|
24 | 17 | render :action => 'index' and return |
|
25 | 18 | end |
|
26 | 19 | |
|
27 | 20 | if GraderConfiguration.time_limit_mode? |
|
28 | 21 | if @user.contest_finished? |
|
29 | 22 | @submitted_test_request.errors.add(:base,'Contest is over.') |
|
30 | 23 | prepare_index_information |
|
31 | 24 | render :action => 'index' and return |
|
32 | 25 | end |
|
33 | 26 | |
|
34 | 27 | if !GraderConfiguration.allow_test_request(@user) |
|
35 | 28 | prepare_index_information |
|
36 | 29 | flash[:notice] = 'Test request is not allowed during the last 30 minutes' |
|
37 | 30 | redirect_to :action => 'index' and return |
|
38 | 31 | end |
|
39 | 32 | end |
|
40 | 33 | |
|
41 | 34 | if @submitted_test_request.save |
|
42 | 35 | redirect_to :action => 'index' |
|
43 | 36 | else |
|
44 | 37 | prepare_index_information |
|
45 | 38 | render :action => 'index' |
|
46 | 39 | end |
|
47 | 40 | end |
|
48 | 41 | |
|
49 | 42 | def read |
|
50 | 43 | user = User.find(session[:user_id]) |
|
51 | 44 | begin |
|
52 | 45 | test_request = TestRequest.find(params[:id]) |
|
53 | 46 | rescue |
|
54 | 47 | test_request = nil |
|
55 | 48 | end |
|
56 | 49 | if test_request==nil or test_request.user_id != user.id |
|
57 | 50 | flash[:notice] = 'Invalid output' |
|
58 | 51 | redirect_to :action => 'index' |
|
59 | 52 | return |
|
60 | 53 | end |
|
61 | 54 | if test_request.output_file_name!=nil |
|
62 | 55 | data = File.open(test_request.output_file_name).read(2048) |
|
63 | 56 | if data==nil |
|
64 | 57 | data="" |
|
65 | 58 | end |
|
66 | 59 | send_data(data, |
|
67 | 60 | {:filename => 'output.txt', |
|
68 | 61 | :type => 'text/plain'}) |
|
69 | 62 | return |
|
70 | 63 | end |
|
71 | 64 | redirect_to :action => 'index' |
|
72 | 65 | end |
|
73 | 66 | |
|
74 | 67 | def result |
|
75 | 68 | @user = User.find(session[:user_id]) |
|
76 | 69 | begin |
|
77 | 70 | @test_request = TestRequest.find(params[:id]) |
|
78 | 71 | rescue |
|
79 | 72 | @test_request = nil |
|
80 | 73 | end |
|
81 | 74 | if @test_request==nil or @test_request.user_id != @user.id |
|
82 | 75 | flash[:notice] = 'Invalid request' |
|
83 | 76 | redirect_to :action => 'index' |
|
84 | 77 | return |
|
85 | 78 | end |
|
86 | 79 | end |
|
87 | 80 | |
|
88 | 81 | protected |
|
89 | 82 | |
|
90 | 83 | def prepare_index_information |
|
91 | 84 | @user = User.find(session[:user_id]) |
|
92 | 85 | @submissions = Submission.find_last_for_all_available_problems(@user.id) |
|
93 | 86 | all_problems = @submissions.collect { |submission| submission.problem } |
|
94 | 87 | @problems = [] |
|
95 | 88 | all_problems.each do |problem| |
|
96 | 89 | if problem.test_allowed |
|
97 | 90 | @problems << problem |
|
98 | 91 | end |
|
99 | 92 | end |
|
100 | 93 | @test_requests = [] |
|
101 | 94 | @user.test_requests.each do |ts| |
|
102 | 95 | if ts.problem and ts.problem.available |
|
103 | 96 | @test_requests << ts |
|
104 | 97 | end |
|
105 | 98 | end |
|
106 | 99 | end |
|
107 | 100 | |
|
108 | 101 | def check_viewability |
|
109 | 102 | user = User.find(session[:user_id]) |
|
110 | 103 | if !GraderConfiguration.show_tasks_to?(user) |
|
111 | 104 | redirect_to :controller => 'main', :action => 'list' |
|
112 | 105 | end |
|
113 | 106 | if (!GraderConfiguration.show_submitbox_to?(user)) and (action_name=='submit') |
|
114 | 107 | redirect_to :controller => 'test', :action => 'index' |
|
115 | 108 | end |
|
116 | 109 | end |
|
117 | 110 | |
|
118 | 111 | end |
@@ -1,401 +1,392 | |||
|
1 | 1 | require 'csv' |
|
2 | 2 | |
|
3 | 3 | class UserAdminController < ApplicationController |
|
4 | 4 | |
|
5 | 5 | include MailHelperMethods |
|
6 | 6 | |
|
7 | 7 | before_action :admin_authorization |
|
8 | 8 | |
|
9 | - # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) | |
|
10 | - verify :method => :post, :only => [ | |
|
11 | - :create, :create_from_list, | |
|
12 | - :update, | |
|
13 | - :manage_contest, | |
|
14 | - :bulk_mail | |
|
15 | - ], | |
|
16 | - :redirect_to => { :action => :list } | |
|
17 | - | |
|
18 | 9 | def index |
|
19 | 10 | @user_count = User.count |
|
20 | 11 | if params[:page] == 'all' |
|
21 | 12 | @users = User.all |
|
22 | 13 | @paginated = false |
|
23 | 14 | else |
|
24 | 15 | @users = User.paginate :page => params[:page] |
|
25 | 16 | @paginated = true |
|
26 | 17 | end |
|
27 | 18 | @users = User.all |
|
28 | 19 | @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at'] |
|
29 | 20 | @contests = Contest.enabled |
|
30 | 21 | end |
|
31 | 22 | |
|
32 | 23 | def active |
|
33 | 24 | sessions = ActiveRecord::SessionStore::Session.where("updated_at >= ?", 60.minutes.ago) |
|
34 | 25 | @users = [] |
|
35 | 26 | sessions.each do |session| |
|
36 | 27 | if session.data[:user_id] |
|
37 | 28 | @users << User.find(session.data[:user_id]) |
|
38 | 29 | end |
|
39 | 30 | end |
|
40 | 31 | end |
|
41 | 32 | |
|
42 | 33 | def show |
|
43 | 34 | @user = User.find(params[:id]) |
|
44 | 35 | end |
|
45 | 36 | |
|
46 | 37 | def new |
|
47 | 38 | @user = User.new |
|
48 | 39 | end |
|
49 | 40 | |
|
50 | 41 | def create |
|
51 | 42 | @user = User.new(user_params) |
|
52 | 43 | @user.activated = true |
|
53 | 44 | if @user.save |
|
54 | 45 | flash[:notice] = 'User was successfully created.' |
|
55 | 46 | redirect_to :action => 'index' |
|
56 | 47 | else |
|
57 | 48 | render :action => 'new' |
|
58 | 49 | end |
|
59 | 50 | end |
|
60 | 51 | |
|
61 | 52 | def clear_last_ip |
|
62 | 53 | @user = User.find(params[:id]) |
|
63 | 54 | @user.last_ip = nil |
|
64 | 55 | @user.save |
|
65 | 56 | redirect_to action: 'index', page: params[:page] |
|
66 | 57 | end |
|
67 | 58 | |
|
68 | 59 | def create_from_list |
|
69 | 60 | lines = params[:user_list] |
|
70 | 61 | |
|
71 | 62 | note = [] |
|
72 | 63 | |
|
73 | 64 | lines.split("\n").each do |line| |
|
74 | 65 | items = line.chomp.split(',') |
|
75 | 66 | if items.length>=2 |
|
76 | 67 | login = items[0] |
|
77 | 68 | full_name = items[1] |
|
78 | 69 | remark ='' |
|
79 | 70 | user_alias = '' |
|
80 | 71 | |
|
81 | 72 | added_random_password = false |
|
82 | 73 | if items.length >= 3 and items[2].chomp(" ").length > 0; |
|
83 | 74 | password = items[2].chomp(" ") |
|
84 | 75 | else |
|
85 | 76 | password = random_password |
|
86 | 77 | add_random_password=true; |
|
87 | 78 | end |
|
88 | 79 | |
|
89 | 80 | if items.length>= 4 and items[3].chomp(" ").length > 0; |
|
90 | 81 | user_alias = items[3].chomp(" ") |
|
91 | 82 | else |
|
92 | 83 | user_alias = login |
|
93 | 84 | end |
|
94 | 85 | |
|
95 | 86 | if items.length>=5 |
|
96 | 87 | remark = items[4].strip; |
|
97 | 88 | end |
|
98 | 89 | |
|
99 | 90 | user = User.find_by_login(login) |
|
100 | 91 | if (user) |
|
101 | 92 | user.full_name = full_name |
|
102 | 93 | user.password = password |
|
103 | 94 | user.remark = remark |
|
104 | 95 | else |
|
105 | 96 | user = User.new({:login => login, |
|
106 | 97 | :full_name => full_name, |
|
107 | 98 | :password => password, |
|
108 | 99 | :password_confirmation => password, |
|
109 | 100 | :alias => user_alias, |
|
110 | 101 | :remark => remark}) |
|
111 | 102 | end |
|
112 | 103 | user.activated = true |
|
113 | 104 | user.save |
|
114 | 105 | |
|
115 | 106 | if added_random_password |
|
116 | 107 | note << "'#{login}' (+)" |
|
117 | 108 | else |
|
118 | 109 | note << login |
|
119 | 110 | end |
|
120 | 111 | end |
|
121 | 112 | end |
|
122 | 113 | flash[:success] = 'User(s) ' + note.join(', ') + |
|
123 | 114 | ' were successfully created. ' + |
|
124 | 115 | '( (+) - created with random passwords.)' |
|
125 | 116 | redirect_to :action => 'index' |
|
126 | 117 | end |
|
127 | 118 | |
|
128 | 119 | def edit |
|
129 | 120 | @user = User.find(params[:id]) |
|
130 | 121 | end |
|
131 | 122 | |
|
132 | 123 | def update |
|
133 | 124 | @user = User.find(params[:id]) |
|
134 | 125 | if @user.update_attributes(user_params) |
|
135 | 126 | flash[:notice] = 'User was successfully updated.' |
|
136 | 127 | redirect_to :action => 'show', :id => @user |
|
137 | 128 | else |
|
138 | 129 | render :action => 'edit' |
|
139 | 130 | end |
|
140 | 131 | end |
|
141 | 132 | |
|
142 | 133 | def destroy |
|
143 | 134 | User.find(params[:id]).destroy |
|
144 | 135 | redirect_to :action => 'index' |
|
145 | 136 | end |
|
146 | 137 | |
|
147 | 138 | def user_stat |
|
148 | 139 | if params[:commit] == 'download csv' |
|
149 | 140 | @problems = Problem.all |
|
150 | 141 | else |
|
151 | 142 | @problems = Problem.available_problems |
|
152 | 143 | end |
|
153 | 144 | @users = User.includes(:contests, :contest_stat).where(enabled: true) |
|
154 | 145 | @scorearray = Array.new |
|
155 | 146 | @users.each do |u| |
|
156 | 147 | ustat = Array.new |
|
157 | 148 | ustat[0] = u |
|
158 | 149 | @problems.each do |p| |
|
159 | 150 | sub = Submission.find_last_by_user_and_problem(u.id,p.id) |
|
160 | 151 | if (sub!=nil) and (sub.points!=nil) and p and p.full_score |
|
161 | 152 | ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)] |
|
162 | 153 | else |
|
163 | 154 | ustat << [0,false] |
|
164 | 155 | end |
|
165 | 156 | end |
|
166 | 157 | @scorearray << ustat |
|
167 | 158 | end |
|
168 | 159 | if params[:commit] == 'download csv' then |
|
169 | 160 | csv = gen_csv_from_scorearray(@scorearray,@problems) |
|
170 | 161 | send_data csv, filename: 'last_score.csv' |
|
171 | 162 | else |
|
172 | 163 | render template: 'user_admin/user_stat' |
|
173 | 164 | end |
|
174 | 165 | end |
|
175 | 166 | |
|
176 | 167 | def user_stat_max |
|
177 | 168 | if params[:commit] == 'download csv' |
|
178 | 169 | @problems = Problem.all |
|
179 | 170 | else |
|
180 | 171 | @problems = Problem.available_problems |
|
181 | 172 | end |
|
182 | 173 | @users = User.includes(:contests).includes(:contest_stat).all |
|
183 | 174 | @scorearray = Array.new |
|
184 | 175 | #set up range from param |
|
185 | 176 | since_id = params.fetch(:since_id, 0).to_i |
|
186 | 177 | until_id = params.fetch(:until_id, 0).to_i |
|
187 | 178 | @users.each do |u| |
|
188 | 179 | ustat = Array.new |
|
189 | 180 | ustat[0] = u |
|
190 | 181 | @problems.each do |p| |
|
191 | 182 | max_points = 0 |
|
192 | 183 | Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub| |
|
193 | 184 | max_points = sub.points if sub and sub.points and (sub.points > max_points) |
|
194 | 185 | end |
|
195 | 186 | ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)] |
|
196 | 187 | end |
|
197 | 188 | @scorearray << ustat |
|
198 | 189 | end |
|
199 | 190 | |
|
200 | 191 | if params[:commit] == 'download csv' then |
|
201 | 192 | csv = gen_csv_from_scorearray(@scorearray,@problems) |
|
202 | 193 | send_data csv, filename: 'max_score.csv' |
|
203 | 194 | else |
|
204 | 195 | render template: 'user_admin/user_stat' |
|
205 | 196 | end |
|
206 | 197 | end |
|
207 | 198 | |
|
208 | 199 | def import |
|
209 | 200 | if params[:file]=='' |
|
210 | 201 | flash[:notice] = 'Error importing no file' |
|
211 | 202 | redirect_to :action => 'index' and return |
|
212 | 203 | end |
|
213 | 204 | import_from_file(params[:file]) |
|
214 | 205 | end |
|
215 | 206 | |
|
216 | 207 | def random_all_passwords |
|
217 | 208 | users = User.all |
|
218 | 209 | @prefix = params[:prefix] || '' |
|
219 | 210 | @non_admin_users = User.find_non_admin_with_prefix(@prefix) |
|
220 | 211 | @changed = false |
|
221 | 212 | if request.request_method == 'POST' |
|
222 | 213 | @non_admin_users.each do |user| |
|
223 | 214 | password = random_password |
|
224 | 215 | user.password = password |
|
225 | 216 | user.password_confirmation = password |
|
226 | 217 | user.save |
|
227 | 218 | end |
|
228 | 219 | @changed = true |
|
229 | 220 | end |
|
230 | 221 | end |
|
231 | 222 | |
|
232 | 223 | |
|
233 | 224 | # contest management |
|
234 | 225 | |
|
235 | 226 | def contests |
|
236 | 227 | @contest, @users = find_contest_and_user_from_contest_id(params[:id]) |
|
237 | 228 | @contests = Contest.enabled |
|
238 | 229 | end |
|
239 | 230 | |
|
240 | 231 | def assign_from_list |
|
241 | 232 | contest_id = params[:users_contest_id] |
|
242 | 233 | org_contest, users = find_contest_and_user_from_contest_id(contest_id) |
|
243 | 234 | contest = Contest.find(params[:new_contest][:id]) |
|
244 | 235 | if !contest |
|
245 | 236 | flash[:notice] = 'Error: no contest' |
|
246 | 237 | redirect_to :action => 'contests', :id =>contest_id |
|
247 | 238 | end |
|
248 | 239 | |
|
249 | 240 | note = [] |
|
250 | 241 | users.each do |u| |
|
251 | 242 | u.contests = [contest] |
|
252 | 243 | note << u.login |
|
253 | 244 | end |
|
254 | 245 | flash[:notice] = 'User(s) ' + note.join(', ') + |
|
255 | 246 | " were successfully reassigned to #{contest.title}." |
|
256 | 247 | redirect_to :action => 'contests', :id =>contest.id |
|
257 | 248 | end |
|
258 | 249 | |
|
259 | 250 | def add_to_contest |
|
260 | 251 | user = User.find(params[:id]) |
|
261 | 252 | contest = Contest.find(params[:contest_id]) |
|
262 | 253 | if user and contest |
|
263 | 254 | user.contests << contest |
|
264 | 255 | end |
|
265 | 256 | redirect_to :action => 'index' |
|
266 | 257 | end |
|
267 | 258 | |
|
268 | 259 | def remove_from_contest |
|
269 | 260 | user = User.find(params[:id]) |
|
270 | 261 | contest = Contest.find(params[:contest_id]) |
|
271 | 262 | if user and contest |
|
272 | 263 | user.contests.delete(contest) |
|
273 | 264 | end |
|
274 | 265 | redirect_to :action => 'index' |
|
275 | 266 | end |
|
276 | 267 | |
|
277 | 268 | def contest_management |
|
278 | 269 | end |
|
279 | 270 | |
|
280 | 271 | def manage_contest |
|
281 | 272 | contest = Contest.find(params[:contest][:id]) |
|
282 | 273 | if !contest |
|
283 | 274 | flash[:notice] = 'You did not choose the contest.' |
|
284 | 275 | redirect_to :action => 'contest_management' and return |
|
285 | 276 | end |
|
286 | 277 | |
|
287 | 278 | operation = params[:operation] |
|
288 | 279 | |
|
289 | 280 | if not ['add','remove','assign'].include? operation |
|
290 | 281 | flash[:notice] = 'You did not choose the operation to perform.' |
|
291 | 282 | redirect_to :action => 'contest_management' and return |
|
292 | 283 | end |
|
293 | 284 | |
|
294 | 285 | lines = params[:login_list] |
|
295 | 286 | if !lines or lines.blank? |
|
296 | 287 | flash[:notice] = 'You entered an empty list.' |
|
297 | 288 | redirect_to :action => 'contest_management' and return |
|
298 | 289 | end |
|
299 | 290 | |
|
300 | 291 | note = [] |
|
301 | 292 | users = [] |
|
302 | 293 | lines.split("\n").each do |line| |
|
303 | 294 | user = User.find_by_login(line.chomp) |
|
304 | 295 | if user |
|
305 | 296 | if operation=='add' |
|
306 | 297 | if ! user.contests.include? contest |
|
307 | 298 | user.contests << contest |
|
308 | 299 | end |
|
309 | 300 | elsif operation=='remove' |
|
310 | 301 | user.contests.delete(contest) |
|
311 | 302 | else |
|
312 | 303 | user.contests = [contest] |
|
313 | 304 | end |
|
314 | 305 | |
|
315 | 306 | if params[:reset_timer] |
|
316 | 307 | user.contest_stat.forced_logout = true |
|
317 | 308 | user.contest_stat.reset_timer_and_save |
|
318 | 309 | end |
|
319 | 310 | |
|
320 | 311 | if params[:notification_emails] |
|
321 | 312 | send_contest_update_notification_email(user, contest) |
|
322 | 313 | end |
|
323 | 314 | |
|
324 | 315 | note << user.login |
|
325 | 316 | users << user |
|
326 | 317 | end |
|
327 | 318 | end |
|
328 | 319 | |
|
329 | 320 | if params[:reset_timer] |
|
330 | 321 | logout_users(users) |
|
331 | 322 | end |
|
332 | 323 | |
|
333 | 324 | flash[:notice] = 'User(s) ' + note.join(', ') + |
|
334 | 325 | ' were successfully modified. ' |
|
335 | 326 | redirect_to :action => 'contest_management' |
|
336 | 327 | end |
|
337 | 328 | |
|
338 | 329 | # admin management |
|
339 | 330 | |
|
340 | 331 | def admin |
|
341 | 332 | @admins = User.all.find_all {|user| user.admin? } |
|
342 | 333 | end |
|
343 | 334 | |
|
344 | 335 | def grant_admin |
|
345 | 336 | login = params[:login] |
|
346 | 337 | user = User.find_by_login(login) |
|
347 | 338 | if user!=nil |
|
348 | 339 | admin_role = Role.find_by_name('admin') |
|
349 | 340 | user.roles << admin_role |
|
350 | 341 | else |
|
351 | 342 | flash[:notice] = 'Unknown user' |
|
352 | 343 | end |
|
353 | 344 | flash[:notice] = 'User added as admins' |
|
354 | 345 | redirect_to :action => 'admin' |
|
355 | 346 | end |
|
356 | 347 | |
|
357 | 348 | def revoke_admin |
|
358 | 349 | user = User.find(params[:id]) |
|
359 | 350 | if user==nil |
|
360 | 351 | flash[:notice] = 'Unknown user' |
|
361 | 352 | redirect_to :action => 'admin' and return |
|
362 | 353 | elsif user.login == 'root' |
|
363 | 354 | flash[:notice] = 'You cannot revoke admisnistrator permission from root.' |
|
364 | 355 | redirect_to :action => 'admin' and return |
|
365 | 356 | end |
|
366 | 357 | |
|
367 | 358 | admin_role = Role.find_by_name('admin') |
|
368 | 359 | user.roles.delete(admin_role) |
|
369 | 360 | flash[:notice] = 'User permission revoked' |
|
370 | 361 | redirect_to :action => 'admin' |
|
371 | 362 | end |
|
372 | 363 | |
|
373 | 364 | # mass mailing |
|
374 | 365 | |
|
375 | 366 | def mass_mailing |
|
376 | 367 | end |
|
377 | 368 | |
|
378 | 369 | def bulk_mail |
|
379 | 370 | lines = params[:login_list] |
|
380 | 371 | if !lines or lines.blank? |
|
381 | 372 | flash[:notice] = 'You entered an empty list.' |
|
382 | 373 | redirect_to :action => 'mass_mailing' and return |
|
383 | 374 | end |
|
384 | 375 | |
|
385 | 376 | mail_subject = params[:subject] |
|
386 | 377 | if !mail_subject or mail_subject.blank? |
|
387 | 378 | flash[:notice] = 'You entered an empty mail subject.' |
|
388 | 379 | redirect_to :action => 'mass_mailing' and return |
|
389 | 380 | end |
|
390 | 381 | |
|
391 | 382 | mail_body = params[:email_body] |
|
392 | 383 | if !mail_body or mail_body.blank? |
|
393 | 384 | flash[:notice] = 'You entered an empty mail body.' |
|
394 | 385 | redirect_to :action => 'mass_mailing' and return |
|
395 | 386 | end |
|
396 | 387 | |
|
397 | 388 | note = [] |
|
398 | 389 | users = [] |
|
399 | 390 | lines.split("\n").each do |line| |
|
400 | 391 | user = User.find_by_login(line.chomp) |
|
401 | 392 | if user |
@@ -1,222 +1,219 | |||
|
1 | 1 | require 'net/smtp' |
|
2 | 2 | |
|
3 | 3 | class UsersController < ApplicationController |
|
4 | 4 | |
|
5 | 5 | include MailHelperMethods |
|
6 | 6 | |
|
7 | 7 | before_action :authenticate, :except => [:new, |
|
8 | 8 | :register, |
|
9 | 9 | :confirm, |
|
10 | 10 | :forget, |
|
11 | 11 | :retrieve_password] |
|
12 | 12 | |
|
13 | 13 | before_action :verify_online_registration, :only => [:new, |
|
14 | 14 | :register, |
|
15 | 15 | :forget, |
|
16 | 16 | :retrieve_password] |
|
17 | 17 | before_action :authenticate, :profile_authorization, only: [:profile] |
|
18 | 18 | |
|
19 | 19 | before_action :admin_authorization, only: [:stat, :toggle_activate, :toggle_enable] |
|
20 | 20 | |
|
21 | 21 | |
|
22 | - verify :method => :post, :only => [:chg_passwd], | |
|
23 | - :redirect_to => { :action => :index } | |
|
24 | - | |
|
25 | 22 | #in_place_edit_for :user, :alias_for_editing |
|
26 | 23 | #in_place_edit_for :user, :email_for_editing |
|
27 | 24 | |
|
28 | 25 | def index |
|
29 | 26 | if !GraderConfiguration['system.user_setting_enabled'] |
|
30 | 27 | redirect_to :controller => 'main', :action => 'list' |
|
31 | 28 | else |
|
32 | 29 | @user = User.find(session[:user_id]) |
|
33 | 30 | end |
|
34 | 31 | end |
|
35 | 32 | |
|
36 | 33 | def chg_passwd |
|
37 | 34 | user = User.find(session[:user_id]) |
|
38 | 35 | user.password = params[:passwd] |
|
39 | 36 | user.password_confirmation = params[:passwd_verify] |
|
40 | 37 | if user.save |
|
41 | 38 | flash[:notice] = 'password changed' |
|
42 | 39 | else |
|
43 | 40 | flash[:notice] = 'Error: password changing failed' |
|
44 | 41 | end |
|
45 | 42 | redirect_to :action => 'index' |
|
46 | 43 | end |
|
47 | 44 | |
|
48 | 45 | def new |
|
49 | 46 | @user = User.new |
|
50 | 47 | render :action => 'new', :layout => 'empty' |
|
51 | 48 | end |
|
52 | 49 | |
|
53 | 50 | def register |
|
54 | 51 | if(params[:cancel]) |
|
55 | 52 | redirect_to :controller => 'main', :action => 'login' |
|
56 | 53 | return |
|
57 | 54 | end |
|
58 | 55 | @user = User.new(user_params) |
|
59 | 56 | @user.password_confirmation = @user.password = User.random_password |
|
60 | 57 | @user.activated = false |
|
61 | 58 | if (@user.valid?) and (@user.save) |
|
62 | 59 | if send_confirmation_email(@user) |
|
63 | 60 | render :action => 'new_splash', :layout => 'empty' |
|
64 | 61 | else |
|
65 | 62 | @admin_email = GraderConfiguration['system.admin_email'] |
|
66 | 63 | render :action => 'email_error', :layout => 'empty' |
|
67 | 64 | end |
|
68 | 65 | else |
|
69 | 66 | @user.errors.add(:base,"Email cannot be blank") if @user.email=='' |
|
70 | 67 | render :action => 'new', :layout => 'empty' |
|
71 | 68 | end |
|
72 | 69 | end |
|
73 | 70 | |
|
74 | 71 | def confirm |
|
75 | 72 | login = params[:login] |
|
76 | 73 | key = params[:activation] |
|
77 | 74 | @user = User.find_by_login(login) |
|
78 | 75 | if (@user) and (@user.verify_activation_key(key)) |
|
79 | 76 | if @user.valid? # check uniquenss of email |
|
80 | 77 | @user.activated = true |
|
81 | 78 | @user.save |
|
82 | 79 | @result = :successful |
|
83 | 80 | else |
|
84 | 81 | @result = :email_used |
|
85 | 82 | end |
|
86 | 83 | else |
|
87 | 84 | @result = :failed |
|
88 | 85 | end |
|
89 | 86 | render :action => 'confirm', :layout => 'empty' |
|
90 | 87 | end |
|
91 | 88 | |
|
92 | 89 | def forget |
|
93 | 90 | render :action => 'forget', :layout => 'empty' |
|
94 | 91 | end |
|
95 | 92 | |
|
96 | 93 | def retrieve_password |
|
97 | 94 | email = params[:email] |
|
98 | 95 | user = User.find_by_email(email) |
|
99 | 96 | if user |
|
100 | 97 | last_updated_time = user.updated_at || user.created_at || (Time.now.gmtime - 1.hour) |
|
101 | 98 | if last_updated_time > Time.now.gmtime - 5.minutes |
|
102 | 99 | flash[:notice] = 'The account has recently created or new password has recently been requested. Please wait for 5 minutes' |
|
103 | 100 | else |
|
104 | 101 | user.password = user.password_confirmation = User.random_password |
|
105 | 102 | user.save |
|
106 | 103 | send_new_password_email(user) |
|
107 | 104 | flash[:notice] = 'New password has been mailed to you.' |
|
108 | 105 | end |
|
109 | 106 | else |
|
110 | 107 | flash[:notice] = I18n.t 'registration.password_retrieval.no_email' |
|
111 | 108 | end |
|
112 | 109 | redirect_to :action => 'forget' |
|
113 | 110 | end |
|
114 | 111 | |
|
115 | 112 | def stat |
|
116 | 113 | @user = User.find(params[:id]) |
|
117 | 114 | @submission = Submission.joins(:problem).where(user_id: params[:id]) |
|
118 | 115 | @submission = @submission.where('problems.available = true') unless current_user.admin? |
|
119 | 116 | |
|
120 | 117 | range = 120 |
|
121 | 118 | @histogram = { data: Array.new(range,0), summary: {} } |
|
122 | 119 | @summary = {count: 0, solve: 0, attempt: 0} |
|
123 | 120 | problem = Hash.new(0) |
|
124 | 121 | |
|
125 | 122 | @submission.find_each do |sub| |
|
126 | 123 | #histogram |
|
127 | 124 | d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60 |
|
128 | 125 | @histogram[:data][d.to_i] += 1 if d < range |
|
129 | 126 | |
|
130 | 127 | @summary[:count] += 1 |
|
131 | 128 | next unless sub.problem |
|
132 | 129 | problem[sub.problem] = [problem[sub.problem], ( (sub.try(:points) || 0) >= sub.problem.full_score) ? 1 : 0].max |
|
133 | 130 | end |
|
134 | 131 | |
|
135 | 132 | @histogram[:summary][:max] = [@histogram[:data].max,1].max |
|
136 | 133 | @summary[:attempt] = problem.count |
|
137 | 134 | problem.each_value { |v| @summary[:solve] += 1 if v == 1 } |
|
138 | 135 | end |
|
139 | 136 | |
|
140 | 137 | def toggle_activate |
|
141 | 138 | @user = User.find(params[:id]) |
|
142 | 139 | @user.update_attributes( activated: !@user.activated? ) |
|
143 | 140 | respond_to do |format| |
|
144 | 141 | format.js { render partial: 'toggle_button', |
|
145 | 142 | locals: {button_id: "#toggle_activate_user_#{@user.id}",button_on: @user.activated? } } |
|
146 | 143 | end |
|
147 | 144 | end |
|
148 | 145 | |
|
149 | 146 | def toggle_enable |
|
150 | 147 | @user = User.find(params[:id]) |
|
151 | 148 | @user.update_attributes( enabled: !@user.enabled? ) |
|
152 | 149 | respond_to do |format| |
|
153 | 150 | format.js { render partial: 'toggle_button', |
|
154 | 151 | locals: {button_id: "#toggle_enable_user_#{@user.id}",button_on: @user.enabled? } } |
|
155 | 152 | end |
|
156 | 153 | end |
|
157 | 154 | |
|
158 | 155 | protected |
|
159 | 156 | |
|
160 | 157 | def verify_online_registration |
|
161 | 158 | if !GraderConfiguration['system.online_registration'] |
|
162 | 159 | redirect_to :controller => 'main', :action => 'login' |
|
163 | 160 | end |
|
164 | 161 | end |
|
165 | 162 | |
|
166 | 163 | def send_confirmation_email(user) |
|
167 | 164 | contest_name = GraderConfiguration['contest.name'] |
|
168 | 165 | activation_url = url_for(:action => 'confirm', |
|
169 | 166 | :login => user.login, |
|
170 | 167 | :activation => user.activation_key) |
|
171 | 168 | home_url = url_for(:controller => 'main', :action => 'index') |
|
172 | 169 | mail_subject = "[#{contest_name}] Confirmation" |
|
173 | 170 | mail_body = t('registration.email_body', { |
|
174 | 171 | :full_name => user.full_name, |
|
175 | 172 | :contest_name => contest_name, |
|
176 | 173 | :login => user.login, |
|
177 | 174 | :password => user.password, |
|
178 | 175 | :activation_url => activation_url, |
|
179 | 176 | :admin_email => GraderConfiguration['system.admin_email'] |
|
180 | 177 | }) |
|
181 | 178 | |
|
182 | 179 | logger.info mail_body |
|
183 | 180 | |
|
184 | 181 | send_mail(user.email, mail_subject, mail_body) |
|
185 | 182 | end |
|
186 | 183 | |
|
187 | 184 | def send_new_password_email(user) |
|
188 | 185 | contest_name = GraderConfiguration['contest.name'] |
|
189 | 186 | mail_subject = "[#{contest_name}] Password recovery" |
|
190 | 187 | mail_body = t('registration.password_retrieval.email_body', { |
|
191 | 188 | :full_name => user.full_name, |
|
192 | 189 | :contest_name => contest_name, |
|
193 | 190 | :login => user.login, |
|
194 | 191 | :password => user.password, |
|
195 | 192 | :admin_email => GraderConfiguration['system.admin_email'] |
|
196 | 193 | }) |
|
197 | 194 | |
|
198 | 195 | logger.info mail_body |
|
199 | 196 | |
|
200 | 197 | send_mail(user.email, mail_subject, mail_body) |
|
201 | 198 | end |
|
202 | 199 | |
|
203 | 200 | # allow viewing of regular user profile only when options allow so |
|
204 | 201 | # only admins can view admins profile |
|
205 | 202 | def profile_authorization |
|
206 | 203 | #if view admins' profile, allow only admin |
|
207 | 204 | return false unless(params[:id]) |
|
208 | 205 | user = User.find(params[:id]) |
|
209 | 206 | return false unless user |
|
210 | 207 | return admin_authorization if user.admin? |
|
211 | 208 | return true if GraderConfiguration["right.user_view_submission"] |
|
212 | 209 | |
|
213 | 210 | #finally, we allow only admin |
|
214 | 211 | admin_authorization |
|
215 | 212 | end |
|
216 | 213 | |
|
217 | 214 | private |
|
218 | 215 | def user_params |
|
219 | 216 | params.require(:user).permit(:login, :full_name, :email) |
|
220 | 217 | end |
|
221 | 218 | |
|
222 | 219 | end |
@@ -1,13 +1,13 | |||
|
1 | 1 | class Group < ActiveRecord::Base |
|
2 | - has_many :groups_problems, class_name: GroupProblem | |
|
2 | + has_many :groups_problems, class_name: 'GroupProblem' | |
|
3 | 3 | has_many :problems, :through => :groups_problems |
|
4 | 4 | |
|
5 | - has_many :groups_users, class_name: GroupUser | |
|
5 | + has_many :groups_users, class_name: 'GroupUser' | |
|
6 | 6 | has_many :users, :through => :groups_users |
|
7 | 7 | |
|
8 | 8 | #has_and_belongs_to_many :problems |
|
9 | 9 | #has_and_belongs_to_many :users |
|
10 | 10 | |
|
11 | 11 | |
|
12 | 12 | end |
|
13 | 13 |
@@ -1,146 +1,146 | |||
|
1 | 1 | class Problem < ActiveRecord::Base |
|
2 | 2 | |
|
3 | 3 | belongs_to :description |
|
4 | 4 | has_and_belongs_to_many :contests, :uniq => true |
|
5 | 5 | |
|
6 | 6 | #has_and_belongs_to_many :groups |
|
7 | - has_many :groups_problems, class_name: GroupProblem | |
|
7 | + has_many :groups_problems, class_name: 'GroupProblem' | |
|
8 | 8 | has_many :groups, :through => :groups_problems |
|
9 | 9 | |
|
10 | - has_many :problems_tags, class_name: ProblemTag | |
|
10 | + has_many :problems_tags, class_name: 'ProblemTag' | |
|
11 | 11 | has_many :tags, through: :problems_tags |
|
12 | 12 | |
|
13 | 13 | has_many :test_pairs, :dependent => :delete_all |
|
14 | 14 | has_many :testcases, :dependent => :destroy |
|
15 | 15 | |
|
16 | 16 | has_many :submissions |
|
17 | 17 | |
|
18 | 18 | validates_presence_of :name |
|
19 | 19 | validates_format_of :name, :with => /\A\w+\z/ |
|
20 | 20 | validates_presence_of :full_name |
|
21 | 21 | |
|
22 | 22 | scope :available, -> { where(available: true) } |
|
23 | 23 | |
|
24 | 24 | DEFAULT_TIME_LIMIT = 1 |
|
25 | 25 | DEFAULT_MEMORY_LIMIT = 32 |
|
26 | 26 | |
|
27 | 27 | def self.available_problems |
|
28 | 28 | available.order(date_added: :desc).order(:name) |
|
29 | 29 | #Problem.available.all(:order => "date_added DESC, name ASC") |
|
30 | 30 | end |
|
31 | 31 | |
|
32 | 32 | def self.create_from_import_form_params(params, old_problem=nil) |
|
33 | 33 | org_problem = old_problem || Problem.new |
|
34 | 34 | import_params, problem = Problem.extract_params_and_check(params, |
|
35 | 35 | org_problem) |
|
36 | 36 | |
|
37 | 37 | if !problem.errors.empty? |
|
38 | 38 | return problem, 'Error importing' |
|
39 | 39 | end |
|
40 | 40 | |
|
41 | 41 | problem.full_score = 100 |
|
42 | 42 | problem.date_added = Time.new |
|
43 | 43 | problem.test_allowed = true |
|
44 | 44 | problem.output_only = false |
|
45 | 45 | problem.available = false |
|
46 | 46 | |
|
47 | 47 | if not problem.save |
|
48 | 48 | return problem, 'Error importing' |
|
49 | 49 | end |
|
50 | 50 | |
|
51 | 51 | import_to_db = params.has_key? :import_to_db |
|
52 | 52 | |
|
53 | 53 | importer = TestdataImporter.new(problem) |
|
54 | 54 | |
|
55 | 55 | if not importer.import_from_file(import_params[:file], |
|
56 | 56 | import_params[:time_limit], |
|
57 | 57 | import_params[:memory_limit], |
|
58 | 58 | import_params[:checker_name], |
|
59 | 59 | import_to_db) |
|
60 | 60 | problem.errors.add(:base,'Import error.') |
|
61 | 61 | end |
|
62 | 62 | |
|
63 | 63 | return problem, importer.log_msg |
|
64 | 64 | end |
|
65 | 65 | |
|
66 | 66 | def self.download_file_basedir |
|
67 | 67 | return "#{Rails.root}/data/tasks" |
|
68 | 68 | end |
|
69 | 69 | |
|
70 | 70 | def get_submission_stat |
|
71 | 71 | result = Hash.new |
|
72 | 72 | #total number of submission |
|
73 | 73 | result[:total_sub] = Submission.where(problem_id: self.id).count |
|
74 | 74 | result[:attempted_user] = Submission.where(problem_id: self.id).group(:user_id) |
|
75 | 75 | result[:pass] = Submission.where(problem_id: self.id).where("points >= ?",self.full_score).count |
|
76 | 76 | return result |
|
77 | 77 | end |
|
78 | 78 | |
|
79 | 79 | def long_name |
|
80 | 80 | "[#{name}] #{full_name}" |
|
81 | 81 | end |
|
82 | 82 | |
|
83 | 83 | protected |
|
84 | 84 | |
|
85 | 85 | def self.to_i_or_default(st, default) |
|
86 | 86 | if st!='' |
|
87 | 87 | result = st.to_i |
|
88 | 88 | end |
|
89 | 89 | result ||= default |
|
90 | 90 | end |
|
91 | 91 | |
|
92 | 92 | def self.to_f_or_default(st, default) |
|
93 | 93 | if st!='' |
|
94 | 94 | result = st.to_f |
|
95 | 95 | end |
|
96 | 96 | result ||= default |
|
97 | 97 | end |
|
98 | 98 | |
|
99 | 99 | def self.extract_params_and_check(params, problem) |
|
100 | 100 | time_limit = Problem.to_f_or_default(params[:time_limit], |
|
101 | 101 | DEFAULT_TIME_LIMIT) |
|
102 | 102 | memory_limit = Problem.to_i_or_default(params[:memory_limit], |
|
103 | 103 | DEFAULT_MEMORY_LIMIT) |
|
104 | 104 | |
|
105 | 105 | if time_limit<=0 or time_limit >60 |
|
106 | 106 | problem.errors.add(:base,'Time limit out of range.') |
|
107 | 107 | end |
|
108 | 108 | |
|
109 | 109 | if memory_limit==0 and params[:memory_limit]!='0' |
|
110 | 110 | problem.errors.add(:base,'Memory limit format errors.') |
|
111 | 111 | elsif memory_limit<=0 or memory_limit >512 |
|
112 | 112 | problem.errors.add(:base,'Memory limit out of range.') |
|
113 | 113 | end |
|
114 | 114 | |
|
115 | 115 | if params[:file]==nil or params[:file]=='' |
|
116 | 116 | problem.errors.add(:base,'No testdata file.') |
|
117 | 117 | end |
|
118 | 118 | |
|
119 | 119 | checker_name = 'text' |
|
120 | 120 | if ['text','float'].include? params[:checker] |
|
121 | 121 | checker_name = params[:checker] |
|
122 | 122 | end |
|
123 | 123 | |
|
124 | 124 | file = params[:file] |
|
125 | 125 | |
|
126 | 126 | if !problem.errors.empty? |
|
127 | 127 | return nil, problem |
|
128 | 128 | end |
|
129 | 129 | |
|
130 | 130 | problem.name = params[:name] |
|
131 | 131 | if params[:full_name]!='' |
|
132 | 132 | problem.full_name = params[:full_name] |
|
133 | 133 | else |
|
134 | 134 | problem.full_name = params[:name] |
|
135 | 135 | end |
|
136 | 136 | |
|
137 | 137 | return [{ |
|
138 | 138 | :time_limit => time_limit, |
|
139 | 139 | :memory_limit => memory_limit, |
|
140 | 140 | :file => file, |
|
141 | 141 | :checker_name => checker_name |
|
142 | 142 | }, |
|
143 | 143 | problem] |
|
144 | 144 | end |
|
145 | 145 | |
|
146 | 146 | end |
@@ -1,4 +1,4 | |||
|
1 | 1 | class Tag < ActiveRecord::Base |
|
2 | - has_many :problems_tags, class_name: ProblemTag | |
|
2 | + has_many :problems_tags, class_name: 'ProblemTag' | |
|
3 | 3 | has_many :problems, through: :problems_tags |
|
4 | 4 | end |
@@ -1,369 +1,369 | |||
|
1 | 1 | require 'digest/sha1' |
|
2 | 2 | require 'net/pop' |
|
3 | 3 | require 'net/https' |
|
4 | 4 | require 'net/http' |
|
5 | 5 | require 'json' |
|
6 | 6 | |
|
7 | 7 | class User < ActiveRecord::Base |
|
8 | 8 | |
|
9 | 9 | has_and_belongs_to_many :roles |
|
10 | 10 | |
|
11 | 11 | #has_and_belongs_to_many :groups |
|
12 | - has_many :groups_users, class_name: GroupUser | |
|
12 | + has_many :groups_users, class_name: 'GroupUser' | |
|
13 | 13 | has_many :groups, :through => :groups_users |
|
14 | 14 | |
|
15 | 15 | has_many :test_requests, -> {order(submitted_at: :desc)} |
|
16 | 16 | |
|
17 | 17 | has_many :messages, -> { order(created_at: :desc) }, |
|
18 | 18 | :class_name => "Message", |
|
19 | 19 | :foreign_key => "sender_id" |
|
20 | 20 | |
|
21 | 21 | has_many :replied_messages, -> { order(created_at: :desc) }, |
|
22 | 22 | :class_name => "Message", |
|
23 | 23 | :foreign_key => "receiver_id" |
|
24 | 24 | |
|
25 | 25 | has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy |
|
26 | 26 | |
|
27 | 27 | belongs_to :site |
|
28 | 28 | belongs_to :country |
|
29 | 29 | |
|
30 | 30 | has_and_belongs_to_many :contests, -> { order(:name); uniq} |
|
31 | 31 | |
|
32 | 32 | scope :activated_users, -> {where activated: true} |
|
33 | 33 | |
|
34 | 34 | validates_presence_of :login |
|
35 | 35 | validates_uniqueness_of :login |
|
36 | 36 | validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/ |
|
37 | 37 | validates_length_of :login, :within => 3..30 |
|
38 | 38 | |
|
39 | 39 | validates_presence_of :full_name |
|
40 | 40 | validates_length_of :full_name, :minimum => 1 |
|
41 | 41 | |
|
42 | 42 | validates_presence_of :password, :if => :password_required? |
|
43 | 43 | validates_length_of :password, :within => 4..20, :if => :password_required? |
|
44 | 44 | validates_confirmation_of :password, :if => :password_required? |
|
45 | 45 | |
|
46 | 46 | validates_format_of :email, |
|
47 | 47 | :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, |
|
48 | 48 | :if => :email_validation? |
|
49 | 49 | validate :uniqueness_of_email_from_activated_users, |
|
50 | 50 | :if => :email_validation? |
|
51 | 51 | validate :enough_time_interval_between_same_email_registrations, |
|
52 | 52 | :if => :email_validation? |
|
53 | 53 | |
|
54 | 54 | # these are for ytopc |
|
55 | 55 | # disable for now |
|
56 | 56 | #validates_presence_of :province |
|
57 | 57 | |
|
58 | 58 | attr_accessor :password |
|
59 | 59 | |
|
60 | 60 | before_save :encrypt_new_password |
|
61 | 61 | before_save :assign_default_site |
|
62 | 62 | before_save :assign_default_contest |
|
63 | 63 | |
|
64 | 64 | # this is for will_paginate |
|
65 | 65 | cattr_reader :per_page |
|
66 | 66 | @@per_page = 50 |
|
67 | 67 | |
|
68 | 68 | def self.authenticate(login, password) |
|
69 | 69 | user = find_by_login(login) |
|
70 | 70 | if user |
|
71 | 71 | return user if user.authenticated?(password) |
|
72 | 72 | end |
|
73 | 73 | end |
|
74 | 74 | |
|
75 | 75 | def authenticated?(password) |
|
76 | 76 | if self.activated |
|
77 | 77 | hashed_password == User.encrypt(password,self.salt) |
|
78 | 78 | else |
|
79 | 79 | false |
|
80 | 80 | end |
|
81 | 81 | end |
|
82 | 82 | |
|
83 | 83 | def admin? |
|
84 | 84 | self.roles.detect {|r| r.name == 'admin' } |
|
85 | 85 | end |
|
86 | 86 | |
|
87 | 87 | def email_for_editing |
|
88 | 88 | if self.email==nil |
|
89 | 89 | "(unknown)" |
|
90 | 90 | elsif self.email=='' |
|
91 | 91 | "(blank)" |
|
92 | 92 | else |
|
93 | 93 | self.email |
|
94 | 94 | end |
|
95 | 95 | end |
|
96 | 96 | |
|
97 | 97 | def email_for_editing=(e) |
|
98 | 98 | self.email=e |
|
99 | 99 | end |
|
100 | 100 | |
|
101 | 101 | def alias_for_editing |
|
102 | 102 | if self.alias==nil |
|
103 | 103 | "(unknown)" |
|
104 | 104 | elsif self.alias=='' |
|
105 | 105 | "(blank)" |
|
106 | 106 | else |
|
107 | 107 | self.alias |
|
108 | 108 | end |
|
109 | 109 | end |
|
110 | 110 | |
|
111 | 111 | def alias_for_editing=(e) |
|
112 | 112 | self.alias=e |
|
113 | 113 | end |
|
114 | 114 | |
|
115 | 115 | def activation_key |
|
116 | 116 | if self.hashed_password==nil |
|
117 | 117 | encrypt_new_password |
|
118 | 118 | end |
|
119 | 119 | Digest::SHA1.hexdigest(self.hashed_password)[0..7] |
|
120 | 120 | end |
|
121 | 121 | |
|
122 | 122 | def verify_activation_key(key) |
|
123 | 123 | key == activation_key |
|
124 | 124 | end |
|
125 | 125 | |
|
126 | 126 | def self.random_password(length=5) |
|
127 | 127 | chars = 'abcdefghjkmnopqrstuvwxyz' |
|
128 | 128 | password = '' |
|
129 | 129 | length.times { password << chars[rand(chars.length - 1)] } |
|
130 | 130 | password |
|
131 | 131 | end |
|
132 | 132 | |
|
133 | 133 | def self.find_non_admin_with_prefix(prefix='') |
|
134 | 134 | users = User.all |
|
135 | 135 | return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 } |
|
136 | 136 | end |
|
137 | 137 | |
|
138 | 138 | # Contest information |
|
139 | 139 | |
|
140 | 140 | def self.find_users_with_no_contest() |
|
141 | 141 | users = User.all |
|
142 | 142 | return users.find_all { |u| u.contests.length == 0 } |
|
143 | 143 | end |
|
144 | 144 | |
|
145 | 145 | |
|
146 | 146 | def contest_time_left |
|
147 | 147 | if GraderConfiguration.contest_mode? |
|
148 | 148 | return nil if site==nil |
|
149 | 149 | return site.time_left |
|
150 | 150 | elsif GraderConfiguration.indv_contest_mode? |
|
151 | 151 | time_limit = GraderConfiguration.contest_time_limit |
|
152 | 152 | if time_limit == nil |
|
153 | 153 | return nil |
|
154 | 154 | end |
|
155 | 155 | if contest_stat==nil or contest_stat.started_at==nil |
|
156 | 156 | return (Time.now.gmtime + time_limit) - Time.now.gmtime |
|
157 | 157 | else |
|
158 | 158 | finish_time = contest_stat.started_at + time_limit |
|
159 | 159 | current_time = Time.now.gmtime |
|
160 | 160 | if current_time > finish_time |
|
161 | 161 | return 0 |
|
162 | 162 | else |
|
163 | 163 | return finish_time - current_time |
|
164 | 164 | end |
|
165 | 165 | end |
|
166 | 166 | else |
|
167 | 167 | return nil |
|
168 | 168 | end |
|
169 | 169 | end |
|
170 | 170 | |
|
171 | 171 | def contest_finished? |
|
172 | 172 | if GraderConfiguration.contest_mode? |
|
173 | 173 | return false if site==nil |
|
174 | 174 | return site.finished? |
|
175 | 175 | elsif GraderConfiguration.indv_contest_mode? |
|
176 | 176 | return false if self.contest_stat(true)==nil |
|
177 | 177 | return contest_time_left == 0 |
|
178 | 178 | else |
|
179 | 179 | return false |
|
180 | 180 | end |
|
181 | 181 | end |
|
182 | 182 | |
|
183 | 183 | def contest_started? |
|
184 | 184 | if GraderConfiguration.indv_contest_mode? |
|
185 | 185 | stat = self.contest_stat |
|
186 | 186 | return ((stat != nil) and (stat.started_at != nil)) |
|
187 | 187 | elsif GraderConfiguration.contest_mode? |
|
188 | 188 | return true if site==nil |
|
189 | 189 | return site.started |
|
190 | 190 | else |
|
191 | 191 | return true |
|
192 | 192 | end |
|
193 | 193 | end |
|
194 | 194 | |
|
195 | 195 | def update_start_time |
|
196 | 196 | stat = self.contest_stat |
|
197 | 197 | if stat.nil? or stat.started_at.nil? |
|
198 | 198 | stat ||= UserContestStat.new(:user => self) |
|
199 | 199 | stat.started_at = Time.now.gmtime |
|
200 | 200 | stat.save |
|
201 | 201 | end |
|
202 | 202 | end |
|
203 | 203 | |
|
204 | 204 | def problem_in_user_contests?(problem) |
|
205 | 205 | problem_contests = problem.contests.all |
|
206 | 206 | |
|
207 | 207 | if problem_contests.length == 0 # this is public contest |
|
208 | 208 | return true |
|
209 | 209 | end |
|
210 | 210 | |
|
211 | 211 | contests.each do |contest| |
|
212 | 212 | if problem_contests.find {|c| c.id == contest.id } |
|
213 | 213 | return true |
|
214 | 214 | end |
|
215 | 215 | end |
|
216 | 216 | return false |
|
217 | 217 | end |
|
218 | 218 | |
|
219 | 219 | def available_problems_group_by_contests |
|
220 | 220 | contest_problems = [] |
|
221 | 221 | pin = {} |
|
222 | 222 | contests.enabled.each do |contest| |
|
223 | 223 | available_problems = contest.problems.available |
|
224 | 224 | contest_problems << { |
|
225 | 225 | :contest => contest, |
|
226 | 226 | :problems => available_problems |
|
227 | 227 | } |
|
228 | 228 | available_problems.each {|p| pin[p.id] = true} |
|
229 | 229 | end |
|
230 | 230 | other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0} |
|
231 | 231 | contest_problems << { |
|
232 | 232 | :contest => nil, |
|
233 | 233 | :problems => other_avaiable_problems |
|
234 | 234 | } |
|
235 | 235 | return contest_problems |
|
236 | 236 | end |
|
237 | 237 | |
|
238 | 238 | def solve_all_available_problems? |
|
239 | 239 | available_problems.each do |p| |
|
240 | 240 | u = self |
|
241 | 241 | sub = Submission.find_last_by_user_and_problem(u.id,p.id) |
|
242 | 242 | return false if !p or !sub or sub.points < p.full_score |
|
243 | 243 | end |
|
244 | 244 | return true |
|
245 | 245 | end |
|
246 | 246 | |
|
247 | 247 | #get a list of available problem |
|
248 | 248 | def available_problems |
|
249 | 249 | if not GraderConfiguration.multicontests? |
|
250 | 250 | if GraderConfiguration.use_problem_group? |
|
251 | 251 | return available_problems_in_group |
|
252 | 252 | else |
|
253 | 253 | return Problem.available_problems |
|
254 | 254 | end |
|
255 | 255 | else |
|
256 | 256 | contest_problems = [] |
|
257 | 257 | pin = {} |
|
258 | 258 | contests.enabled.each do |contest| |
|
259 | 259 | contest.problems.available.each do |problem| |
|
260 | 260 | if not pin.has_key? problem.id |
|
261 | 261 | contest_problems << problem |
|
262 | 262 | end |
|
263 | 263 | pin[problem.id] = true |
|
264 | 264 | end |
|
265 | 265 | end |
|
266 | 266 | other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0} |
|
267 | 267 | return contest_problems + other_avaiable_problems |
|
268 | 268 | end |
|
269 | 269 | end |
|
270 | 270 | |
|
271 | 271 | def available_problems_in_group |
|
272 | 272 | problem = [] |
|
273 | 273 | self.groups.each do |group| |
|
274 | 274 | group.problems.where(available: true).each { |p| problem << p } |
|
275 | 275 | end |
|
276 | 276 | problem.uniq! |
|
277 | 277 | if problem |
|
278 | 278 | problem.sort! do |a,b| |
|
279 | 279 | case |
|
280 | 280 | when a.date_added < b.date_added |
|
281 | 281 | 1 |
|
282 | 282 | when a.date_added > b.date_added |
|
283 | 283 | -1 |
|
284 | 284 | else |
|
285 | 285 | a.name <=> b.name |
|
286 | 286 | end |
|
287 | 287 | end |
|
288 | 288 | return problem |
|
289 | 289 | else |
|
290 | 290 | return [] |
|
291 | 291 | end |
|
292 | 292 | end |
|
293 | 293 | |
|
294 | 294 | def can_view_problem?(problem) |
|
295 | 295 | return true if admin? |
|
296 | 296 | return available_problems.include? problem |
|
297 | 297 | end |
|
298 | 298 | |
|
299 | 299 | def self.clear_last_login |
|
300 | 300 | User.update_all(:last_ip => nil) |
|
301 | 301 | end |
|
302 | 302 | |
|
303 | 303 | protected |
|
304 | 304 | def encrypt_new_password |
|
305 | 305 | return if password.blank? |
|
306 | 306 | self.salt = (10+rand(90)).to_s |
|
307 | 307 | self.hashed_password = User.encrypt(self.password,self.salt) |
|
308 | 308 | end |
|
309 | 309 | |
|
310 | 310 | def assign_default_site |
|
311 | 311 | # have to catch error when migrating (because self.site is not available). |
|
312 | 312 | begin |
|
313 | 313 | if self.site==nil |
|
314 | 314 | self.site = Site.find_by_name('default') |
|
315 | 315 | if self.site==nil |
|
316 | 316 | self.site = Site.find(1) # when 'default has be renamed' |
|
317 | 317 | end |
|
318 | 318 | end |
|
319 | 319 | rescue |
|
320 | 320 | end |
|
321 | 321 | end |
|
322 | 322 | |
|
323 | 323 | def assign_default_contest |
|
324 | 324 | # have to catch error when migrating (because self.site is not available). |
|
325 | 325 | begin |
|
326 | 326 | if self.contests.length == 0 |
|
327 | 327 | default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name']) |
|
328 | 328 | if default_contest |
|
329 | 329 | self.contests = [default_contest] |
|
330 | 330 | end |
|
331 | 331 | end |
|
332 | 332 | rescue |
|
333 | 333 | end |
|
334 | 334 | end |
|
335 | 335 | |
|
336 | 336 | def password_required? |
|
337 | 337 | self.hashed_password.blank? || !self.password.blank? |
|
338 | 338 | end |
|
339 | 339 | |
|
340 | 340 | def self.encrypt(string,salt) |
|
341 | 341 | Digest::SHA1.hexdigest(salt + string) |
|
342 | 342 | end |
|
343 | 343 | |
|
344 | 344 | def uniqueness_of_email_from_activated_users |
|
345 | 345 | user = User.activated_users.find_by_email(self.email) |
|
346 | 346 | if user and (user.login != self.login) |
|
347 | 347 | self.errors.add(:base,"Email has already been taken") |
|
348 | 348 | end |
|
349 | 349 | end |
|
350 | 350 | |
|
351 | 351 | def enough_time_interval_between_same_email_registrations |
|
352 | 352 | return if !self.new_record? |
|
353 | 353 | return if self.activated |
|
354 | 354 | open_user = User.find_by_email(self.email, |
|
355 | 355 | :order => 'created_at DESC') |
|
356 | 356 | if open_user and open_user.created_at and |
|
357 | 357 | (open_user.created_at > Time.now.gmtime - 5.minutes) |
|
358 | 358 | self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)") |
|
359 | 359 | end |
|
360 | 360 | end |
|
361 | 361 | |
|
362 | 362 | def email_validation? |
|
363 | 363 | begin |
|
364 | 364 | return VALIDATE_USER_EMAILS |
|
365 | 365 | rescue |
|
366 | 366 | return false |
|
367 | 367 | end |
|
368 | 368 | end |
|
369 | 369 | end |
@@ -1,65 +1,65 | |||
|
1 | 1 | - content_for :head do |
|
2 | 2 | = stylesheet_link_tag 'problems' |
|
3 | 3 | %h1 Problems |
|
4 | 4 | %p |
|
5 | 5 | = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-success btn-sm' |
|
6 | 6 | = link_to 'New problem', new_problem_path, class: 'btn btn-success btn-sm' |
|
7 | 7 | = link_to 'Bulk Manage', { action: 'manage'}, class: 'btn btn-info btn-sm' |
|
8 | 8 | = link_to 'Turn off all problems', {:action => 'turn_all_off'}, class: 'btn btn-default btn-sm' |
|
9 | 9 | = link_to 'Turn on all problems', {:action => 'turn_all_on'}, class: 'btn btn-default btn-sm' |
|
10 | 10 | .submitbox |
|
11 |
- = form_tag |
|
|
11 | + = form_tag action: 'quick_create', controller: 'problems' do | |
|
12 | 12 | %b Quick New: |
|
13 | 13 | %label{:for => "problem_name"} Name |
|
14 | 14 | = text_field 'problem', 'name' |
|
15 | 15 | | |
|
16 | 16 | %label{:for => "problem_full_name"} Full name |
|
17 | 17 | = text_field 'problem', 'full_name' |
|
18 | 18 | = submit_tag "Create" |
|
19 | 19 | %table.table.table-condense.table-hover |
|
20 | 20 | %thead |
|
21 | 21 | %th Name |
|
22 | 22 | %th Full name |
|
23 | 23 | %th.text-right Full score |
|
24 | 24 | %th Tags |
|
25 | 25 | %th |
|
26 | 26 | Submit |
|
27 | 27 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Admin can always submit to any problem' } [?] |
|
28 | 28 | %th Date added |
|
29 | 29 | %th.text-center |
|
30 | 30 | Avail? |
|
31 | 31 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user submits to this problem?' } [?] |
|
32 | 32 | %th.text-center |
|
33 | 33 | View Data? |
|
34 | 34 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user view the testcase of this problem?' } [?] |
|
35 | 35 | %th.text-center |
|
36 | 36 | Test? |
|
37 | 37 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user uses test interface on this problem?' } [?] |
|
38 | 38 | - if GraderConfiguration.multicontests? |
|
39 | 39 | %th Contests |
|
40 | 40 | - for problem in @problems |
|
41 | 41 | %tr{:class => "#{(problem.available) ? "success" : "danger"}", :id => "prob-#{problem.id}", :name => "prob-#{problem.id}"} |
|
42 | 42 | - @problem=problem |
|
43 | 43 | %td= problem.name #in_place_editor_field :problem, :name, {}, :rows=>1 |
|
44 | 44 | %td |
|
45 | 45 | = problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1 |
|
46 | 46 | = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, problem |
|
47 | 47 | %td.text-right= problem.full_score #in_place_editor_field :problem, :full_score, {}, :rows=>1 |
|
48 | 48 | %td |
|
49 | 49 | - problem.tags.each do |t| |
|
50 | 50 | - #%button.btn.btn-default.btn-xs= t.name |
|
51 | 51 | %span.label.label-default= t.name |
|
52 | 52 | %td= link_to "Submit", direct_edit_problem_submissions_path(problem,@current_user.id), class: 'btn btn-xs btn-primary' |
|
53 | 53 | %td= problem.date_added |
|
54 | 54 | %td= toggle_button(@problem.available?, toggle_problem_path(@problem), "problem-avail-#{@problem.id}") |
|
55 | 55 | %td= toggle_button(@problem.view_testcase?, toggle_view_testcase_problem_path(@problem), "problem-view-testcase-#{@problem.id}") |
|
56 | 56 | %td= toggle_button(@problem.test_allowed?, toggle_test_problem_path(@problem), "problem-test-#{@problem.id}") |
|
57 | 57 | - if GraderConfiguration.multicontests? |
|
58 | 58 | %td |
|
59 | 59 | = problem.contests.collect { |c| c.name }.join(', ') |
|
60 | 60 | %td= link_to 'Stat', {:action => 'stat', :id => problem.id}, class: 'btn btn-info btn-xs btn-block' |
|
61 | 61 | %td= link_to 'Show', {:action => 'show', :id => problem}, class: 'btn btn-info btn-xs btn-block' |
|
62 | 62 | %td= link_to 'Edit', {:action => 'edit', :id => problem}, class: 'btn btn-info btn-xs btn-block' |
|
63 | 63 | %td= link_to 'Destroy', { :action => 'destroy', :id => problem }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block' |
|
64 | 64 | %br/ |
|
65 | 65 | = link_to '[New problem]', :action => 'new' |
@@ -1,8 +1,8 | |||
|
1 | 1 | <h1>New problem</h1> |
|
2 | 2 | |
|
3 | 3 | <%= form_tag :action => 'create' do %> |
|
4 | 4 | <%= render :partial => 'form' %> |
|
5 | 5 | <%= submit_tag "Create" %> |
|
6 | 6 | <% end %> |
|
7 | 7 | |
|
8 |
- <%= link_to 'Back', |
|
|
8 | + <%= link_to 'Back', problems_path %> |
@@ -1,13 +1,13 | |||
|
1 | 1 | %h1 Editing user |
|
2 | 2 | |
|
3 | 3 | = form_tag( {:action => 'update', :id => @user}, {class: 'form-horizontal'}) do |
|
4 | 4 | = error_messages_for 'user' |
|
5 | 5 | = render partial: "form" |
|
6 | 6 | .form-group |
|
7 | 7 | .col-md-offset-2.col-md-4 |
|
8 | 8 | = submit_tag "Edit", class: 'btn btn-primary' |
|
9 | 9 | |
|
10 | 10 | |
|
11 | 11 | = link_to 'Show', :action => 'show', :id => @user |
|
12 | 12 | | |
|
13 |
- = link_to 'Back', :action => ' |
|
|
13 | + = link_to 'Back', :action => 'index' |
@@ -1,25 +1,25 | |||
|
1 | 1 | # Be sure to restart your server when you modify this file. |
|
2 | 2 | # |
|
3 | 3 | # This file contains migration options to ease your Rails 5.0 upgrade. |
|
4 | 4 | # |
|
5 | 5 | # Once upgraded flip defaults one by one to migrate to the new default. |
|
6 | 6 | # |
|
7 | 7 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. |
|
8 | 8 | |
|
9 | 9 | Rails.application.config.action_controller.raise_on_unfiltered_parameters = true |
|
10 | 10 | |
|
11 | 11 | # Enable per-form CSRF tokens. Previous versions had false. |
|
12 | 12 | Rails.application.config.action_controller.per_form_csrf_tokens = false |
|
13 | 13 | |
|
14 | 14 | # Enable origin-checking CSRF mitigation. Previous versions had false. |
|
15 | 15 | Rails.application.config.action_controller.forgery_protection_origin_check = false |
|
16 | 16 | |
|
17 | 17 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. |
|
18 | 18 | # Previous versions had false. |
|
19 | 19 | ActiveSupport.to_time_preserves_timezone = false |
|
20 | 20 | |
|
21 | 21 | # Require `belongs_to` associations by default. Previous versions had false. |
|
22 | 22 | Rails.application.config.active_record.belongs_to_required_by_default = false |
|
23 | 23 | |
|
24 | 24 | # Do not halt callback chains when a callback returns false. Previous versions had true. |
|
25 | - ActiveSupport.halt_callback_chains_on_return_false = true | |
|
25 | + # ActiveSupport.halt_callback_chains_on_return_false = true |
@@ -1,167 +1,171 | |||
|
1 | 1 | Rails.application.routes.draw do |
|
2 | 2 | resources :tags |
|
3 | 3 | get "sources/direct_edit" |
|
4 | 4 | |
|
5 | 5 | root :to => 'main#login' |
|
6 | 6 | |
|
7 | 7 | #logins |
|
8 | 8 | match 'login/login', to: 'login#login', via: [:get,:post] |
|
9 | 9 | |
|
10 | 10 | |
|
11 | 11 | resources :contests |
|
12 | 12 | |
|
13 | 13 | resources :sites |
|
14 | 14 | |
|
15 | 15 | resources :test |
|
16 | 16 | |
|
17 | 17 | resources :messages do |
|
18 | 18 | collection do |
|
19 | 19 | get 'console' |
|
20 | 20 | end |
|
21 | 21 | end |
|
22 | 22 | |
|
23 | 23 | resources :announcements do |
|
24 | 24 | member do |
|
25 | 25 | get 'toggle','toggle_front' |
|
26 | 26 | end |
|
27 | 27 | end |
|
28 | 28 | |
|
29 | 29 | resources :problems do |
|
30 | 30 | member do |
|
31 | 31 | get 'toggle' |
|
32 | 32 | get 'toggle_test' |
|
33 | 33 | get 'toggle_view_testcase' |
|
34 | 34 | get 'stat' |
|
35 | 35 | end |
|
36 | 36 | collection do |
|
37 | 37 | get 'turn_all_off' |
|
38 | 38 | get 'turn_all_on' |
|
39 | 39 | get 'import' |
|
40 | 40 | get 'manage' |
|
41 | + get 'quick_create' | |
|
42 | + post 'do_manage' | |
|
41 | 43 | end |
|
42 | 44 | end |
|
43 | 45 | |
|
44 | 46 | resources :groups do |
|
45 | 47 | member do |
|
46 | 48 | post 'add_user', to: 'groups#add_user', as: 'add_user' |
|
47 | 49 | delete 'remove_user/:user_id', to: 'groups#remove_user', as: 'remove_user' |
|
48 | 50 | delete 'remove_all_user', to: 'groups#remove_all_user', as: 'remove_all_user' |
|
49 | 51 | post 'add_problem', to: 'groups#add_problem', as: 'add_problem' |
|
50 | 52 | delete 'remove_problem/:problem_id', to: 'groups#remove_problem', as: 'remove_problem' |
|
51 | 53 | delete 'remove_all_problem', to: 'groups#remove_all_problem', as: 'remove_all_problem' |
|
52 | 54 | end |
|
53 | 55 | collection do |
|
54 | 56 | |
|
55 | 57 | end |
|
56 | 58 | end |
|
57 | 59 | |
|
58 | 60 | resources :testcases, only: [] do |
|
59 | 61 | member do |
|
60 | 62 | get 'download_input' |
|
61 | 63 | get 'download_sol' |
|
62 | 64 | end |
|
63 | 65 | collection do |
|
64 | 66 | get 'show_problem/:problem_id(/:test_num)' => 'testcases#show_problem', as: 'show_problem' |
|
65 | 67 | end |
|
66 | 68 | end |
|
67 | 69 | |
|
68 | 70 | resources :grader_configuration, controller: 'configurations' |
|
69 | 71 | |
|
70 | 72 | resources :users do |
|
71 | 73 | member do |
|
72 | 74 | get 'toggle_activate', 'toggle_enable' |
|
73 | 75 | get 'stat' |
|
74 | 76 | end |
|
75 | 77 | end |
|
76 | 78 | |
|
77 | 79 | resources :submissions do |
|
78 | 80 | member do |
|
79 | 81 | get 'download' |
|
80 | 82 | get 'compiler_msg' |
|
81 | 83 | get 'rejudge' |
|
82 | 84 | end |
|
83 | 85 | collection do |
|
84 | 86 | get 'prob/:problem_id', to: 'submissions#index', as: 'problem' |
|
85 | 87 | get 'direct_edit_problem/:problem_id(/:user_id)', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem' |
|
86 | 88 | get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status' |
|
87 | 89 | end |
|
88 | 90 | end |
|
89 | 91 | |
|
90 | 92 | |
|
91 | 93 | #user admin |
|
92 | 94 | resources :user_admin do |
|
93 | 95 | collection do |
|
94 | 96 | match 'bulk_manage', via: [:get, :post] |
|
97 | + get 'bulk_mail' | |
|
95 | 98 | get 'user_stat' |
|
96 | 99 | get 'import' |
|
97 | 100 | get 'new_list' |
|
98 | 101 | get 'admin' |
|
99 | 102 | get 'random_all_passwords' |
|
100 | 103 | get 'active' |
|
101 | 104 | get 'mass_mailing' |
|
102 | 105 | match 'create_from_list', via: [:get, :post] |
|
106 | + post 'grant_admin' | |
|
103 | 107 | end |
|
104 | 108 | member do |
|
105 | 109 | get 'clear_last_ip' |
|
106 | 110 | end |
|
107 | 111 | end |
|
108 | 112 | |
|
109 | 113 | resources :contest_management, only: [:index] do |
|
110 | 114 | collection do |
|
111 | 115 | get 'user_stat' |
|
112 | 116 | get 'clear_stat' |
|
113 | 117 | get 'clear_all_stat' |
|
114 | 118 | end |
|
115 | 119 | end |
|
116 | 120 | |
|
117 | 121 | #get 'user_admin', to: 'user_admin#index' |
|
118 | 122 | #get 'user_admin/bulk_manage', to: 'user_admin#bulk_manage', as: 'bulk_manage_user_admin' |
|
119 | 123 | #post 'user_admin', to: 'user_admin#create' |
|
120 | 124 | #delete 'user_admin/:id', to: 'user_admin#destroy', as: 'user_admin_destroy' |
|
121 | 125 | |
|
122 | 126 | #singular resource |
|
123 | 127 | #---- BEWARE ---- singular resource maps to plural controller by default, we can override by provide controller name directly |
|
124 | 128 | #report |
|
125 | 129 | resource :report, only: [], controller: 'report' do |
|
126 | 130 | get 'login' |
|
127 | 131 | get 'multiple_login' |
|
128 | 132 | get 'problem_hof/:id', action: 'problem_hof' |
|
129 | 133 | get 'current_score' |
|
130 | 134 | get 'max_score' |
|
131 | 135 | post 'show_max_score' |
|
132 | 136 | end |
|
133 | 137 | #get 'report/current_score', to: 'report#current_score', as: 'report_current_score' |
|
134 | 138 | #get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof' |
|
135 | 139 | #get "report/login" |
|
136 | 140 | #get 'report/max_score', to: 'report#max_score', as: 'report_max_score' |
|
137 | 141 | #post 'report/show_max_score', to: 'report#show_max_score', as: 'report_show_max_score' |
|
138 | 142 | |
|
139 | 143 | resource :main, only: [], controller: 'main' do |
|
140 | 144 | get 'list' |
|
141 | 145 | get 'submission(/:id)', action: 'submission', as: 'main_submission' |
|
142 | 146 | post 'submit' |
|
143 | 147 | get 'announcements' |
|
144 | 148 | get 'help' |
|
145 | 149 | end |
|
146 | 150 | #main |
|
147 | 151 | #get "main/list" |
|
148 | 152 | #get 'main/submission(/:id)', to: 'main#submission', as: 'main_submission' |
|
149 | 153 | #post 'main/submit', to: 'main#submit' |
|
150 | 154 | #get 'main/announcements', to: 'main#announcements' |
|
151 | 155 | |
|
152 | 156 | |
|
153 | 157 | # |
|
154 | 158 | get 'tasks/view/:file.:ext' => 'tasks#view' |
|
155 | 159 | get 'tasks/download/:id/:file.:ext' => 'tasks#download' |
|
156 | 160 | get 'heartbeat/:id/edit' => 'heartbeat#edit' |
|
157 | 161 | |
|
158 | 162 | #grader |
|
159 | 163 | get 'graders/list', to: 'graders#list', as: 'grader_list' |
|
160 | 164 | |
|
161 | 165 | |
|
162 | 166 | # See how all your routes lay out with "rake routes" |
|
163 | 167 | |
|
164 | 168 | # This is a legacy wild controller route that's not recommended for RESTful applications. |
|
165 | 169 | # Note: This route will make all actions in every controller accessible via GET requests. |
|
166 | 170 | # match ':controller(/:action(/:id))(.:format)', via: [:get, :post] |
|
167 | 171 | end |
@@ -1,27 +1,28 | |||
|
1 |
- ENV[ |
|
|
2 |
- require |
|
|
1 | + ENV['RAILS_ENV'] ||= 'test' | |
|
2 | + require_relative '../config/environment' | |
|
3 | 3 | require 'rails/test_help' |
|
4 | 4 | |
|
5 | 5 | #reporter for beautiful result |
|
6 | 6 | require "minitest/reporters" |
|
7 | 7 | Minitest::Reporters.use! |
|
8 | 8 | |
|
9 | 9 | module SignInHelper |
|
10 | 10 | def sign_in_as(user,password) |
|
11 | 11 | post login_login_path, {login: user, password: password } |
|
12 | 12 | end |
|
13 | 13 | end |
|
14 | 14 | |
|
15 | 15 | class ActiveSupport::TestCase |
|
16 | 16 | include SignInHelper |
|
17 | 17 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. |
|
18 | 18 | # |
|
19 | 19 | # Note: You'll currently still have to declare fixtures explicitly in integration tests |
|
20 | 20 | # -- they do not yet inherit this setting |
|
21 | 21 | fixtures :all |
|
22 | 22 | |
|
23 | 23 | # Add more helper methods to be used by all tests here... |
|
24 | 24 | |
|
25 |
- self.use_transactional_ |
|
|
25 | + self.use_transactional_tests = true | |
|
26 | + #self.use_instantiated_fixtures = false | |
|
26 | 27 | self.use_instantiated_fixtures = false |
|
27 | 28 | end |
deleted file |
deleted file |
You need to be logged in to leave comments.
Login now