QR codes generation
This commit is contained in:
1
Gemfile
1
Gemfile
@@ -46,6 +46,7 @@ gem "bootstrap", "~> 5.3.3"
|
|||||||
gem "friendly_id", "~> 5.5.0"
|
gem "friendly_id", "~> 5.5.0"
|
||||||
gem "csv"
|
gem "csv"
|
||||||
gem "damerau-levenshtein"
|
gem "damerau-levenshtein"
|
||||||
|
gem "rqrcode", "~> 3.0"
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
|
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ GEM
|
|||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
regexp_parser (>= 1.5, < 3.0)
|
regexp_parser (>= 1.5, < 3.0)
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
|
chunky_png (1.4.0)
|
||||||
concurrent-ruby (1.3.5)
|
concurrent-ruby (1.3.5)
|
||||||
connection_pool (2.5.4)
|
connection_pool (2.5.4)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
@@ -291,6 +292,10 @@ GEM
|
|||||||
reline (0.6.2)
|
reline (0.6.2)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
rexml (3.4.4)
|
rexml (3.4.4)
|
||||||
|
rqrcode (3.1.0)
|
||||||
|
chunky_png (~> 1.0)
|
||||||
|
rqrcode_core (~> 2.0)
|
||||||
|
rqrcode_core (2.0.0)
|
||||||
rspec-core (3.13.6)
|
rspec-core (3.13.6)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-expectations (3.13.5)
|
rspec-expectations (3.13.5)
|
||||||
@@ -455,6 +460,7 @@ DEPENDENCIES
|
|||||||
puma (>= 5.0)
|
puma (>= 5.0)
|
||||||
pundit (~> 2.5)
|
pundit (~> 2.5)
|
||||||
rails (~> 8.0.2)
|
rails (~> 8.0.2)
|
||||||
|
rqrcode (~> 3.0)
|
||||||
rspec-rails
|
rspec-rails
|
||||||
rubocop-rails-omakase
|
rubocop-rails-omakase
|
||||||
selenium-webdriver
|
selenium-webdriver
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ class ContestantsController < ApplicationController
|
|||||||
include CompletionsConcern
|
include CompletionsConcern
|
||||||
include ContestantsConcern
|
include ContestantsConcern
|
||||||
|
|
||||||
before_action :set_contest, only: %i[ index edit new create update destroy import upload_csv convert_csv finalize_import export ]
|
before_action :set_contest, only: %i[ index edit new create update destroy import upload_csv convert_csv finalize_import export generate_qrcodes ]
|
||||||
before_action :set_contestant, only: %i[ destroy edit update]
|
before_action :set_contestant, only: %i[ destroy edit update]
|
||||||
before_action :set_completions, only: %i[edit update ]
|
before_action :set_completions, only: %i[edit update ]
|
||||||
skip_before_action :require_authentication, only: %i[ get_public_completion post_public_completion public_completion_updated ]
|
skip_before_action :require_authentication, only: %i[ get_public_completion post_public_completion public_completion_updated ]
|
||||||
@@ -42,7 +42,7 @@ class ContestantsController < ApplicationController
|
|||||||
|
|
||||||
if @contestant.update(contestant_params)
|
if @contestant.update(contestant_params)
|
||||||
update_contestant_categories
|
update_contestant_categories
|
||||||
redirect_to @contest, notice: t("contestants.edit.notice")
|
redirect_to contest_contestants_path(@contest), notice: t("contestants.edit.notice")
|
||||||
else
|
else
|
||||||
render :edit, status: :unprocessable_entity
|
render :edit, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
@@ -120,6 +120,12 @@ class ContestantsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def generate_qrcodes
|
||||||
|
authorize @contest
|
||||||
|
|
||||||
|
@contestants = @contest.contestants.sort_by { |contestant| contestant.name }
|
||||||
|
end
|
||||||
|
|
||||||
def get_public_completion
|
def get_public_completion
|
||||||
skip_authorization
|
skip_authorization
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
# email :string
|
# email :string
|
||||||
# name :string
|
# name :string
|
||||||
# projected_time :string
|
# projected_time :string
|
||||||
|
# qrcode :string
|
||||||
# time_seconds :integer
|
# time_seconds :integer
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
@@ -27,6 +28,7 @@ class Contestant < ApplicationRecord
|
|||||||
has_and_belongs_to_many :categories
|
has_and_belongs_to_many :categories
|
||||||
|
|
||||||
before_validation :initialize_time_seconds_if_empty
|
before_validation :initialize_time_seconds_if_empty
|
||||||
|
before_commit :generate_qrcode, on: :create
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
validates :time_seconds, presence: true
|
validates :time_seconds, presence: true
|
||||||
@@ -48,4 +50,17 @@ class Contestant < ApplicationRecord
|
|||||||
self.time_seconds = 0
|
self.time_seconds = 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def generate_qrcode
|
||||||
|
host = Rails.application.config.action_controller.default_url_options[:host]
|
||||||
|
qrcode = RQRCode::QRCode.new("https://#{host}/public/p/#{self.generate_token_for(:token)}")
|
||||||
|
self.qrcode = qrcode.as_svg(
|
||||||
|
color: "000",
|
||||||
|
shape_rendering: "crispEdges",
|
||||||
|
module_size: 3,
|
||||||
|
standalone: true,
|
||||||
|
use_path: true,
|
||||||
|
viewbox: true
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class Offline < ApplicationRecord
|
|||||||
belongs_to :contestant, optional: true
|
belongs_to :contestant, optional: true
|
||||||
belongs_to :completion, optional: true
|
belongs_to :completion, optional: true
|
||||||
|
|
||||||
has_many_attached :images
|
has_many_attached :images, dependent: :destroy
|
||||||
|
|
||||||
generates_token_for :token
|
generates_token_for :token
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class Puzzle < ApplicationRecord
|
|||||||
belongs_to :contest
|
belongs_to :contest
|
||||||
|
|
||||||
has_many :completions, dependent: :destroy
|
has_many :completions, dependent: :destroy
|
||||||
has_one_attached :image
|
has_one_attached :image, dependent: :destroy
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
validates :pieces, presence: true
|
validates :pieces, presence: true
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ class ContestPolicy < ApplicationPolicy
|
|||||||
owner_or_admin
|
owner_or_admin
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def generate_qrcodes?
|
||||||
|
owner_or_admin
|
||||||
|
end
|
||||||
|
|
||||||
def settings_general_edit?
|
def settings_general_edit?
|
||||||
edit?
|
edit?
|
||||||
end
|
end
|
||||||
|
|||||||
11
app/views/contestants/generate_qrcodes.html.slim
Normal file
11
app/views/contestants/generate_qrcodes.html.slim
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.row.mb-4 style="height: calc(100vh - 280px)"
|
||||||
|
.col.d-flex.flex-column style="height: 100%"
|
||||||
|
.d-flex.flex-column style="overflow-y: auto"
|
||||||
|
- for row in 0..((@contestants.length - 1) / 4)
|
||||||
|
.mt-4.d-flex.flex-row
|
||||||
|
- for col in 0..3
|
||||||
|
- if row * 4 + col < @contestants.length
|
||||||
|
.d-flex.flex-column.ms-5 style="align-items: center"
|
||||||
|
= @contestants[row * 4 + col].name
|
||||||
|
.mt-1 style="width: 200px; height: 200px;"
|
||||||
|
= @contestants[row * 4 + col].qrcode.html_safe
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
| + #{t("helpers.buttons.add")}
|
| + #{t("helpers.buttons.add")}
|
||||||
a.ms-2.btn.btn.btn-primary href=contest_import_path(@contest) style="margin-top: -3px"
|
a.ms-2.btn.btn.btn-primary href=contest_import_path(@contest) style="margin-top: -3px"
|
||||||
| #{t("helpers.buttons.import")}
|
| #{t("helpers.buttons.import")}
|
||||||
|
a.ms-2.btn.btn.btn-primary href=contest_generate_qrcodes_path(@contest) style="margin-top: -3px"
|
||||||
|
| #{t("helpers.buttons.generate_qrcodes")}
|
||||||
a.ms-2.btn.btn.btn-primary href="/contests/#{@contest.id}/export.csv" style="margin-top: -3px"
|
a.ms-2.btn.btn.btn-primary href="/contests/#{@contest.id}/export.csv" style="margin-top: -3px"
|
||||||
| #{t("helpers.buttons.export")}
|
| #{t("helpers.buttons.export")}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ Rails.application.configure do
|
|||||||
# Set localhost to be used by links generated in mailer templates.
|
# Set localhost to be used by links generated in mailer templates.
|
||||||
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
|
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
|
||||||
|
|
||||||
|
config.action_controller.default_url_options = { host: "localhost" }
|
||||||
|
|
||||||
# Print deprecation notices to the Rails logger.
|
# Print deprecation notices to the Rails logger.
|
||||||
config.active_support.deprecation = :log
|
config.active_support.deprecation = :log
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ Rails.application.configure do
|
|||||||
# Set host to be used by links generated in mailer templates.
|
# Set host to be used by links generated in mailer templates.
|
||||||
config.action_mailer.default_url_options = { host: "example.com" }
|
config.action_mailer.default_url_options = { host: "example.com" }
|
||||||
|
|
||||||
|
config.action_controller.default_url_options = { host: "public-scoreboard.org" }
|
||||||
|
|
||||||
# Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit.
|
# Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit.
|
||||||
# config.action_mailer.smtp_settings = {
|
# config.action_mailer.smtp_settings = {
|
||||||
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
|
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
|
||||||
|
|||||||
@@ -258,6 +258,7 @@ en:
|
|||||||
edit: "Edit"
|
edit: "Edit"
|
||||||
end: Click here to submit your completion
|
end: Click here to submit your completion
|
||||||
export: Export
|
export: Export
|
||||||
|
generate_qrcodes: Generate QR codes
|
||||||
import: CSV Import
|
import: CSV Import
|
||||||
open: Open
|
open: Open
|
||||||
refresh: Refresh
|
refresh: Refresh
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ fr:
|
|||||||
edit: "Modifier"
|
edit: "Modifier"
|
||||||
end: Clique ici pour valider ta complétion du puzzle
|
end: Clique ici pour valider ta complétion du puzzle
|
||||||
export: Exporter
|
export: Exporter
|
||||||
|
generate_qrcodes: Générer des QR codes
|
||||||
import: Importer un CSV
|
import: Importer un CSV
|
||||||
open: Détails
|
open: Détails
|
||||||
refresh: Rafraîchir
|
refresh: Rafraîchir
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ Rails.application.routes.draw do
|
|||||||
get "import/:id", to: "contestants#convert_csv"
|
get "import/:id", to: "contestants#convert_csv"
|
||||||
post "import/:id", to: "contestants#finalize_import"
|
post "import/:id", to: "contestants#finalize_import"
|
||||||
get "export", to: "contestants#export"
|
get "export", to: "contestants#export"
|
||||||
|
get "generate_qrcodes", to: "contestants#generate_qrcodes"
|
||||||
end
|
end
|
||||||
resources :passwords, param: :token
|
resources :passwords, param: :token
|
||||||
resource :session
|
resource :session
|
||||||
|
|||||||
5
db/migrate/20251120100813_add_qrcode_to_contestant.rb
Normal file
5
db/migrate/20251120100813_add_qrcode_to_contestant.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class AddQrcodeToContestant < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
add_column :contestants, :qrcode, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
3
db/schema.rb
generated
3
db/schema.rb
generated
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2025_11_18_074914) do
|
ActiveRecord::Schema[8.0].define(version: 2025_11_20_100813) do
|
||||||
create_table "active_storage_attachments", force: :cascade do |t|
|
create_table "active_storage_attachments", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "record_type", null: false
|
t.string "record_type", null: false
|
||||||
@@ -83,6 +83,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_11_18_074914) do
|
|||||||
t.string "display_time"
|
t.string "display_time"
|
||||||
t.integer "time_seconds"
|
t.integer "time_seconds"
|
||||||
t.string "projected_time"
|
t.string "projected_time"
|
||||||
|
t.string "qrcode"
|
||||||
t.index ["contest_id"], name: "index_contestants_on_contest_id"
|
t.index ["contest_id"], name: "index_contestants_on_contest_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user