Merge pull request #189 from foodcoops/allow-no-nickname

Make the use of a username optional
This commit is contained in:
wvengen 2013-11-18 03:00:27 -08:00
commit c4530bca6f
53 changed files with 194 additions and 94 deletions

View file

@ -2,13 +2,10 @@ class Admin::UsersController < Admin::BaseController
inherit_resources inherit_resources
def index def index
@users = User.order('nick ASC') @users = User.natural_order
# if somebody uses the search field: # if somebody uses the search field:
unless params[:user_name].blank? @users = @users.natural_search(params[:user_name]) unless params[:user_name].blank?
@users = @users.where("first_name LIKE :user_name OR last_name LIKE :user_name OR nick LIKE :user_name",
user_name: "%#{params[:user_name]}%")
end
@users = @users.page(params[:page]).per(@per_page) @users = @users.page(params[:page]).per(@per_page)
end end

View file

@ -1,19 +1,16 @@
class Foodcoop::UsersController < ApplicationController class Foodcoop::UsersController < ApplicationController
def index def index
@users = User.order('nick ASC') @users = User.natural_order
# if somebody uses the search field: # if somebody uses the search field:
unless params[:user_name].blank? @users = @users.natural_search(params[:user_name]) unless params[:user_name].blank?
@users = @users.where("first_name LIKE :user_name OR last_name LIKE :user_name OR nick LIKE :user_name",
user_name: "%#{params[:user_name]}%")
end
if params[:ordergroup_name] if params[:ordergroup_name]
@users = @users.joins(:groups).where("groups.type = 'Ordergroup' AND groups.name LIKE ?", "%#{params[:ordergroup_name]}%") @users = @users.joins(:groups).where("groups.type = 'Ordergroup' AND groups.name LIKE ?", "%#{params[:ordergroup_name]}%")
end end
@users = @users.page(params[:page]).per(@per_page).order('users.nick ASC') @users = @users.page(params[:page]).per(@per_page)
respond_to do |format| respond_to do |format|
format.html # index.html.haml format.html # index.html.haml

View file

@ -2,9 +2,9 @@ class UsersController < ApplicationController
# Currently used to display users nick and ids for autocomplete # Currently used to display users nick and ids for autocomplete
def index def index
@users = User.where("nick LIKE ?", "%#{params[:q]}%") @users = User.natural_search(params[:q])
respond_to do |format| respond_to do |format|
format.json { render :json => @users.map { |u| u.token_attributes } } format.json { render :json => @users.map(&:token_attributes) }
end end
end end

View file

@ -155,13 +155,6 @@ module ApplicationHelper
:target => "_blank" :target => "_blank"
end end
# offers a link for writing message to user
# checks for nil (useful for relations)
def link_to_user_message_if_valid(user)
user.nil? ? '??' : link_to(user.nick, new_message_path('message[mail_to]' => user.id),
:title => I18n.t('helpers.application.write_message'))
end
def bootstrap_flash def bootstrap_flash
flash_messages = [] flash_messages = []
flash.each do |type, message| flash.each do |type, message|
@ -183,4 +176,32 @@ module ApplicationHelper
render :partial => 'shared/base_errors', :locals => {:error_messages => messages} render :partial => 'shared/base_errors', :locals => {:error_messages => messages}
end end
# show a user, depending on settings
def show_user(user=@current_user, options = {})
if user.nil?
"?"
elsif FoodsoftConfig[:use_nick]
if options[:full] and options[:markup]
raw "<b>#{h user.nick}</b> (#{h user.first_name} #{h user.last_name})"
elsif options[:full]
"#{user.nick} (#{user.first_name} #{user.last_name})"
else
# when use_nick was changed from false to true, users may exist without nick
user.nick.nil? ? I18n.t('helpers.application.nick_fallback') : user.nick
end
else
"#{user.first_name} #{user.last_name}" + (options[:unique] ? " (\##{user.id})" : '')
end
end
# render user presentation linking to default action (write message)
def show_user_link(user=@current_user)
if user.nil?
show_user user
else
link_to show_user(user), new_message_path('message[mail_to]' => user.id),
:title => I18n.t('helpers.application.write_message')
end
end
end end

View file

@ -2,7 +2,7 @@ module TasksHelper
def task_assignments(task) def task_assignments(task)
task.assignments.map do |ass| task.assignments.map do |ass|
content_tag :span, ass.user.nick, :class => (ass.accepted? ? 'accepted' : 'unaccepted') content_tag :span, show_user(ass.user), :class => (ass.accepted? ? 'accepted' : 'unaccepted')
end.join(", ").html_safe end.join(", ").html_safe
end end

View file

@ -1,6 +1,11 @@
# encoding: utf-8 # encoding: utf-8
# ActionMailer class that handles all emails for the FoodSoft. # ActionMailer class that handles all emails for the FoodSoft.
class Mailer < ActionMailer::Base class Mailer < ActionMailer::Base
# XXX Quick fix to allow the use of show_user. Proper take would be one of
# (1) Use draper, decorate user
# (2) Create a helper with this method, include here and in ApplicationHelper
helper :application
include ApplicationHelper
layout 'email' # Use views/layouts/email.txt.erb layout 'email' # Use views/layouts/email.txt.erb
@ -15,7 +20,7 @@ class Mailer < ActionMailer::Base
mail subject: "[#{FoodsoftConfig[:name]}] " + message.subject, mail subject: "[#{FoodsoftConfig[:name]}] " + message.subject,
to: recipient.email, to: recipient.email,
from: "#{message.sender.nick} <#{message.sender.email}>" from: "#{show_user(message.sender)} <#{message.sender.email}>"
end end
# Sends an email with instructions on how to reset the password. # Sends an email with instructions on how to reset the password.
@ -26,7 +31,7 @@ class Mailer < ActionMailer::Base
@link = new_password_url(id: @user.id, token: @user.reset_password_token) @link = new_password_url(id: @user.id, token: @user.reset_password_token)
mail :to => @user.email, mail :to => @user.email,
:subject => "[#{FoodsoftConfig[:name]}] " + I18n.t('mailer.reset_password.subject', :username => @user.nick) :subject => "[#{FoodsoftConfig[:name]}] " + I18n.t('mailer.reset_password.subject', :username => show_user(@user))
end end
# Sends an invite email. # Sends an invite email.
@ -75,7 +80,7 @@ class Mailer < ActionMailer::Base
@feedback = feedback @feedback = feedback
mail :to => FoodsoftConfig[:notification]["error_recipients"], mail :to => FoodsoftConfig[:notification]["error_recipients"],
:from => "#{user.nick} <#{user.email}>", :from => "#{show_user user} <#{user.email}>",
:sender => FoodsoftConfig[:notification]["sender_address"], :sender => FoodsoftConfig[:notification]["sender_address"],
:errors_to => FoodsoftConfig[:notification]["sender_address"], :errors_to => FoodsoftConfig[:notification]["sender_address"],
:subject => "[#{FoodsoftConfig[:name]}] " + I18n.t('mailer.feedback.subject', :email => user.email) :subject => "[#{FoodsoftConfig[:name]}] " + I18n.t('mailer.feedback.subject', :email => user.email)

View file

@ -17,7 +17,7 @@ class Group < ActiveRecord::Base
# Returns all NONmembers and a checks for possible multiple Ordergroup-Memberships # Returns all NONmembers and a checks for possible multiple Ordergroup-Memberships
def non_members def non_members
User.all(:order => 'nick').reject { |u| users.include?(u) } User.natural_order.all.reject { |u| users.include?(u) }
end end
def user_tokens=(ids) def user_tokens=(ids)

View file

@ -49,7 +49,7 @@ class Message < ActiveRecord::Base
@reply_to = Message.find(message_id) @reply_to = Message.find(message_id)
add_recipients([@reply_to.sender]) add_recipients([@reply_to.sender])
self.subject = I18n.t('messages.model.reply_subject', :subject => @reply_to.subject) self.subject = I18n.t('messages.model.reply_subject', :subject => @reply_to.subject)
self.body = I18n.t('messages.model.reply_header', :user => @reply_to.sender.nick, :when => I18n.l(@reply_to.created_at, :format => :short)) + "\n" self.body = I18n.t('messages.model.reply_header', :user => @reply_to.sender.display, :when => I18n.l(@reply_to.created_at, :format => :short)) + "\n"
@reply_to.body.each_line{ |l| self.body += I18n.t('messages.model.reply_indent', :line => l) } @reply_to.body.each_line{ |l| self.body += I18n.t('messages.model.reply_indent', :line => l) }
end end
@ -64,7 +64,7 @@ class Message < ActiveRecord::Base
end end
def sender_name def sender_name
system_message? ? I18n.t('layouts.foodsoft') : sender.nick rescue "??" system_message? ? I18n.t('layouts.foodsoft') : sender.display rescue "?"
end end
def recipients def recipients
@ -77,7 +77,7 @@ class Message < ActiveRecord::Base
begin begin
Mailer.foodsoft_message(self, user).deliver Mailer.foodsoft_message(self, user).deliver
rescue rescue
Rails.logger.warn "Deliver failed for #{user.nick}: #{user.email}" Rails.logger.warn "Deliver failed for user \##{user.id}: #{user.email}"
end end
end end
end end

View file

@ -23,7 +23,7 @@ class Ordergroup < Group
"#{contact_phone} (#{contact_person})" "#{contact_phone} (#{contact_person})"
end end
def non_members def non_members
User.all(:order => 'nick').reject { |u| (users.include?(u) || u.ordergroup) } User.natural_order.all.reject { |u| (users.include?(u) || u.ordergroup) }
end end
def value_of_open_orders(exclude = nil) def value_of_open_orders(exclude = nil)
@ -102,7 +102,7 @@ class Ordergroup < Group
# Make sure, that a user can only be in one ordergroup # Make sure, that a user can only be in one ordergroup
def uniqueness_of_members def uniqueness_of_members
users.each do |user| users.each do |user|
errors.add :user_tokens, I18n.t('ordergroups.model.error_single_group', :user => user.nick) if user.groups.where(:type => 'Ordergroup').size > 1 errors.add :user_tokens, I18n.t('ordergroups.model.error_single_group', :user => user.display) if user.groups.where(:type => 'Ordergroup').size > 1
end end
end end

View file

@ -24,15 +24,19 @@ class User < ActiveRecord::Base
# makes the current_user (logged-in-user) available in models # makes the current_user (logged-in-user) available in models
cattr_accessor :current_user cattr_accessor :current_user
validates_presence_of :nick, :email validates_presence_of :email
validates_presence_of :password, :on => :create validates_presence_of :password, :on => :create
validates_length_of :nick, :in => 2..25
validates_uniqueness_of :nick, :case_sensitive => false
validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
validates_uniqueness_of :email, :case_sensitive => false validates_uniqueness_of :email, :case_sensitive => false
validates_length_of :first_name, :in => 2..50 validates_length_of :first_name, :in => 2..50
validates_confirmation_of :password validates_confirmation_of :password
validates_length_of :password, :in => 5..25, :allow_blank => true validates_length_of :password, :in => 5..25, :allow_blank => true
# allow nick to be nil depending on foodcoop config
# TODO Rails 4 may have a more beautiful way
# http://stackoverflow.com/questions/19845910/conditional-allow-nil-part-of-validation
validates_length_of :nick, :in => 2..25, :allow_nil => true, :unless => Proc.new { FoodsoftConfig[:use_nick] }
validates_length_of :nick, :in => 2..25, :allow_nil => false, :if => Proc.new { FoodsoftConfig[:use_nick] }
validates_uniqueness_of :nick, :case_sensitive => false, :allow_nil => true # allow_nil in length validation
before_validation :set_password before_validation :set_password
after_initialize do after_initialize do
@ -56,6 +60,22 @@ class User < ActiveRecord::Base
end end
end end
# sorted by display name
def self.natural_order
# would be sensible to match ApplicationController#show_user
if FoodsoftConfig[:use_nick]
order('nick ASC')
else
order('first_name ASC, last_name ASC')
end
end
# search by (nick)name
def self.natural_search(q)
# we always use both nick and name, to make sure a user is found
where("CONCAT(first_name, CONCAT(' ', last_name)) LIKE :q OR nick LIKE :q", q: "%#{q}%")
end
def locale def locale
settings.profile['language'] settings.profile['language']
end end
@ -132,8 +152,8 @@ class User < ActiveRecord::Base
self.groups.find(:all, :conditions => {:type => ""}) self.groups.find(:all, :conditions => {:type => ""})
end end
def self.authenticate(nick, password) def self.authenticate(login, password)
user = find_by_nick(nick) user = (find_by_nick(login) or find_by_email(login))
if user && user.has_password(password) if user && user.has_password(password)
user user
else else
@ -141,8 +161,21 @@ class User < ActiveRecord::Base
end end
end end
# XXX this is view-related; need to move out things like token_attributes
# then this can be removed
def display
# would be sensible to match ApplicationHelper#show_user
if FoodsoftConfig[:use_nick]
nick.nil? ? I18n.t('helpers.application.nick_fallback') : nick
else
name
end
end
def token_attributes def token_attributes
{:id => id, :name => "#{nick} (#{ordergroup.try(:name)})"} # would be sensible to match ApplicationController#show_user
# this should not be part of the model anyway
{:id => id, :name => "#{display} (#{ordergroup.try(:name)})"}
end end
end end

View file

@ -9,13 +9,15 @@
%table.table.table-striped %table.table.table-striped
%thead %thead
%tr %tr
- if FoodsoftConfig[:use_nick]
%th= t '.username' %th= t '.username'
%th= t '.name' %th= t '.name'
%th= t '.created_at' %th= t '.created_at'
- for user in @users - for user in @users
%tr{:class => cycle('even','odd', :name => 'users')} %tr{:class => cycle('even','odd', :name => 'users')}
%td= link_to user.nick, [:admin, user] %td= link_to show_user(user), [:admin, user]
%td=h user.name - if FoodsoftConfig[:use_nick]
%td= link_to user.name
%td= format_date(user.created_on) %td= format_date(user.created_on)
= link_to t('.all_users'), admin_users_path = link_to t('.all_users'), admin_users_path
| |

View file

@ -4,6 +4,7 @@
%table.table.table-striped %table.table.table-striped
%thead %thead
%tr %tr
- if FoodsoftConfig[:use_nick]
%th= t '.login' %th= t '.login'
%th= t '.name' %th= t '.name'
%th= t '.email' %th= t '.email'
@ -13,7 +14,8 @@
%tbody %tbody
- for user in @users - for user in @users
%tr %tr
%td= link_to user.nick, [:admin, user] %td= link_to show_user(user), [:admin, user]
- if FoodsoftConfig[:use_nick]
%td= user.name %td= user.name
%td= user.email %td= user.email
%td= format_roles(user) %td= format_roles(user)

View file

@ -1,4 +1,4 @@
- title @user.nick - title show_user(@user)
.row-fluid .row-fluid
.span3 .span3
@ -6,6 +6,7 @@
%h4= t '.person' %h4= t '.person'
%p= t '.member_since', time: distance_of_time_in_words(Time.now, @user.created_on) %p= t '.member_since', time: distance_of_time_in_words(Time.now, @user.created_on)
%dl %dl
- if FoodsoftConfig[:use_nick]
%dt= t '.nick' %dt= t '.nick'
%dd= @user.nick %dd= @user.nick
%dt= t '.name' %dt= t '.name'

View file

@ -16,7 +16,7 @@
%td= link_to truncate(order.name), new_finance_order_path(order_id: order.id) %td= link_to truncate(order.name), new_finance_order_path(order_id: order.id)
%td=h format_time(order.ends) unless order.ends.nil? %td=h format_time(order.ends) unless order.ends.nil?
%td= order.closed? ? t('.cleared', amount: number_to_currency(order.foodcoop_result)) : t('.ended') %td= order.closed? ? t('.cleared', amount: number_to_currency(order.foodcoop_result)) : t('.ended')
%td= order.updated_by.nil? ? '??' : order.updated_by.nick %td= show_user(order.updated_by)
%td %td
- unless order.closed? - unless order.closed?
= link_to t('.clear'), new_finance_order_path(order_id: order.id), class: 'btn btn-mini btn-primary' = link_to t('.clear'), new_finance_order_path(order_id: order.id), class: 'btn btn-mini btn-primary'

View file

@ -12,6 +12,6 @@
- @financial_transactions.each do |t| - @financial_transactions.each do |t|
%tr %tr
%td= format_time(t.created_on) %td= format_time(t.created_on)
%td= h t.user.nil? ? '??' : t.user.nick %td= h show_user(t.user)
%td= h t.note %td= h t.note
%td.currency{:style => "color:#{t.amount < 0 ? 'red' : 'black'}; width:5em"}= number_to_currency(t.amount) %td.currency{:style => "color:#{t.amount < 0 ? 'red' : 'black'}; width:5em"}= number_to_currency(t.amount)

View file

@ -13,7 +13,7 @@
- for ordergroup in @ordergroups - for ordergroup in @ordergroups
%tr %tr
%td= ordergroup.name %td= ordergroup.name
%td=h ordergroup.users.collect { |u| u.nick }.join(", ") %td=h ordergroup.users.collect { |u| show_user(u) }.join(", ")
%td= format_date ordergroup.orders.order('orders.starts DESC').first.try(:starts) %td= format_date ordergroup.orders.order('orders.starts DESC').first.try(:starts)
%td= link_to_new_message(message_params: {group_id: ordergroup.id}) %td= link_to_new_message(message_params: {group_id: ordergroup.id})

View file

@ -4,6 +4,7 @@
%table.table.table-striped %table.table.table-striped
%thead %thead
%tr %tr
- if FoodsoftConfig[:use_nick]
%th= heading_helper User, :nick %th= heading_helper User, :nick
%th= heading_helper User, :name %th= heading_helper User, :name
%th= heading_helper User, :email %th= heading_helper User, :email
@ -13,8 +14,9 @@
%tbody %tbody
- for user in @users - for user in @users
%tr %tr
%td= user.nick - if FoodsoftConfig[:use_nick]
%td= user.name if @current_user.role_admin? || user.settings.profile["name_is_public"] %td= show_user user
%td= user.name if @current_user.role_admin? || user.settings.profile["name_is_public"] || !FoodsoftConfig[:use_nick]
%td= user.email if @current_user.role_admin? || user.settings.profile["email_is_public"] %td= user.email if @current_user.role_admin? || user.settings.profile["email_is_public"]
%td= user.phone if @current_user.role_admin? || user.settings.profile["phone_is_public"] %td= user.phone if @current_user.role_admin? || user.settings.profile["phone_is_public"]
%td= user.ordergroup_name %td= user.ordergroup_name

View file

@ -30,7 +30,7 @@
%dt= t '.note' %dt= t '.note'
%dd= @order.note %dd= @order.note
%dt= t '.created_by' %dt= t '.created_by'
%dd= link_to_user_message_if_valid(@order.created_by) %dd= show_user_link(@order.created_by)
%dt= t '.ending' %dt= t '.ending'
%dd= format_time(@order.ends) %dd= format_time(@order.ends)
- unless @order.stockit? or @order.supplier.min_order_quantity.blank? - unless @order.stockit? or @order.supplier.min_order_quantity.blank?
@ -40,7 +40,7 @@
%dd= number_to_currency @order.sum %dd= number_to_currency @order.sum
%dt= t '.last_update' %dt= t '.last_update'
%dd %dd
= @group_order.updated_by.nick if @group_order.updated_by = show_user(@group_order.updated_by) if @group_order.updated_by
(#{format_time(@group_order.updated_on)}) (#{format_time(@group_order.updated_on)})
%dt= t '.funds' %dt= t '.funds'
%dd= number_to_currency(@ordering_data[:available_funds]) %dd= number_to_currency(@ordering_data[:available_funds])

View file

@ -22,7 +22,7 @@
- else - else
= t '.not_ordered' = t '.not_ordered'
- if @order.closed? - if @order.closed?
%p= t '.closed_by', user: @order.updated_by.nick %p= t '.closed_by', user: show_user(@order.updated_by)
= link_to t('.comment'), "#comments" = link_to t('.comment'), "#comments"
// Article box // Article box

View file

@ -57,7 +57,7 @@
- for ft in current_user.ordergroup.financial_transactions.limit(5).order('created_on DESC') - for ft in current_user.ordergroup.financial_transactions.limit(5).order('created_on DESC')
%tr %tr
%td= format_time(ft.created_on) %td= format_time(ft.created_on)
%td= h(ft.user.nil? ? '?' : ft.user.nick) %td= h(show_user(ft.user))
%td= h(ft.note) %td= h(ft.note)
- color = ft.amount < 0 ? 'red' : 'black' - color = ft.amount < 0 ? 'red' : 'black'
%td{:style => "color:#{color}; width:5em", :class => "currency"}= number_to_currency(ft.amount) %td{:style => "color:#{color}; width:5em", :class => "currency"}= number_to_currency(ft.amount)

View file

@ -11,7 +11,7 @@
= number_to_currency(@ordergroup.get_available_funds()) = number_to_currency(@ordergroup.get_available_funds())
%p %p
%b= Ordergroup.human_attribute_name(:user_tokens) + ':' %b= Ordergroup.human_attribute_name(:user_tokens) + ':'
= @ordergroup.memberships.map{|m| m.user.nick}.join(', ') = @ordergroup.memberships.map{|m| show_user m.user}.join(', ')
= link_to t('.invite'), new_invite_path(:id => @ordergroup), :remote => true, class: 'btn btn-primary' = link_to t('.invite'), new_invite_path(:id => @ordergroup), :remote => true, class: 'btn btn-primary'
.span8 .span8
%h2= t('.account_summary') %h2= t('.account_summary')

View file

@ -3,7 +3,7 @@
.row-fluid .row-fluid
.span7 .span7
%h3 %h3
= h(t('.user.title', user: @current_user.nick)) = h(t('.user.title', user: show_user))
%small= t '.user.since', when: distance_of_time_in_words(Time.now, @current_user.created_on) %small= t '.user.since', when: distance_of_time_in_words(Time.now, @current_user.created_on)
= simple_form_for(@current_user, :url => update_profile_path) do |f| = simple_form_for(@current_user, :url => update_profile_path) do |f|
= render :partial => 'shared/user_form_fields', :locals => {:f => f} = render :partial => 'shared/user_form_fields', :locals => {:f => f}

View file

@ -4,7 +4,7 @@
%ul.nav.nav-pills.pull-right %ul.nav.nav-pills.pull-right
%li.dropdown %li.dropdown
%a.dropdown-toggle(data-toggle="dropdown" href="#") %a.dropdown-toggle(data-toggle="dropdown" href="#")
= current_user.nick = show_user current_user
%b.caret %b.caret
%ul.dropdown-menu %ul.dropdown-menu
%li= link_to t('.profile'), my_profile_path %li= link_to t('.profile'), my_profile_path

View file

@ -1,6 +1,6 @@
- content_for :javascript do - content_for :javascript do
:javascript :javascript
$('user_nick').focus(); $(#{FoodsoftConfig[:use_nick] ? 'user_nick' : 'user_first_name'}).focus();
- title t('.title', name: FoodsoftConfig[:name]) - title t('.title', name: FoodsoftConfig[:name])
= t('.body', group: h(@invite.group.name), foodcoop: h(FoodsoftConfig[:name])).html_safe = t('.body', group: h(@invite.group.name), foodcoop: h(FoodsoftConfig[:name])).html_safe

View file

@ -1,5 +1,5 @@
- title t('.title') - title t('.title')
= t('.body', user: h(@user.nick)).html_safe = raw t('.body', user: h(show_user(@user)))
= simple_form_for @user, :url => update_password_path(@user.id, :token => @user.reset_password_token) do |form| = simple_form_for @user, :url => update_password_path(@user.id, :token => @user.reset_password_token) do |form|
= form.input :password = form.input :password
= form.input :password_confirmation = form.input :password_confirmation

View file

@ -1,3 +1,3 @@
= t '.header', user: @user.nick, date: I18n.l(Time.now, :format => :short) = t '.header', user: show_user(@user), date: I18n.l(Time.now, :format => :short)
\ \
= @feedback = @feedback

View file

@ -1 +1 @@
= t '.text', group: @group.name, when: @transaction.created_on.strftime('%d.%m.%Y um %H:%M'), balance: @group.account_balance, amount:@transaction.amount, note: @transaction.note, user: @transaction.user.nick, foodcoop: FoodsoftConfig[:name] = t '.text', group: @group.name, when: @transaction.created_on.strftime('%d.%m.%Y um %H:%M'), balance: @group.account_balance, amount:@transaction.amount, note: @transaction.note, user: show_user(@transaction.user), foodcoop: FoodsoftConfig[:name]

View file

@ -1,4 +1,4 @@
= raw t '.text0', ordergroup: @group_order.ordergroup.name, order: @order.name, when: I18n.l(@order.ends), user: @order.updated_by.nick = raw t '.text0', ordergroup: @group_order.ordergroup.name, order: @order.name, when: I18n.l(@order.ends), user: show_user(@order.updated_by)
- for group_order_article in @group_order.group_order_articles.ordered.all(:include => :order_article) - for group_order_article in @group_order.group_order_articles.ordered.all(:include => :order_article)
- article = group_order_article.order_article.article - article = group_order_article.order_article.article
\- #{article.name}: #{group_order_article.result} x #{article.unit} = #{group_order_article.result * article.fc_price} \- #{article.name}: #{group_order_article.result} x #{article.unit} = #{group_order_article.result * article.fc_price}

View file

@ -1 +1 @@
= raw t '.text', user: @user.nick, link: @link, expires: I18n.l(@user.reset_password_expires) = raw t '.text', user: show_user(@user), link: @link, expires: I18n.l(@user.reset_password_expires)

View file

@ -36,7 +36,7 @@
= t '.list.mail', email: mail_to(FoodsoftConfig[:mailing_list_subscribe]) = t '.list.mail', email: mail_to(FoodsoftConfig[:mailing_list_subscribe])
#recipients #recipients
= f.input :recipient_tokens, :input_html => { 'data-pre' => User.find_all_by_id(@message.recipients_ids).map { |u| u.token_attributes }.to_json } = f.input :recipient_tokens, :input_html => { 'data-pre' => User.find_all_by_id(@message.recipients_ids).map(&:token_attributes).to_json }
= f.input :group_id, :as => :select, :collection => Group.undeleted.order('type DESC, name ASC').all.reject { |g| g.memberships.empty? } = f.input :group_id, :as => :select, :collection => Group.undeleted.order('type DESC, name ASC').all.reject { |g| g.memberships.empty? }
= f.input :private = f.input :private
= f.input :subject, input_html: {class: 'input-xxlarge'} = f.input :subject, input_html: {class: 'input-xxlarge'}

View file

@ -13,7 +13,7 @@
%dt= t '.note' %dt= t '.note'
%dd= @order.note %dd= @order.note
%dt= t '.created_by' %dt= t '.created_by'
%dd= link_to_user_message_if_valid(@order.created_by) %dd= show_user_link(@order.created_by)
%dt= t '.begin' %dt= t '.begin'
%dd= format_time(@order.starts) %dd= format_time(@order.starts)
%dt= t '.ending' %dt= t '.ending'

View file

@ -12,12 +12,14 @@
= form_tag sessions_path, class: 'form-horizontal' do = form_tag sessions_path, class: 'form-horizontal' do
.control-group .control-group
%label(for='nick' class='control-label')= t '.user' %label(for='nick' class='control-label')
= User.human_attribute_name (FoodsoftConfig[:use_nick] ? :user : :email)
.controls .controls
= text_field_tag 'nick', nil, autocapitalize: 'off', autocorrect: 'off' = text_field_tag 'nick', nil, autocapitalize: 'off', autocorrect: 'off'
.control-group .control-group
%label(for='password' class='control-label')= t '.password' %label(for='password' class='control-label')
= User.human_attribute_name :password
.controls .controls
= password_field_tag 'password', nil, autocapitalize: 'off', autocorrect: 'off' = password_field_tag 'password', nil, autocapitalize: 'off', autocorrect: 'off'

View file

@ -1,5 +1,5 @@
- comments.each do |comment| - comments.each do |comment|
.comment[comment] .comment[comment]
%strong= comment.user.try(:ordergroup_name) %strong= comment.user.try(:ordergroup_name)
%small (#{comment.user.try(:nick)} am #{format_time(comment.created_at)}): %small (#{show_user comment.user} am #{format_time(comment.created_at)}):
= simple_format(comment.text) = simple_format(comment.text)

View file

@ -12,7 +12,7 @@
%dd %dd
- members = group.users - members = group.users
= "(#{members.size})" = "(#{members.size})"
= members.collect(&:nick).join(", ") = members.collect{|u| show_user(u)}.join(", ")
- unless group.is_a?(Workgroup) - unless group.is_a?(Workgroup)
%dt= t '.apple_limit' %dt= t '.apple_limit'
%dd= group.ignore_apple_restriction ? t('.deactivated') : t('.activated') %dd= group.ignore_apple_restriction ? t('.deactivated') : t('.activated')

View file

@ -4,7 +4,7 @@
= yield = yield
= f.input :user_tokens, :as => :string, = f.input :user_tokens, :as => :string,
:input_html => { 'data-pre' => f.object.users.map { |u| u.token_attributes }.to_json } :input_html => { 'data-pre' => f.object.users.map(&:token_attributes).to_json }
- content_for :javascript do - content_for :javascript do
:javascript :javascript

View file

@ -19,7 +19,7 @@
%td= format_time(order.ends) unless order.ends.nil? %td= format_time(order.ends) unless order.ends.nil?
- if group_order = order.group_order(ordergroup) - if group_order = order.group_order(ordergroup)
- total += group_order.price - total += group_order.price
%td= "#{group_order.updated_by.nick} (#{format_time(group_order.updated_on)})" %td= "#{show_user group_order.updated_by} (#{format_time(group_order.updated_on)})"
%td.numeric= number_to_currency(group_order.price) %td.numeric= number_to_currency(group_order.price)
- else - else
%td{:colspan => 2} %td{:colspan => 2}

View file

@ -1,4 +1,6 @@
= f.input :nick - if FoodsoftConfig[:use_nick]
-# use_nil option to user model validators break required mark
= f.input :nick, required: true
= f.input :first_name = f.input :first_name
= f.input :last_name = f.input :last_name
= f.input :email = f.input :email
@ -19,6 +21,7 @@
= t 'simple_form.labels.settings.settings_group.privacy' = t 'simple_form.labels.settings.settings_group.privacy'
= profile.input 'phone_is_public', as: :boolean, label: false, input_html: { checked: f.object.settings.profile['phone_is_public'] } = profile.input 'phone_is_public', as: :boolean, label: false, input_html: { checked: f.object.settings.profile['phone_is_public'] }
= profile.input 'email_is_public', as: :boolean, label: false, input_html: { checked: f.object.settings.profile['email_is_public'] } = profile.input 'email_is_public', as: :boolean, label: false, input_html: { checked: f.object.settings.profile['email_is_public'] }
- if FoodsoftConfig[:use_nick]
= profile.input 'name_is_public', as: :boolean, label: false, input_html: { checked: f.object.settings.profile['name_is_public'] } = profile.input 'name_is_public', as: :boolean, label: false, input_html: { checked: f.object.settings.profile['name_is_public'] }
.settings-group .settings-group

View file

@ -8,7 +8,8 @@
%div{id: "collapse#{workgroup.id}", class: 'accordion-body collapse'} %div{id: "collapse#{workgroup.id}", class: 'accordion-body collapse'}
.accordion-inner .accordion-inner
%ul.unstyled %ul.unstyled
- workgroup.users.includes(:groups).order('nick').each do |user| - workgroup.users.includes(:groups).natural_order.each do |user|
%li %li
= user.nick = show_user(user)
%small (#{user.ordergroup.try(:name)}) %small (#{user.ordergroup.try(:name)})

View file

@ -7,7 +7,7 @@
= simple_form_for(@stock_taking) do |f| = simple_form_for(@stock_taking) do |f|
= f.input :date, as: :date_picker = f.input :date, as: :date_picker
= f.input :note, :input_html => {:size => "28x7", :value => "#{@current_user.nick}: ..."} = f.input :note, :input_html => {:size => "28x7", :value => "#{show_user @current_user, unique: true}: ..."}
%h2= t '.stock_articles' %h2= t '.stock_articles'
#stock_changes #stock_changes

View file

@ -19,7 +19,7 @@
= f.input :name = f.input :name
= f.input :description, as: :text, input_html: {rows: 10} = f.input :description, as: :text, input_html: {rows: 10}
= f.input :duration, :as => :select, :collection => 1..3 = f.input :duration, :as => :select, :collection => 1..3
= f.input :user_list, :as => :string, :input_html => { 'data-pre' => @task.users.map { |u| u.token_attributes }.to_json } = f.input :user_list, :as => :string, :input_html => { 'data-pre' => @task.users.map(&:token_attributes).to_json }
= f.input :required_users = f.input :required_users
= f.association :workgroup = f.association :workgroup
= f.input :due_date, as: :date_picker = f.input :due_date, as: :date_picker

View file

@ -52,6 +52,11 @@ default: &defaults
# not fully enforced right now, since the check is only client-side # not fully enforced right now, since the check is only client-side
# minimum_balance: 0 # minimum_balance: 0
# When use_nick is enabled, there will be a nickname field in the user form,
# and the option to show a nickname instead of full name to foodcoop members.
# Members of a user's groups and administrators can still see full names.
use_nick: false
# email address to be used as sender # email address to be used as sender
email_sender: foodsoft@foodcoop.test email_sender: foodsoft@foodcoop.test

View file

@ -1328,9 +1328,7 @@ de:
login: Anmelden login: Anmelden
nojs: Achtung, Cookies und Javascript müssen aktiviert sein! %{link} bitte abschalten. nojs: Achtung, Cookies und Javascript müssen aktiviert sein! %{link} bitte abschalten.
noscript: NoScript noscript: NoScript
password: Passwort
title: Foodsoft anmelden title: Foodsoft anmelden
user: Benutzerin
shared: shared:
articles: articles:
ordered: Bestellt ordered: Bestellt

View file

@ -797,6 +797,7 @@ en:
helpers: helpers:
application: application:
edit_user: Edit user edit_user: Edit user
nick_fallback: (empty username)
role_admin: Admin role_admin: Admin
role_article_meta: Articles role_article_meta: Articles
role_finance: Finance role_finance: Finance
@ -1332,9 +1333,7 @@ en:
login: Login login: Login
nojs: Attention, cookies and javascript have to be activated! Please switch off %{link}. nojs: Attention, cookies and javascript have to be activated! Please switch off %{link}.
noscript: NoScript noscript: NoScript
password: Password
title: Foodsoft login title: Foodsoft login
user: User
shared: shared:
articles: articles:
ordered: Ordered ordered: Ordered

View file

@ -1335,9 +1335,7 @@ fr:
login: Te connecter login: Te connecter
nojs: Attention, les cookies et le javascript doivent être activés! Merci de désactiver %{link}. nojs: Attention, les cookies et le javascript doivent être activés! Merci de désactiver %{link}.
noscript: NoScript noscript: NoScript
password: Mot de passe
title: Te connecter à Foodsoft title: Te connecter à Foodsoft
user: Identifiant
shared: shared:
articles: articles:
ordered: Commandé ordered: Commandé

View file

@ -1262,9 +1262,7 @@ nl:
login: Aanmelden login: Aanmelden
nojs: Opgelet, cookies en javascript moeten toegelaten worden! %{link} uitzetten, alsjeblieft. nojs: Opgelet, cookies en javascript moeten toegelaten worden! %{link} uitzetten, alsjeblieft.
noscript: NoScript noscript: NoScript
password: Wachtwoord
title: Foodsoft aanmelden title: Foodsoft aanmelden
user: Gebruiker
shared: shared:
articles: articles:
ordered: Besteld ordered: Besteld

View file

@ -0,0 +1,9 @@
class AllowMissingNick < ActiveRecord::Migration
def self.up
change_column :users, :nick, :string, :default => nil, :null => true
end
def self.down
change_column :users, :nick, :string, :default => "", :null => false
end
end

View file

@ -11,7 +11,7 @@
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130718183101) do ActiveRecord::Schema.define(:version => 20130920201529) do
create_table "article_categories", :force => true do |t| create_table "article_categories", :force => true do |t|
t.string "name", :default => "", :null => false t.string "name", :default => "", :null => false
@ -324,7 +324,7 @@ ActiveRecord::Schema.define(:version => 20130718183101) do
add_index "tasks", ["workgroup_id"], :name => "index_tasks_on_workgroup_id" add_index "tasks", ["workgroup_id"], :name => "index_tasks_on_workgroup_id"
create_table "users", :force => true do |t| create_table "users", :force => true do |t|
t.string "nick", :default => "", :null => false t.string "nick"
t.string "password_hash", :default => "", :null => false t.string "password_hash", :default => "", :null => false
t.string "password_salt", :default => "", :null => false t.string "password_salt", :default => "", :null => false
t.string "first_name", :default => "", :null => false t.string "first_name", :default => "", :null => false

View file

@ -9,6 +9,8 @@ class FoodsoftConfig
set_config Rails.env set_config Rails.env
# Overwrite scope to have a better namescope than 'production' # Overwrite scope to have a better namescope than 'production'
self.scope = config[:default_scope] or raise "No default_scope is set" self.scope = config[:default_scope] or raise "No default_scope is set"
# Set defaults for backward-compatibility
set_missing
end end
# Set config and database connection for specific foodcoop # Set config and database connection for specific foodcoop
@ -46,5 +48,13 @@ class FoodsoftConfig
ActiveRecord::Base.establish_connection(database_config) ActiveRecord::Base.establish_connection(database_config)
end end
# When new options are introduced, put backward-compatible defaults here, so that
# configuration files that haven't been updated, still work as they did.
def set_missing
config.replace({
use_nick: true
}.merge(config))
end
end end
end end

View file

@ -2,7 +2,7 @@
%tr %tr
%td{:style => "padding-left: #{ident}px"} %td{:style => "padding-left: #{ident}px"}
= link_to page.title, wiki_page_path(page.permalink) = link_to page.title, wiki_page_path(page.permalink)
%td #{page.user.try(:nick)} (#{format_datetime_timespec(page.updated_at, t('.date_format'))}) %td #{show_user page.user} (#{format_datetime_timespec(page.updated_at, t('.date_format'))})
-if siteMap == 1 -if siteMap == 1
-for child in page.children.all -for child in page.children.all
= render :partial => 'page_list_item', :locals => {:page => child, :level => level+1, :siteMap => 1} = render :partial => 'page_list_item', :locals => {:page => child, :level => level+1, :siteMap => 1}

View file

@ -17,7 +17,7 @@
- @page.versions.reverse.each do |version| - @page.versions.reverse.each do |version|
%li %li
= link_to I18n.l(version.updated_at, :format => t('.date_format')), version_page_path(@page, :version => version.lock_version) = link_to I18n.l(version.updated_at, :format => t('.date_format')), version_page_path(@page, :version => version.lock_version)
= "(#{User.find_by_id(version.updated_by).try(:nick)})" = "(#{show_user(User.find_by_id(version.updated_by))})"
- unless @page.children.empty? - unless @page.children.empty?
#subpages.well.well-small{:style => "display:none"} #subpages.well.well-small{:style => "display:none"}
@ -48,4 +48,4 @@
%i.icon-edit= t '.edit' %i.icon-edit= t '.edit'
= link_to t('.delete'), @page, class: 'btn btn-danger', :method => :delete, = link_to t('.delete'), @page, class: 'btn btn-danger', :method => :delete,
:confirm => t('.delete_confirm') :confirm => t('.delete_confirm')
!= '| ' + t('.last_updated', user: h(@page.user.try(:nick)), when: format_datetime(@page.updated_at)) != '| ' + t('.last_updated', user: show_user(@page.user), when: format_datetime(@page.updated_at))

View file

@ -4,7 +4,7 @@
%h3= t '.title_version' %h3= t '.title_version'
%b= "#{format_datetime_timespec(@version.updated_at, t('.date_format'))}" %b= "#{format_datetime_timespec(@version.updated_at, t('.date_format'))}"
%ul %ul
%li= t '.author', user: User.find(@version.updated_by).nick %li= t '.author', user: show_user(User.find(@version.updated_by))
%li= link_to t('.view_current'), wiki_page_path(:permalink => @page.permalink) %li= link_to t('.view_current'), wiki_page_path(:permalink => @page.permalink)
%li= link_to t('.revert'), revert_page_path(@page, :version => @version.lock_version) %li= link_to t('.revert'), revert_page_path(@page, :version => @version.lock_version)

View file

@ -4,7 +4,7 @@ describe 'the session', :type => :feature do
let(:user) { create :user } let(:user) { create :user }
describe 'login page', :type => :feature do describe 'login page', :type => :feature do
it 'is accesible' do it 'is accessible' do
get login_path get login_path
expect(response).to be_success expect(response).to be_success
end end
@ -16,6 +16,13 @@ describe 'the session', :type => :feature do
login user.nick, 'XX'+user.password login user.nick, 'XX'+user.password
expect(page).to have_selector('.alert-error') expect(page).to have_selector('.alert-error')
end end
it 'can log me in using an email address' do
visit login_path
fill_in 'nick', :with => user.email
fill_in 'password', :with => user.password
find('input[type=submit]').click
expect(page).to_not have_selector('.alert-error')
end
end end
end end

View file

@ -49,6 +49,16 @@ describe User do
it 'has a unique email' do it 'has a unique email' do
expect(build(:user, email: "#{user.email}")).to be_invalid expect(build(:user, email: "#{user.email}")).to be_invalid
end end
it 'can authenticate using email address' do
expect(User.authenticate(user.email, 'blahblah')).to be_true
end
it 'can authenticate when there is no nick' do
user.nick = nil
expect(user).to be_valid
expect(User.authenticate(user.email, 'blahblah')).to be_true
end
end end
describe 'admin' do describe 'admin' do