drg_cms 0.5.6 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/drg_cms/drg_cms.js +17 -2
  3. data/app/assets/stylesheets/drg_cms/drg_cms.css +197 -68
  4. data/app/controllers/cmsedit_controller.rb +3 -3
  5. data/app/controllers/dc_application_controller.rb +71 -43
  6. data/app/forms/cms_menu.yml +42 -55
  7. data/app/forms/dc_page.yml +1 -1
  8. data/app/forms/dc_site.yml +8 -0
  9. data/app/helpers/cmsedit_helper.rb +8 -2
  10. data/app/helpers/dc_application_helper.rb +58 -12
  11. data/app/helpers/dc_common_renderer.rb +5 -4
  12. data/app/helpers/dc_menu_renderer.rb +2 -2
  13. data/app/helpers/dc_page_renderer.rb +3 -1
  14. data/app/helpers/dc_part_renderer.rb +41 -1
  15. data/app/helpers/dc_simple_menu_renderer.rb +2 -1
  16. data/app/models/dc_page.rb +0 -2
  17. data/app/models/dc_site.rb +4 -0
  18. data/app/models/drgcms_form_fields.rb +27 -18
  19. data/app/views/cmsedit/_edit_stuff.html.erb +54 -38
  20. data/app/views/cmsedit/_form.html.erb +1 -1
  21. data/app/views/cmsedit/edit.html.erb +0 -1
  22. data/app/views/cmsedit/login.html.erb +2 -1
  23. data/app/views/cmsedit/{_edit_stuff.js.erb → remove_edit_stuff.js.erb} +0 -0
  24. data/app/views/layouts/{cmsedit.html.erb → __cmsedit.html.erb} +0 -0
  25. data/app/views/layouts/cms.html.erb +3 -1
  26. data/app/views/layouts/content.html.erb +1 -1
  27. data/config/locales/drgcms_en.yml +2 -0
  28. data/config/locales/drgcms_sl.yml +2 -0
  29. data/config/locales/models_en.yml +4 -1
  30. data/config/locales/models_sl.yml +3 -1
  31. data/lib/drg_cms/version.rb +1 -1
  32. data/lib/tasks/dc_at_the_beginning.rake +5 -5
  33. data/lib/tasks/dc_cleanup.rake +8 -2
  34. metadata +4 -4
@@ -55,8 +55,9 @@ end
55
55
  # Renderer for Google analytics code.
56
56
  #
57
57
  # Parameters:
58
- # Are passed through @opts hash and can therefore be set on site or design
59
- # or page document parameters field as ga_acc key.
58
+ # Are passed through @opts hash and can therefore be set on site
59
+ # or page document parameters field as ga_acc key. You may also disable sending
60
+ #
60
61
  #
61
62
  # If eu_cookies_allowed function is defined in javascript libraries it will be
62
63
  # called and if false is returned GA code will not be executed. This is in
@@ -67,7 +68,7 @@ end
67
68
  ########################################################################
68
69
  def google_analytics
69
70
  ga_acc = @opts[:code] || @opts[:ga_acc]
70
- return '' if ga_acc.nil?
71
+ return '' if ga_acc.nil? or ga_acc == '/'
71
72
  html =<<EOT
72
73
  <!-- Google analytics. -->
73
74
  <script type="text/javascript">
@@ -93,7 +94,7 @@ end
93
94
  # are found in url iframe will be initial loaded with url parameters thus
94
95
  # enabling forms load on page display.
95
96
  ########################################################################
96
- def iframe_edit()
97
+ def _remove_iframe_edit()
97
98
  @parent.render(partial: 'dc_common/iframe_edit', formats: [:html])
98
99
  end
99
100
 
@@ -126,8 +126,8 @@ def do_menu_level(menu, options={})
126
126
  if (sub = item.dc_menu_items).size > 0
127
127
  if @opts[:edit_mode] > 1
128
128
  opts = options.clone
129
- opts[:ids] = (opts[:ids] ? "#{opts[:ids]};" : '') + menu._id.to_s
130
- opts[:table] = (opts[:table] ? "#{opts[:table]};" : '') + 'dc_menu_item'
129
+ opts['ids'] = (opts['ids'] ? "#{opts['ids']};" : '') + menu._id.to_s
130
+ opts['table'] = (opts['table'] ? "#{opts['table']};" : '') + 'dc_menu_item'
131
131
  end
132
132
  html << do_menu_level(item, opts)
133
133
  end
@@ -44,6 +44,7 @@ def initialize( parent, opts={} ) #:nodoc:
44
44
  @parent = parent
45
45
  @opts = opts
46
46
  @page = @parent.page
47
+ @css = ''
47
48
  end
48
49
 
49
50
  #########################################################################
@@ -55,6 +56,7 @@ def default
55
56
  #
56
57
  html = ''
57
58
  html << dc_page_edit_menu() if @opts[:edit_mode] > 1
59
+ @parent.page_title = @page.title.blank? ? @page.subject : @page.title
58
60
  html << @page.body
59
61
  end
60
62
 
@@ -70,7 +72,7 @@ end
70
72
  # Return CSS part of code.
71
73
  ########################################################################
72
74
  def render_css
73
- @page.css
75
+ @css
74
76
  end
75
77
 
76
78
  end
@@ -97,6 +97,11 @@ def load_parts #:nodoc:
97
97
  type = decamelize_type(part._type) || 'dc_part'
98
98
  @parent.parts << [part, @parent.page.id, type, "#{@parent.site.page_table};#{type}"]
99
99
  end
100
+ # add parts in page
101
+ @parent.site.dc_parts.where(active: true).each do |part|
102
+ type = decamelize_type(part._type) || 'dc_part'
103
+ @parent.parts << [part, @parent.site.id, type, "dc_site;#{type}"]
104
+ end
100
105
  # add parts belonging to site, defined in dc_pieces
101
106
  DcPiece.where(site_id: @parent.site._id, active: true).each do |part|
102
107
  @parent.parts << [part, part._id, 'dc_piece', 'dc_piece']
@@ -142,7 +147,7 @@ end
142
147
 
143
148
  ########################################################################
144
149
  # This method will search and render single part defined in pages file. Part may
145
- # be defined in current page document or in any page document in found in pages file. Parameters
150
+ # be defined in current page document or in any page document found in pages file. Parameters
146
151
  # are send through options hash.
147
152
  #
148
153
  # Options:
@@ -173,6 +178,41 @@ def in_page
173
178
  end
174
179
  end
175
180
 
181
+ ########################################################################
182
+ # Renderer for single datapage kind of sites.
183
+ ########################################################################
184
+ def single_sitedoc
185
+ # if div_id option specified search for part with this div_id.
186
+ # This can be used to render footer or header of site.
187
+ part = if @opts[:div_id]
188
+ @parent.parts.find_by(div_id: @opts[:div_id])
189
+ else
190
+ @parent.part
191
+ end
192
+ # part not found. Render error message.
193
+ return "Part #{@opts[:div_id]} not found!" if part.nil?
194
+ # prepare edit parameters
195
+ @opts[:editparams].merge!(id: part, ids: @parent.site._id, formname: 'dc_part',
196
+ table: "dc_site;dc_part", record_div_id: 'document' )
197
+ render_particle(part, @opts)
198
+ end
199
+
200
+ ########################################################################
201
+ # Render menu for single datapage kind of sites.
202
+ ########################################################################
203
+ def single_sitedoc_menu
204
+ # prepare div markup
205
+ menu_div = @opts[:menu_div] ? "id=#{@opts[:menu_div]}" : ''
206
+ html = "<div #{menu_div}><ul>\n"
207
+ # collect all dc_part documents which make menu
208
+ @parent.parts.where(div_id: 'document').order_by(order: 1).each do |part|
209
+ # mark selected item
210
+ selected = (part == @parent.part) ? 'class="menu-selected"' : ''
211
+ html << "<li #{selected}>#{ @parent.link_to(part.name, part.link ) }</li>\n"
212
+ end
213
+ html << "</ul></div>\n"
214
+ end
215
+
176
216
  ########################################################################
177
217
  # Renderer dispatcher. Method returns HTML part of code.
178
218
  ########################################################################
@@ -107,9 +107,10 @@ def link_4menu(item)
107
107
  else
108
108
  @parent.fa_icon(item.picture)
109
109
  end
110
+ caption << ' '
110
111
  end
111
112
  # - in first place won't write caption text
112
- caption = caption.html_safe + (item.caption[0] == '-' ? '' : (' ' + item.caption) )
113
+ caption = caption.html_safe + (item.caption[0] == '-' ? '' : item.caption )
113
114
 
114
115
  target = item.target.blank? ? nil : item.target
115
116
  @parent.link_to(caption, link, {target: target})
@@ -60,8 +60,6 @@ field :kats, type: Array # Categories
60
60
  field :policy_id, type: BSON::ObjectId
61
61
 
62
62
  embeds_many :dc_parts
63
- #embeds_many :dc_parts, as: :dc_parts
64
-
65
63
 
66
64
  belongs_to :dc_site
67
65
  belongs_to :dc_design
@@ -56,9 +56,13 @@ included do
56
56
  field :design, type: String, default: ''
57
57
 
58
58
  embeds_many :dc_policies
59
+ embeds_many :dc_parts
59
60
 
60
61
  index( { name: 1 }, { unique: true } )
61
62
 
63
+ validates :name, presence: true
64
+ validates :name, uniqueness: true
65
+
62
66
  ########################################################################
63
67
  # Return choices for select for site_id
64
68
  ########################################################################
@@ -221,7 +221,13 @@ def render
221
221
  @html << if @yaml['eval']
222
222
  if @yaml['eval'].match('dc_name4_id')
223
223
  a = @yaml['eval'].split(',')
224
- @parent.dc_name4_id(a[1], a[2], @record[ @yaml['name'] ])
224
+ if a.size == 3
225
+ @parent.dc_name4_id(a[1], a[2], nil, @record[ @yaml['name'] ])
226
+ else
227
+ @parent.dc_name4_id(a[1], a[2], a[3], @record[ @yaml['name'] ])
228
+ end
229
+
230
+ # @parent.dc_name4_id(a[1], a[2], @record[ @yaml['name'] ])
225
231
  else
226
232
  eval( "#{@yaml['eval']} '#{@record[ @yaml['name'] ]}'")
227
233
  end
@@ -379,9 +385,11 @@ def ro_standard(table, search)
379
385
  result = ''
380
386
  table = table.classify.constantize
381
387
  return self if @record[@yaml['name']].nil?
382
- @record[@yaml['name']].each do |element|
383
- result << table.find(element)[search] + '<br>'
384
- end
388
+ # when field name and method are defined together
389
+ search = search.split('.').first if search.match('.')
390
+ @record[@yaml['name']].each do |element|
391
+ result << table.find(element)[search] + '<br>'
392
+ end
385
393
  super(result)
386
394
  end
387
395
 
@@ -587,7 +595,6 @@ def ro_standard
587
595
  return self if value.nil?
588
596
  #
589
597
  get_choices.each do |choice|
590
- p choice, value
591
598
  if choice.class == Array
592
599
  return super(choice.first) if choice.last == value
593
600
  else
@@ -1215,21 +1222,23 @@ end
1215
1222
  # * +type:+ text_with_select (required)
1216
1223
  # * +choices:+ Values for choices separated by comma. Values can also be specified like description:value.
1217
1224
  # In this case description will be shown to user, but value will be saved to document.
1218
- # * choices: 'OK:0,Ready:1,Error:2'
1219
- # * choices: Ruby,Pyton,PHP
1225
+ # choices: 'OK:0,Ready:1,Error:2'
1226
+ # choices: Ruby,Pyton,PHP
1220
1227
  # * +eval:+ Choices will be provided by evaluating expression
1221
- # * eval: dc_choices4('model_name','description_field_name','_id'); dc_choices4 helper will provide data for select field.
1222
- # * eval: ModelName.choices4_field; ModelName class will define method choices4_field which
1228
+ # eval: dc_choices4('model_name','description_field_name','_id'); dc_choices4 helper will provide data for select field.
1229
+ # eval: ModelName.choices4_field; ModelName class will define method choices4_field which
1223
1230
  # will provide data for select field. Since expression is evaluated in the context of Form Field object
1224
- # even session session variables can be accessed. Ex. +eval: 'MyClass.method(@parent.session[:user_id])'+
1225
- # * collection_name.search_field_name.method_name; When searching is more complex custom search
1226
- # method may be defined in CollectionName model which will provide result set for search.
1227
- # * If choices or eval is not defined choices will be provided from translation helpers. For example:
1228
- # Collection has field status choices for field may be provided by en.helpers.model_name.choices4_status
1229
- # entry of english translation. English is of course default translation. If you provide translations in
1230
- # your local language then select choices will be localized.
1231
- # * en.helpers.model_name.choices4_status: 'OK:0,Ready:1,Error:2'
1232
- # * sl.helpers.model_name.choices4_status: 'V redu:0,Pripravljen:1,Napaka:2'
1231
+ # Even session session variables can be accessed.
1232
+ # eval: 'MyClass.method(@parent.session[:user_id])'
1233
+ # When searching is more complex custom search method may be defined in CollectionName
1234
+ # model which will provide result set for search.
1235
+ # eval: collection_name.search_field_name.method_name;
1236
+ # If choices or eval is not defined choices will be provided from translation helpers. For example:
1237
+ # Collection has field status. Choices for field will be provided by en.helpers.model_name.choices4_status
1238
+ # entry of english translation. English is of course default translation. If you provide translations in
1239
+ # your local language then select choices will be localized.
1240
+ # en.helpers.model_name.choices4_status: 'OK:0,Ready:1,Error:2'
1241
+ # sl.helpers.model_name.choices4_status: 'V redu:0,Pripravljen:1,Napaka:2'
1233
1242
  # * +html:+ html options which apply to select and text_field fields (optional)
1234
1243
  #
1235
1244
  # Form example:
@@ -2,50 +2,66 @@
2
2
  <div id="cmsedit-div">
3
3
  <%= link_to('CMS', controller: 'dc_common', action: 'toggle_edit_mode', return_to: request.url) %>
4
4
  <% if session[:edit_mode] > 1 %>
5
- <table class="cmsedit-table">
6
- <tr>
7
- <td class="cmsedit-menu">
8
- <%#= link_to(image_tag('drg_cms/view_tile.png', class: 'dc-link-img', title: t('drgcms.menu_plus')), '#',
9
- onclick: "$('#cmsedit-menu-plus').toggleClass('div-hidden');") %>
10
- <% if @page %>
11
- <%= link_to( fa_icon('edit lg', class: "dc-animate", title: "#{t('drgcms.edit_page')}"),
5
+ <table class="cmsedit-table">
6
+ <tr>
7
+ <td class="cmsedit-menu">
8
+ <% if false %>
9
+ <%= link_to(image_tag('drg_cms/view_tile.png', class: 'dc-link-img', title: t('drgcms.menu_plus')), '#',
10
+ onclick: "$('#cmsedit-menu-plus').toggleClass('div-hidden');") %>
11
+ <% end %>
12
+ <ul><li>
13
+ <%= fa_icon('bars lg') %>
14
+ <ul>
15
+ <%= dc_choices4_cmsmenu.html_safe %>
16
+ </ul>
17
+ </li></ul>
18
+
19
+ <% if @page %>
20
+ <%= link_to( fa_icon('file-text-o lg', class: "dc-animate", title: "#{t('drgcms.edit_page')}"),
12
21
  {controller: 'cmsedit', action: 'edit', table: @site.page_table, id: @page._id}, target: 'iframe_cms') %>
22
+ <% end %>
13
23
 
24
+ <% if @design %>
14
25
  <%= link_to( fa_icon('credit-card lg', class: "dc-animate", title: "#{t('drgcms.edit_design')}"),
15
26
  {controller: 'cmsedit', action: 'edit', table: 'dc_design', id: @design._id}, target: 'iframe_cms') %>
16
-
27
+ <% end %>
28
+
29
+ <% if @site %>
17
30
  <%= link_to( fa_icon('globe lg', class: "dc-animate", title: "#{t('drgcms.edit_site')}"),
18
31
  {controller: 'cmsedit', action: 'edit', table: 'dc_site', id: @site._id}, target: 'iframe_cms') %>
19
- <% end %>
20
-
21
- <%= link_to( fa_icon('compress lg', class: "dc-animate", title: t('drgcms.minimize')), '#',
22
- onclick: "$('#iframe_cms').height(1); return false;" ) %>
23
-
24
- <%= link_to( fa_icon('expand lg', class: "dc-animate", title: t('drgcms.maximize')), '#',
25
- onclick: "$('#iframe_cms').height( $('#iframe_cms').height() + 500);; return false;") %>
26
-
27
- <%= link_to( fa_icon('refresh lg', class: "dc-animate", title: t('drgcms.reload')), '#',
28
- onclick: "document.location.reload(true); return false;") %>
29
-
30
- <%= select('record', 'select', dc_choices4_cmsmenu ) %>
31
- <%= link_to( fa_icon('bars lg', class: "dc-animate", title: t('drgcms.edit_selected')), '#',
32
- onclick: "$('#iframe_cms').attr('src', $('#record_select').val() ); return false;") %>
33
-
34
- <%= link_to( fa_icon('copy lg', class: "dc-animate", title: "#{t('drgcms.doc_paste_title')}"),
35
- {controller: 'dc_common', action: 'paste_clipboard'}, target: 'iframe_cms') %>
36
-
37
- <%= DrgcmsFormFields::Elfinder.file_manager_url(self) %>
38
-
39
- </td>
40
- </tr>
41
-
42
- <tr class="cmsedit-menu-plus" class="div-hidden dc-form-frame"><td>
43
- <%= @menu_plus %>
44
- </td><tr>
45
-
46
- <tr class="cmsedit-iframe"><td class="cmsedit-iframe">
47
- <div><iframe id="iframe_cms" name="iframe_cms"></iframe></div>
48
- </td></tr>
32
+ <% end %>
33
+ <b>&nbsp; | &nbsp; </b>
34
+ <%= link_to( fa_icon('compress lg', class: "dc-animate", title: t('drgcms.minimize')), '#',
35
+ onclick: "$('#iframe_cms').height(1); return false;" ) %>
36
+
37
+ <%= link_to( fa_icon('expand lg', class: "dc-animate", title: t('drgcms.maximize')), '#',
38
+ onclick: "$('#iframe_cms').height( $('#iframe_cms').height() + 500);; return false;") %>
39
+
40
+ <%= link_to( fa_icon('refresh lg', class: "dc-animate", title: t('drgcms.reload')), '#',
41
+ onclick: "document.location.reload(true); return false;") %>
42
+ <b>&nbsp; | &nbsp; </b>
43
+ <% if false %>
44
+ <%= select('record', 'select', dc_choices4_cmsmenu ) %>
45
+
46
+ <%= link_to( fa_icon('bars lg', class: "dc-animate", title: t('drgcms.edit_selected')), '#',
47
+ onclick: "$('#iframe_cms').attr('src', $('#record_select').val() ); return false;") %>
48
+ <% end %>
49
+
50
+ <%= link_to( fa_icon('copy lg', class: "dc-animate", title: "#{t('drgcms.doc_paste_title')}"),
51
+ {controller: 'dc_common', action: 'paste_clipboard'}, target: 'iframe_cms') %>
52
+
53
+ <%= DrgcmsFormFields::Elfinder.file_manager_url(self) %>
54
+
55
+ </td>
56
+ </tr>
57
+ <% if false %>
58
+ <tr class="cmsedit-menu-plus div-hidden dc-form-frame"><td>
59
+ <%= @menu_plus %>
60
+ </td><tr>
61
+ <% end %>
62
+ <tr class="cmsedit-iframe"><td class="cmsedit-iframe">
63
+ <div><iframe id="iframe_cms" name="iframe_cms"></iframe></div>
64
+ </td></tr>
49
65
  </table>
50
66
  <% end %>
51
67
  </div>
@@ -17,4 +17,4 @@
17
17
 
18
18
  </div>
19
19
 
20
- <%= javascript_tag @js if @js.to_s.size > 0 %>
20
+ <%= javascript_tag(@js) unless @js.blank? %>
@@ -1,4 +1,3 @@
1
-
2
1
  <%= dc_table_title dc_edit_title() %>
3
2
 
4
3
  <%= form_tag @parms, method: :put, id: 'cmsform' do %>
@@ -1,7 +1,7 @@
1
1
  <% if session[:edit_mode] and session[:edit_mode] > 0 %>
2
2
  <%= render partial: 'edit_stuff' %>
3
- <%= render partial: 'edit_stuff', formats: ['js'] %>
4
3
  <% else %>
4
+ <div id="dc-login">
5
5
  <h2>CMS login</h2>
6
6
  <%= dc_flash_messages %>
7
7
  <%= form_tag controller: 'dc_common', action: 'process_login', return_to: '/cmsedit/login?ok=1', method: :put do %>
@@ -18,4 +18,5 @@
18
18
  </table>
19
19
  <%= hidden_field_tag('return_to_error', request.url ) %>
20
20
  <% end %>
21
+ </div>
21
22
  <% end %>
@@ -4,8 +4,10 @@
4
4
  <title><%= @page_title %></title>
5
5
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6
6
  <%= stylesheet_link_tag "cms", media: 'all' %>
7
- <style type="text/css" media="all"><%= "#{@site.css}#{@design.css}\n#{@page.css}".html_safe if @page %></style>
7
+ <% if false %> not require in cms mode
8
+ <style type="text/css" media="all"><%= "#{@site.css if @site}#{@design.css if @design}\n#{@page.css if @page}".html_safe %></style>
8
9
  <%= @site.header.html_safe if @site %>
10
+ <% end %>
9
11
  <%= javascript_include_tag "cms" %>
10
12
  <%= csrf_meta_tags %>
11
13
  </head>
@@ -4,7 +4,7 @@
4
4
  <title><%= @page_title %></title>
5
5
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6
6
  <%= stylesheet_link_tag "application", media: 'all' %>
7
- <style type="text/css" media="all"><%= "#{@site.css}#{@design.css}\n#{@page.css}".html_safe %></style>
7
+ <style type="text/css" media="all"><%= "#{@site.css if @site}#{@design.css if @design}\n#{@page.css if @page}".html_safe %></style>
8
8
  <%= @site.header.html_safe %>
9
9
  <%= javascript_include_tag "application" %>
10
10
  <%= csrf_meta_tags %>
@@ -36,6 +36,8 @@ en:
36
36
  doc_paste_clipboard: Start update
37
37
  doc_paste_title: Insert (update) documents from JSON data
38
38
  doc_paste_update: Update existing document if document._id exists
39
+ doc_no_delete: Document can not be deleted!
40
+ doc_no_update: Document can not be updated!
39
41
  edit_content: Edit content
40
42
  edit_advanced: Advanced edit
41
43
  edit_new_page: Create new page
@@ -36,6 +36,8 @@ sl:
36
36
  doc_paste_clipboard: Obdelaj podatke
37
37
  doc_paste_title: Ustvari (spremeni) dokumente iz JSON zapisa
38
38
  doc_paste_update: Spremeni obstoječ dokument, če dokument._id obstaja
39
+ doc_no_delete: Dokumenta ni možno izbrisati!
40
+ doc_no_update: Dokumenta ni možno spremeniti!
39
41
  edit_content: Urejanje vsebine
40
42
  edit_advanced: Napredno urejanje
41
43
  edit_new_page: Ustvarite novo stran
@@ -35,6 +35,7 @@ en:
35
35
  3advanced: Advanced
36
36
  4design: Design
37
37
  5permissions: Permissions
38
+ 6parts: Parts
38
39
 
39
40
  name: Name
40
41
  description: Description
@@ -53,6 +54,7 @@ en:
53
54
  files_directory: Files directory
54
55
  active: Active
55
56
  dc_policies: Access policies
57
+ dc_parts: Parts
56
58
  settings: Settings
57
59
  menu_name: Menu name
58
60
  alias_for: Alias for
@@ -179,7 +181,7 @@ en:
179
181
  active: Active
180
182
 
181
183
  dc_simple_menu:
182
- tabletitle: Simple (my first) menus
184
+ tabletitle: Simple menus
183
185
  tab1: Base
184
186
  tab2: Items
185
187
  tab3: CSS
@@ -504,6 +506,7 @@ en:
504
506
  alias_for: Is alias name for entered site name
505
507
  rails_view: Rails view filename used as standard design
506
508
  design: Standard design can also be defined at the site level
509
+ dc_parts: Parts contained in site
507
510
 
508
511
  dc_user:
509
512
  username: Username