Add nullable property for fields

master
Vitaliy Filippov 2014-03-24 18:44:31 +04:00
parent b2d8118048
commit 1cd3cae485
10 changed files with 124 additions and 130 deletions

View File

@ -102,9 +102,10 @@ sub get_param_list
my $legal = {};
for (qw(priority bug_severity platform op_sys))
{
$legal->{$_} = [ Bugzilla->get_field($_)->legal_value_names ];
# Ignore evaluation errors - this piece of code may be called in checksetup.pl,
# fielddefs table may not be created at that time...
$legal->{$_} = eval { Bugzilla->get_field($_)->legal_value_names } || [];
}
my @param_list = (
{
name => 'useclassification',

View File

@ -541,6 +541,7 @@ use constant ABSTRACT_SCHEMA => {
mailhead => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
sortkey => {TYPE => 'INT2', NOTNULL => 1},
obsolete => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
nullable => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
enter_bug => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
buglist => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'},
visibility_field_id => {TYPE => 'INT4', REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}},

View File

@ -102,6 +102,7 @@ use constant DB_COLUMNS => qw(
mailhead
sortkey
obsolete
nullable
enter_bug
clone_bug
buglist
@ -123,6 +124,7 @@ use constant VALIDATORS => {
buglist => \&Bugzilla::Object::check_boolean,
mailhead => \&Bugzilla::Object::check_boolean,
obsolete => \&Bugzilla::Object::check_boolean,
nullable => \&Bugzilla::Object::check_boolean,
sortkey => \&_check_sortkey,
type => \&_check_type,
visibility_field_id => \&_check_visibility_field_id,
@ -138,6 +140,7 @@ use constant UPDATE_COLUMNS => qw(
mailhead
sortkey
obsolete
nullable
enter_bug
clone_bug
buglist
@ -168,7 +171,7 @@ use constant SQL_DEFINITIONS => {
# These are used by populate_field_definitions to populate
# the fielddefs table.
use constant DEFAULT_FIELDS => (
{name => 'bug_id', desc => 'Bug ID', buglist => 1, in_new_bugmail => 1},
{name => 'bug_id', desc => 'Bug ID', buglist => 1, in_new_bugmail => 1},
{name => 'short_desc', desc => 'Summary', buglist => 1, in_new_bugmail => 1},
{name => 'classification', desc => 'Classification', buglist => 1, in_new_bugmail => 1},
{name => 'product', desc => 'Product', buglist => 1, in_new_bugmail => 1, type => FIELD_TYPE_SINGLE_SELECT},
@ -426,10 +429,21 @@ sub obsolete { return $_[0]->{obsolete} }
=over
=item C<nullable>
a boolean specifying whether NULL value is allowed for this field
=back
=cut
sub nullable { return $_[0]->{nullable} }
=over
=item C<enter_bug>
A boolean specifying whether or not this field should appear on
enter_bug.cgi
A boolean specifying whether this field should appear on enter_bug.cgi
=back
@ -736,6 +750,8 @@ They will throw an error if you try to set the values to something invalid.
=item C<set_obsolete>
=item C<set_nullable>
=item C<set_sortkey>
=item C<set_in_new_bugmail>
@ -754,6 +770,7 @@ sub set_description { $_[0]->set('description', $_[1]); }
sub set_enter_bug { $_[0]->set('enter_bug', $_[1]); }
sub set_clone_bug { $_[0]->set('clone_bug', $_[1]); }
sub set_obsolete { $_[0]->set('obsolete', $_[1]); }
sub set_nullable { $_[0]->set('nullable', $_[1]); }
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
sub set_in_new_bugmail { $_[0]->set('mailhead', $_[1]); }
sub set_buglist { $_[0]->set('buglist', $_[1]); }

View File

@ -1,31 +1,10 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Initial Developer of the Original Code is NASA.
# Portions created by NASA are Copyright (C) 2006 San Jose State
# University Foundation. All Rights Reserved.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
# Greg Hendricks <ghendricks@novell.com>
# Vitaliy Filippov <vitalif@mail.ru>
# Class representing single value of a field
# Nearly 100% refactored
# Author(s): Vitaliy Filippov <vitalif@mail.ru>, Max Kanat-Alexander <mkanat@bugzilla.org>, Greg Hendricks <ghendricks@novell.com>
# License: Dual-license GPL 3.0+ or MPL 1.1+
use strict;
##############################################
# Class representing single value of a field #
##############################################
package Bugzilla::Field::Choice;
use base qw(Bugzilla::Object);
@ -139,9 +118,11 @@ EOC
# We just make new() enforce this, which should give developers
# the understanding that you can't use Bugzilla::Field::Choice
# without calling type().
sub new {
sub new
{
my $class = shift;
if ($class eq 'Bugzilla::Field::Choice') {
if ($class eq 'Bugzilla::Field::Choice')
{
ThrowCodeError('field_choice_must_use_type');
}
$class->SUPER::new(@_);
@ -164,7 +145,8 @@ sub create
return $self;
}
sub update {
sub update
{
my $self = shift;
my $dbh = Bugzilla->dbh;
my $fname = $self->field->name;
@ -172,7 +154,8 @@ sub update {
$dbh->bz_start_transaction();
my ($changes, $old_self) = $self->SUPER::update(@_);
if (exists $changes->{$self->NAME_FIELD}) {
if (exists $changes->{$self->NAME_FIELD})
{
my ($old, $new) = @{ $changes->{$self->NAME_FIELD} };
if ($self->field->type != FIELD_TYPE_MULTI_SELECT)
{
@ -185,8 +168,8 @@ sub update {
$dbh->do("UPDATE bugs SET $fname = ?, lastdiffed = NOW() WHERE $fname = ?",
undef, $new, $old);
}
if ($old_self->is_default) {
if ($old_self->is_default)
{
my $param = $self->DEFAULT_MAP->{$self->field->name};
SetParam($param, $self->name);
write_params();
@ -198,21 +181,30 @@ sub update {
return wantarray ? ($changes, $old_self) : $changes;
}
sub remove_from_db {
sub remove_from_db
{
my $self = shift;
if ($self->is_default) {
ThrowUserError('fieldvalue_is_default',
{ field => $self->field, value => $self,
param_name => $self->DEFAULT_MAP->{$self->field->name},
});
if ($self->is_default)
{
ThrowUserError('fieldvalue_is_default', {
field => $self->field,
value => $self,
param_name => $self->DEFAULT_MAP->{$self->field->name},
});
}
if ($self->is_static) {
ThrowUserError('fieldvalue_not_deletable',
{ field => $self->field, value => $self });
if ($self->is_static)
{
ThrowUserError('fieldvalue_not_deletable', {
field => $self->field,
value => $self,
});
}
if ($self->bug_count) {
ThrowUserError('fieldvalue_still_has_bugs',
{ field => $self->field, value => $self });
if ($self->bug_count)
{
ThrowUserError('fieldvalue_still_has_bugs', {
field => $self->field,
value => $self,
});
}
$self->_check_if_controller();
$self->set_visibility_values(undef);
@ -353,20 +345,20 @@ sub _check_if_controller
sub is_active { return $_[0]->{'isactive'}; }
sub sortkey { return $_[0]->{'sortkey'}; }
sub bug_count {
sub bug_count
{
my $self = shift;
return $self->{bug_count} if defined $self->{bug_count};
my $dbh = Bugzilla->dbh;
my $fname = $self->field->name;
my $count;
if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM bug_$fname
WHERE value_id = ?", undef, $self->id);
if ($self->field->type == FIELD_TYPE_MULTI_SELECT)
{
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM bug_$fname WHERE value_id = ?", undef, $self->id);
}
else {
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs
WHERE $fname = ?",
undef, $self->name);
else
{
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs WHERE $fname = ?", undef, $self->id);
}
$self->{bug_count} = $count;
return $count;
@ -378,7 +370,8 @@ sub field
return Bugzilla->get_field($invocant->FIELD_NAME);
}
sub is_default {
sub is_default
{
my $self = shift;
my $name = $self->DEFAULT_MAP->{$self->field->name};
# If it doesn't exist in DEFAULT_MAP, then there is no parameter
@ -389,14 +382,6 @@ sub is_default {
sub is_static
{
my $self = shift;
# If we need to special-case Resolution for *anything* else, it should
# get its own subclass.
if ($self->field->name eq 'resolution')
{
return grep($_ eq $self->name, ('', 'FIXED', 'MOVED', 'DUPLICATE'))
? 1 : 0;
}
return 0;
}
@ -529,14 +514,15 @@ sub set_visibility_values
my ($value_ids) = @_;
update_visibility_values($self->field, $self->id, $value_ids);
delete $self->{visibility_values};
return 1;
return $value_ids;
}
##############
# Validators #
##############
sub _check_value {
sub _check_value
{
my ($invocant, $value) = @_;
my $field = $invocant->field;
@ -544,11 +530,9 @@ sub _check_value {
$value = trim($value);
# Make sure people don't rename static values
if (blessed($invocant) && $value ne $invocant->name
&& $invocant->is_static)
if (blessed($invocant) && $value ne $invocant->name && $invocant->is_static)
{
ThrowUserError('fieldvalue_not_editable',
{ field => $field, old_value => $invocant });
ThrowUserError('fieldvalue_not_editable', { field => $field, old_value => $invocant });
}
ThrowUserError('fieldvalue_undefined') if !defined $value || $value eq "";
@ -556,24 +540,25 @@ sub _check_value {
if length($value) > MAX_FIELD_VALUE_SIZE;
my $exists = $invocant->type($field)->new({ name => $value });
if ($exists && (!blessed($invocant) || $invocant->id != $exists->id)) {
ThrowUserError('fieldvalue_already_exists',
{ field => $field, value => $exists });
if ($exists && (!blessed($invocant) || $invocant->id != $exists->id))
{
ThrowUserError('fieldvalue_already_exists', { field => $field, value => $exists });
}
return $value;
}
sub _check_sortkey {
sub _check_sortkey
{
my ($invocant, $value) = @_;
$value = trim($value);
return 0 if !$value;
# Store for the error message in case detaint_natural clears it.
my $orig_value = $value;
detaint_natural($value)
|| ThrowUserError('fieldvalue_sortkey_invalid',
{ sortkey => $orig_value,
field => $invocant->field });
detaint_natural($value) || ThrowUserError('fieldvalue_sortkey_invalid', {
sortkey => $orig_value,
field => $invocant->field,
});
return $value;
}

View File

@ -215,24 +215,27 @@ if ($action eq 'edit') {
#
# action='update' -> update the field value
#
if ($action eq 'update') {
if ($action eq 'update')
{
check_token_data($token, 'edit_field_value');
$vars->{'value_old'} = $value->name;
my $visibility_values;
if (!($value->is_static || $value->is_default)) {
$value->set_is_active($cgi->param('is_active'));
$value->set_name($cgi->param('value_new'));
$visibility_values = [ $cgi->param('visibility_value_id') ];
}
if ($value->can('set_timetracking')) {
$vars->{value_old} = $value->name;
if ($value->can('set_timetracking'))
{
$value->set_timetracking($cgi->param('timetracking') ? 1 : 0);
}
$value->set_sortkey($cgi->param('sortkey'));
$vars->{'changes'} = $value->update();
my $ch = $value->set_visibility_values($visibility_values);
$vars->{'changes'}->{'visibility_values'} = $ch if $visibility_values && $ch;
if (!($value->is_static || $value->is_default))
{
$value->set_is_active($cgi->param('is_active'));
$value->set_name($cgi->param('value_new'));
if ($value->field->value_field)
{
$vars->{changes}->{visibility_values} = $value->set_visibility_values([ $cgi->param('visibility_value_id') ]);
}
}
delete_token($token);
$vars->{'message'} = 'field_value_updated';
$vars->{changes} = $value->update;
$vars->{message} = 'field_value_updated';
display_field_values($vars);
}

View File

@ -401,11 +401,6 @@ $vars->{'product'} = $product;
$vars->{product_flag_types} = $types;
}
$vars->{'priority'} = Bugzilla->get_field('priority')->legal_value_names;
$vars->{'bug_severity'} = Bugzilla->get_field('bug_severity')->legal_value_names;
$vars->{'rep_platform'} = Bugzilla->get_field('rep_platform')->legal_value_names if Bugzilla->params->{useplatform};
$vars->{'op_sys'} = Bugzilla->get_field('op_sys')->legal_value_names if Bugzilla->params->{useopsys};
$vars->{'assigned_to'} = formvalue('assigned_to');
$vars->{'assigned_to_disabled'} = !$has_editbugs;
$vars->{'cc_disabled'} = 0;
@ -615,7 +610,6 @@ unless ($has_editbugs || $has_canconfirm) {
}
$vars->{bug_status} = \@status;
$vars->{resolution} = [ grep ($_, @{Bugzilla->get_field('resolution')->legal_value_names}) ];
# Get the default from a template value if it is legitimate.
# Otherwise, and only if the user has privs, set the default

View File

@ -424,6 +424,9 @@ sub install_update_fielddefs
# Bug 90854 - Тип поля "ссылка во внешнюю систему по ID"
$dbh->bz_add_column('fielddefs', url => {TYPE => 'VARCHAR(255)'});
# Nullable field property
$dbh->bz_add_column('fielddefs', nullable => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
# Bug 70605 - Кэширование зависимостей полей для поиска и формы бага на клиентской стороне
if (!$dbh->bz_column_info('fielddefs', 'delta_ts'))
{

View File

@ -397,8 +397,8 @@ function checkWorktime(inp)
[% IF Param('useopsys') %]
<tr>
[% INCLUDE bug/field.html.tmpl
bug = default, field = select_fields.op_sys, editable = 1,
value = default.op_sys %]
bug = default, field = select_fields.op_sys, editable = 1,
value = default.op_sys %]
</tr>
[% END %]
</tbody>
@ -406,8 +406,9 @@ function checkWorktime(inp)
<tbody class="expert_fields">
<tr>
[% IF Param('usetargetmilestone') && Param('letsubmitterchoosemilestone') %]
[% sel = { description => 'Target Milestone', name => 'target_milestone' } %]
[% INCLUDE select %]
[% INCLUDE bug/field.html.tmpl
bug = default, field = select_fields.target_milestone, editable = 1,
value = default.target_milestone %]
[% ELSE %]
<td colspan="2">&nbsp;</td>
[% END %]
@ -445,8 +446,7 @@ function checkWorktime(inp)
<tr>
[% IF bug_status.size <= 1 %]
<input type="hidden" name="bug_status"
value="[% default.bug_status FILTER html %]">
<input type="hidden" name="bug_status" value="[% default.bug_status FILTER html %]" />
<th>Initial State:</th>
<td>[% default.bug_status FILTER html %]</td>
[% ELSE %]
@ -461,15 +461,8 @@ function checkWorktime(inp)
</tr>
<tr id="resolution_container" style="display:none">
<th><a href="page.cgi?id=fields.html#resolution">Resolution</a>:</th>
<td>
<select name="resolution" id="resolution">
[%- FOREACH x = resolution %]
[% NEXT IF x == "MOVED" %]
<option value="[% x FILTER html %]">[% x FILTER html %]</option>
[% END %]
</select>
</td>
[% INCLUDE bug/field.html.tmpl
bug = default, field = select_fields.resolution, editable = 1 %]
</tr>
<tr><td colspan="4"><hr /></td></tr>

View File

@ -29,13 +29,13 @@
# allow_dont_change: display the --do_not_change-- option for select fields.
# value_span: A colspan for the table cell containing
# the field value.
# no_tds: boolean; if true, don't display the label <th> or the
# no_tds: boolean; if true, don't display the label <th> or the
# wrapping <td> for the field.
# desc_url: string; Normally the label of a non-custom field links to
# fields.html. If you want it to link elsewhere, specify the
# relative URL you want to link to, here. Remember to call
# url_quote on any query string arguments.
# bug: (optional) The current Bugzilla::Bug being displayed, or a hash
# bug: (optional) The current Bugzilla::Bug being displayed, or a hash
# with default field values being displayed on a page.
# tabindex: (optional) HTML tabindex.
#%]
@ -57,14 +57,10 @@
[%- '</a>' IF (!field.custom || desc_url) %]
[% '</label>' IF editable %]
</th>
[% END %]
[% IF NOT no_tds %]
<td class="field_value [% ' bz_hidden_field' IF hidden %]"
id="field_container_[% field.name FILTER html %]"
<td class="field_value [% ' bz_hidden_field' IF hidden %]"
id="field_container_[% field.name FILTER html %]"
[% " colspan=\"$value_span\"" FILTER none IF value_span %]>
[% END %]
[% Hook.process('start_field_column') %]
[% IF editable %]
[% SWITCH field.type %]
[% CASE constants.FIELD_TYPE_FREETEXT %]
@ -131,11 +127,11 @@
'[% field.name FILTER js %]',
"[% bug.${field.name} FILTER js %]");
</script>
[% CASE [ constants.FIELD_TYPE_SINGLE_SELECT
[% CASE [ constants.FIELD_TYPE_SINGLE_SELECT
constants.FIELD_TYPE_MULTI_SELECT ] %]
<select id="[% field.name FILTER html %]"
<select id="[% field.name FILTER html %]"
[% IF tabindex %] tabindex="[% tabindex FILTER html %]"[% END %]
name="[% field.name FILTER html %]"
name="[% field.name FILTER html %]"
[% IF field.type == constants.FIELD_TYPE_MULTI_SELECT %]
[% SET field_size = 5 %]
[% IF field.legal_values.size < 5 %]
@ -239,11 +235,10 @@
[% ELSIF field.type == constants.FIELD_TYPE_TEXTAREA %]
<div class="uneditable_textarea">[% value FILTER html FILTER wrap_comment %]</div>
[% ELSIF field.type == constants.FIELD_TYPE_BUG_ID %]
[% IF bug.${field.name} %]
[% bug.${field.name} FILTER bug_link(bug.${field.name}) FILTER none %]
[% END %]
[% IF bug.${field.name} %]
[% bug.${field.name} FILTER bug_link(bug.${field.name}) FILTER none %]
[% END %]
[% ELSE %]
[% value.join(', ') FILTER html %]
[% END %]
[% Hook.process('end_field_column') %]
[% '</td>' IF NOT no_tds %]

View File

@ -350,8 +350,10 @@
</li>
[% END %]
[% IF changes.sortkey %]
<li>Sortkey updated to
<em>[% changes.sortkey.1 | html %]</em>.</li>
<li>Sortkey updated to <em>[% changes.sortkey.1 | html %]</em>.</li>
[% END %]
[% IF changes.isactive %]
<li>It is now [% IF changes.isactive.1 %]enabled[% ELSE %]disabled[% END %] for selection.</li>
[% END %]
[% IF changes.visibility_values.defined %]
[% IF value.visibility_values.size > 0 %]