Commit c02ab7f8 authored by lpsolit%gmail.com's avatar lpsolit%gmail.com

Bug 313129: Implement $milestone->create and $milestone->update based on…

Bug 313129: Implement $milestone->create and $milestone->update based on Object.pm - Patch by Fré©ric Buclin <LpSolit@gmail.com> r/a=mkanat
parent 7d05707c
...@@ -142,7 +142,11 @@ use File::Basename; ...@@ -142,7 +142,11 @@ use File::Basename;
SAFE_PROTOCOLS SAFE_PROTOCOLS
MIN_SMALLINT
MAX_SMALLINT
MAX_LEN_QUERY_NAME MAX_LEN_QUERY_NAME
MAX_MILESTONE_SIZE
); );
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes); @Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
...@@ -397,9 +401,15 @@ use constant ROOT_USER => $^O =~ /MSWin32/i ? 'Administrator' : 'root'; ...@@ -397,9 +401,15 @@ use constant ROOT_USER => $^O =~ /MSWin32/i ? 'Administrator' : 'root';
# True if we're on Win32. # True if we're on Win32.
use constant ON_WINDOWS => ($^O =~ /MSWin32/i); use constant ON_WINDOWS => ($^O =~ /MSWin32/i);
use constant MIN_SMALLINT => -32768;
use constant MAX_SMALLINT => 32767;
# The longest that a saved search name can be. # The longest that a saved search name can be.
use constant MAX_LEN_QUERY_NAME => 64; use constant MAX_LEN_QUERY_NAME => 64;
# The longest milestone name allowed.
use constant MAX_MILESTONE_SIZE => 20;
sub bz_locations { sub bz_locations {
# We know that Bugzilla/Constants.pm must be in %INC at this point. # We know that Bugzilla/Constants.pm must be in %INC at this point.
# So the only question is, what's the name of the directory # So the only question is, what's the name of the directory
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
# #
# Contributor(s): Tiago R. Mello <timello@async.com.br> # Contributor(s): Tiago R. Mello <timello@async.com.br>
# Max Kanat-Alexander <mkanat@bugzilla.org> # Max Kanat-Alexander <mkanat@bugzilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
use strict; use strict;
...@@ -21,6 +22,7 @@ package Bugzilla::Milestone; ...@@ -21,6 +22,7 @@ package Bugzilla::Milestone;
use base qw(Bugzilla::Object); use base qw(Bugzilla::Object);
use Bugzilla::Constants;
use Bugzilla::Util; use Bugzilla::Util;
use Bugzilla::Error; use Bugzilla::Error;
...@@ -31,6 +33,8 @@ use Bugzilla::Error; ...@@ -31,6 +33,8 @@ use Bugzilla::Error;
use constant DEFAULT_SORTKEY => 0; use constant DEFAULT_SORTKEY => 0;
use constant DB_TABLE => 'milestones'; use constant DB_TABLE => 'milestones';
use constant NAME_FIELD => 'value';
use constant LIST_ORDER => 'sortkey, value';
use constant DB_COLUMNS => qw( use constant DB_COLUMNS => qw(
id id
...@@ -39,8 +43,26 @@ use constant DB_COLUMNS => qw( ...@@ -39,8 +43,26 @@ use constant DB_COLUMNS => qw(
sortkey sortkey
); );
use constant NAME_FIELD => 'value'; use constant REQUIRED_CREATE_FIELDS => qw(
use constant LIST_ORDER => 'sortkey, value'; name
product
);
use constant UPDATE_COLUMNS => qw(
value
sortkey
);
use constant VALIDATORS => {
product => \&_check_product,
sortkey => \&_check_sortkey,
};
use constant UPDATE_VALIDATORS => {
value => \&_check_value,
};
################################
sub new { sub new {
my $class = shift; my $class = shift;
...@@ -71,6 +93,118 @@ sub new { ...@@ -71,6 +93,118 @@ sub new {
return $class->SUPER::new(@_); return $class->SUPER::new(@_);
} }
sub run_create_validators {
my $class = shift;
my $params = $class->SUPER::run_create_validators(@_);
my $product = delete $params->{product};
$params->{product_id} = $product->id;
$params->{value} = $class->_check_value($params->{name}, $product);
delete $params->{name};
return $params;
}
sub update {
my $self = shift;
my $changes = $self->SUPER::update(@_);
if (exists $changes->{value}) {
my $dbh = Bugzilla->dbh;
# The milestone value is stored in the bugs table instead of its ID.
$dbh->do('UPDATE bugs SET target_milestone = ?
WHERE target_milestone = ? AND product_id = ?',
undef, ($self->name, $changes->{value}->[0], $self->product_id));
# The default milestone also stores the value instead of the ID.
$dbh->do('UPDATE products SET defaultmilestone = ?
WHERE id = ? AND defaultmilestone = ?',
undef, ($self->name, $self->product_id, $changes->{value}->[0]));
}
return $changes;
}
sub remove_from_db {
my $self = shift;
my $dbh = Bugzilla->dbh;
# The default milestone cannot be deleted.
if ($self->name eq $self->product->default_milestone) {
ThrowUserError('milestone_is_default', { milestone => $self });
}
if ($self->bug_count) {
# We don't want to delete bugs when deleting a milestone.
# Bugs concerned are reassigned to the default milestone.
my $bug_ids =
$dbh->selectcol_arrayref('SELECT bug_id FROM bugs
WHERE product_id = ? AND target_milestone = ?',
undef, ($self->product->id, $self->name));
my $timestamp = $dbh->selectrow_array('SELECT NOW()');
$dbh->do('UPDATE bugs SET target_milestone = ?, delta_ts = ?
WHERE bug_id IN (' . join(', ', @$bug_ids) . ')',
undef, ($self->product->default_milestone, $timestamp));
require Bugzilla::Bug;
import Bugzilla::Bug qw(LogActivityEntry);
foreach my $bug_id (@$bug_ids) {
LogActivityEntry($bug_id, 'target_milestone',
$self->name,
$self->product->default_milestone,
Bugzilla->user->id, $timestamp);
}
}
$dbh->do('DELETE FROM milestones WHERE id = ?', undef, $self->id);
}
################################
# Validators
################################
sub _check_value {
my ($invocant, $name, $product) = @_;
trim($name) || ThrowUserError('milestone_blank_name');
if (length($name) > MAX_MILESTONE_SIZE) {
ThrowUserError('milestone_name_too_long', {name => $name});
}
$product = $invocant->product if (ref $invocant);
my $milestone = new Bugzilla::Milestone({product => $product, name => $name});
if ($milestone && (!ref $invocant || $milestone->id != $invocant->id)) {
ThrowUserError('milestone_already_exists', { name => $milestone->name,
product => $product->name });
}
return $name;
}
sub _check_sortkey {
my ($invocant, $sortkey) = @_;
# Keep a copy in case detaint_signed() clears the sortkey
my $stored_sortkey = $sortkey;
if (!detaint_signed($sortkey) || $sortkey < MIN_SMALLINT || $sortkey > MAX_SMALLINT) {
ThrowUserError('milestone_sortkey_invalid', {sortkey => $stored_sortkey});
}
return $sortkey;
}
sub _check_product {
my ($invocant, $product) = @_;
return Bugzilla->user->check_can_admin_product($product->name);
}
################################
# Methods
################################
sub set_name { $_[0]->set('value', $_[1]); }
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
sub bug_count { sub bug_count {
my $self = shift; my $self = shift;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
...@@ -92,22 +226,12 @@ sub name { return $_[0]->{'value'}; } ...@@ -92,22 +226,12 @@ sub name { return $_[0]->{'value'}; }
sub product_id { return $_[0]->{'product_id'}; } sub product_id { return $_[0]->{'product_id'}; }
sub sortkey { return $_[0]->{'sortkey'}; } sub sortkey { return $_[0]->{'sortkey'}; }
################################ sub product {
##### Subroutines ##### my $self = shift;
################################
sub check_sort_key {
my ($milestone_name, $sortkey) = @_;
# Keep a copy in case detaint_signed() clears the sortkey
my $stored_sortkey = $sortkey;
if (!detaint_signed($sortkey) || $sortkey < -32768 require Bugzilla::Product;
|| $sortkey > 32767) { $self->{'product'} ||= new Bugzilla::Product($self->product_id);
ThrowUserError('milestone_sortkey_invalid', return $self->{'product'};
{'name' => $milestone_name,
'sortkey' => $stored_sortkey});
}
return $sortkey;
} }
1; 1;
...@@ -122,13 +246,21 @@ Bugzilla::Milestone - Bugzilla product milestone class. ...@@ -122,13 +246,21 @@ Bugzilla::Milestone - Bugzilla product milestone class.
use Bugzilla::Milestone; use Bugzilla::Milestone;
my $milestone = new Bugzilla::Milestone( my $milestone = new Bugzilla::Milestone({ name => $name, product => $product });
{ product => $product, name => 'milestone_value' });
my $name = $milestone->name;
my $product_id = $milestone->product_id; my $product_id = $milestone->product_id;
my $value = $milestone->value; my $product = $milestone->product;
my $sortkey = $milestone->sortkey;
my $milestone = Bugzilla::Milestone->create(
{ name => $name, product => $product, sortkey => $sortkey });
my $milestone = $hash_ref->{'milestone_value'}; $milestone->set_name($new_name);
$milestone->set_sortkey($new_sortkey);
$milestone->update();
$milestone->remove_from_db;
=head1 DESCRIPTION =head1 DESCRIPTION
...@@ -138,16 +270,48 @@ Milestone.pm represents a Product Milestone object. ...@@ -138,16 +270,48 @@ Milestone.pm represents a Product Milestone object.
=over =over
=item C<new($product_id, $value)> =item C<new({name => $name, product => $product})>
Description: The constructor is used to load an existing milestone Description: The constructor is used to load an existing milestone
by passing a product id and a milestone value. by passing a product object and a milestone name.
Params: $product_id - Integer with a Bugzilla product id. Params: $product - a Bugzilla::Product object.
$value - String with a milestone value. $name - the name of a milestone (string).
Returns: A Bugzilla::Milestone object. Returns: A Bugzilla::Milestone object.
=item C<name()>
Description: Name (value) of the milestone.
Params: none.
Returns: The name of the milestone.
=item C<product_id()>
Description: ID of the product the milestone belongs to.
Params: none.
Returns: The ID of a product.
=item C<product()>
Description: The product object of the product the milestone belongs to.
Params: none.
Returns: A Bugzilla::Product object.
=item C<sortkey()>
Description: Sortkey of the milestone.
Params: none.
Returns: The sortkey of the milestone.
=item C<bug_count()> =item C<bug_count()>
Description: Returns the total of bugs that belong to the milestone. Description: Returns the total of bugs that belong to the milestone.
...@@ -156,4 +320,55 @@ Milestone.pm represents a Product Milestone object. ...@@ -156,4 +320,55 @@ Milestone.pm represents a Product Milestone object.
Returns: Integer with the number of bugs. Returns: Integer with the number of bugs.
=item C<set_name($new_name)>
Description: Changes the name of the milestone.
Params: $new_name - new name of the milestone (string). This name
must be unique within the product.
Returns: Nothing.
=item C<set_sortkey($new_sortkey)>
Description: Changes the sortkey of the milestone.
Params: $new_sortkey - new sortkey of the milestone (signed integer).
Returns: Nothing.
=item C<update()>
Description: Writes the new name and/or the new sortkey into the DB.
Params: none.
Returns: A hashref with changes made to the milestone object.
=item C<remove_from_db()>
Description: Deletes the current milestone from the DB. The object itself
is not destroyed.
Params: none.
Returns: Nothing.
=back
=head1 CLASS METHODS
=over
=item C<create({name => $name, product => $product, sortkey => $sortkey})>
Description: Create a new milestone for the given product.
Params: $name - name of the new milestone (string). This name
must be unique within the product.
$product - a Bugzilla::Product object.
$sortkey - the sortkey of the new milestone (signed integer)
Returns: A Bugzilla::Milestone object.
=back =back
...@@ -215,6 +215,7 @@ sub set { ...@@ -215,6 +215,7 @@ sub set {
if (exists $validators{$field}) { if (exists $validators{$field}) {
my $validator = $validators{$field}; my $validator = $validators{$field};
$value = $self->$validator($value, $field); $value = $self->$validator($value, $field);
trick_taint($value) if (defined $value && !ref($value));
if ($self->can('_set_global_validator')) { if ($self->can('_set_global_validator')) {
$self->_set_global_validator($value, $field); $self->_set_global_validator($value, $field);
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
# Frédéric Buclin <LpSolit@gmail.com> # Frédéric Buclin <LpSolit@gmail.com>
# #
use strict; use strict;
use lib "."; use lib ".";
...@@ -24,7 +23,6 @@ use Bugzilla::Constants; ...@@ -24,7 +23,6 @@ use Bugzilla::Constants;
use Bugzilla::Util; use Bugzilla::Util;
use Bugzilla::Error; use Bugzilla::Error;
use Bugzilla::Milestone; use Bugzilla::Milestone;
use Bugzilla::Bug;
use Bugzilla::Token; use Bugzilla::Token;
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
...@@ -37,7 +35,6 @@ my $vars = {}; ...@@ -37,7 +35,6 @@ my $vars = {};
# #
my $user = Bugzilla->login(LOGIN_REQUIRED); my $user = Bugzilla->login(LOGIN_REQUIRED);
my $whoid = $user->id;
print $cgi->header(); print $cgi->header();
...@@ -86,16 +83,11 @@ unless ($action) { ...@@ -86,16 +83,11 @@ unless ($action) {
$vars->{'showbugcounts'} = $showbugcounts; $vars->{'showbugcounts'} = $showbugcounts;
$vars->{'product'} = $product; $vars->{'product'} = $product;
$template->process("admin/milestones/list.html.tmpl", $template->process("admin/milestones/list.html.tmpl", $vars)
$vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit; exit;
} }
# #
# action='add' -> present form for parameters for new milestone # action='add' -> present form for parameters for new milestone
# #
...@@ -105,62 +97,29 @@ unless ($action) { ...@@ -105,62 +97,29 @@ unless ($action) {
if ($action eq 'add') { if ($action eq 'add') {
$vars->{'token'} = issue_session_token('add_milestone'); $vars->{'token'} = issue_session_token('add_milestone');
$vars->{'product'} = $product; $vars->{'product'} = $product;
$template->process("admin/milestones/create.html.tmpl", $template->process("admin/milestones/create.html.tmpl", $vars)
$vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit; exit;
} }
# #
# action='new' -> add milestone entered in the 'action=add' screen # action='new' -> add milestone entered in the 'action=add' screen
# #
if ($action eq 'new') { if ($action eq 'new') {
check_token_data($token, 'add_milestone'); check_token_data($token, 'add_milestone');
$milestone_name || ThrowUserError('milestone_blank_name'); my $milestone = Bugzilla::Milestone->create({ name => $milestone_name,
product => $product,
if (length($milestone_name) > 20) { sortkey => $sortkey });
ThrowUserError('milestone_name_too_long',
{'name' => $milestone_name});
}
$sortkey = Bugzilla::Milestone::check_sort_key($milestone_name,
$sortkey);
my $milestone = new Bugzilla::Milestone(
{ product => $product, name => $milestone_name });
if ($milestone) {
ThrowUserError('milestone_already_exists',
{'name' => $milestone->name,
'product' => $product->name});
}
# Add the new milestone
trick_taint($milestone_name);
$dbh->do('INSERT INTO milestones ( value, product_id, sortkey )
VALUES ( ?, ?, ? )',
undef, $milestone_name, $product->id, $sortkey);
$milestone = new Bugzilla::Milestone(
{ product => $product, name => $milestone_name });
delete_token($token); delete_token($token);
$vars->{'milestone'} = $milestone; $vars->{'milestone'} = $milestone;
$vars->{'product'} = $product; $vars->{'product'} = $product;
$template->process("admin/milestones/created.html.tmpl", $template->process("admin/milestones/created.html.tmpl", $vars)
$vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit; exit;
} }
# #
# action='del' -> ask if user really wants to delete # action='del' -> ask if user really wants to delete
# #
...@@ -176,7 +135,7 @@ if ($action eq 'del') { ...@@ -176,7 +135,7 @@ if ($action eq 'del') {
# The default milestone cannot be deleted. # The default milestone cannot be deleted.
if ($product->default_milestone eq $milestone->name) { if ($product->default_milestone eq $milestone->name) {
ThrowUserError("milestone_is_default", $vars); ThrowUserError("milestone_is_default", { milestone => $milestone });
} }
$vars->{'token'} = issue_session_token('delete_milestone'); $vars->{'token'} = issue_session_token('delete_milestone');
...@@ -185,8 +144,6 @@ if ($action eq 'del') { ...@@ -185,8 +144,6 @@ if ($action eq 'del') {
exit; exit;
} }
# #
# action='delete' -> really delete the milestone # action='delete' -> really delete the milestone
# #
...@@ -195,47 +152,17 @@ if ($action eq 'delete') { ...@@ -195,47 +152,17 @@ if ($action eq 'delete') {
check_token_data($token, 'delete_milestone'); check_token_data($token, 'delete_milestone');
my $milestone = Bugzilla::Milestone->check({ product => $product, my $milestone = Bugzilla::Milestone->check({ product => $product,
name => $milestone_name }); name => $milestone_name });
$milestone->remove_from_db;
delete_token($token);
$vars->{'milestone'} = $milestone; $vars->{'milestone'} = $milestone;
$vars->{'product'} = $product; $vars->{'product'} = $product;
# The default milestone cannot be deleted.
if ($milestone->name eq $product->default_milestone) {
ThrowUserError("milestone_is_default", $vars);
}
if ($milestone->bug_count) {
# We don't want to delete bugs when deleting a milestone.
# Bugs concerned are reassigned to the default milestone.
my $bug_ids =
$dbh->selectcol_arrayref("SELECT bug_id FROM bugs
WHERE product_id = ? AND target_milestone = ?",
undef, ($product->id, $milestone->name));
my $timestamp = $dbh->selectrow_array("SELECT NOW()");
foreach my $bug_id (@$bug_ids) {
$dbh->do("UPDATE bugs SET target_milestone = ?,
delta_ts = ? WHERE bug_id = ?",
undef, ($product->default_milestone, $timestamp,
$bug_id));
# We have to update the 'bugs_activity' table too.
LogActivityEntry($bug_id, 'target_milestone',
$milestone->name,
$product->default_milestone,
$whoid, $timestamp);
}
}
$dbh->do("DELETE FROM milestones WHERE product_id = ? AND value = ?",
undef, ($product->id, $milestone->name));
delete_token($token);
$template->process("admin/milestones/deleted.html.tmpl", $vars) $template->process("admin/milestones/deleted.html.tmpl", $vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit; exit;
} }
# #
# action='edit' -> present the edit milestone form # action='edit' -> present the edit milestone form
# #
...@@ -251,15 +178,11 @@ if ($action eq 'edit') { ...@@ -251,15 +178,11 @@ if ($action eq 'edit') {
$vars->{'product'} = $product; $vars->{'product'} = $product;
$vars->{'token'} = issue_session_token('edit_milestone'); $vars->{'token'} = issue_session_token('edit_milestone');
$template->process("admin/milestones/edit.html.tmpl", $template->process("admin/milestones/edit.html.tmpl", $vars)
$vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit; exit;
} }
# #
# action='update' -> update the milestone # action='update' -> update the milestone
# #
...@@ -267,93 +190,23 @@ if ($action eq 'edit') { ...@@ -267,93 +190,23 @@ if ($action eq 'edit') {
if ($action eq 'update') { if ($action eq 'update') {
check_token_data($token, 'edit_milestone'); check_token_data($token, 'edit_milestone');
my $milestone_old_name = trim($cgi->param('milestoneold') || ''); my $milestone_old_name = trim($cgi->param('milestoneold') || '');
my $milestone_old = Bugzilla::Milestone->check( my $milestone = Bugzilla::Milestone->check({ product => $product,
{ product => $product, name => $milestone_old_name }); name => $milestone_old_name });
if (length($milestone_name) > 20) {
ThrowUserError('milestone_name_too_long',
{'name' => $milestone_name});
}
$dbh->bz_lock_tables('bugs WRITE',
'milestones WRITE',
'products WRITE');
if ($sortkey ne $milestone_old->sortkey) {
$sortkey = Bugzilla::Milestone::check_sort_key($milestone_name,
$sortkey);
$dbh->do('UPDATE milestones SET sortkey = ?
WHERE product_id = ?
AND value = ?',
undef,
$sortkey,
$product->id,
$milestone_old->name);
$vars->{'updated_sortkey'} = 1;
}
if ($milestone_name ne $milestone_old->name) {
unless ($milestone_name) {
ThrowUserError('milestone_blank_name');
}
my $milestone = new Bugzilla::Milestone(
{ product => $product, name => $milestone_name });
if ($milestone) {
ThrowUserError('milestone_already_exists',
{'name' => $milestone->name,
'product' => $product->name});
}
trick_taint($milestone_name);
$dbh->do('UPDATE bugs
SET target_milestone = ?
WHERE target_milestone = ?
AND product_id = ?',
undef,
$milestone_name,
$milestone_old->name,
$product->id);
$dbh->do("UPDATE milestones
SET value = ?
WHERE product_id = ?
AND value = ?",
undef,
$milestone_name,
$product->id,
$milestone_old->name);
$dbh->do("UPDATE products
SET defaultmilestone = ?
WHERE id = ?
AND defaultmilestone = ?",
undef,
$milestone_name,
$product->id,
$milestone_old->name);
$vars->{'updated_name'} = 1;
}
$dbh->bz_unlock_tables(); $milestone->set_name($milestone_name);
$milestone->set_sortkey($sortkey);
my $changes = $milestone->update();
my $milestone = Bugzilla::Milestone->check({ product => $product,
name => $milestone_name });
delete_token($token); delete_token($token);
$vars->{'milestone'} = $milestone; $vars->{'milestone'} = $milestone;
$vars->{'product'} = $product; $vars->{'product'} = $product;
$template->process("admin/milestones/updated.html.tmpl", $vars->{'changes'} = $changes;
$vars) $template->process("admin/milestones/updated.html.tmpl", $vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit; exit;
} }
# #
# No valid action found # No valid action found
# #
......
...@@ -19,11 +19,10 @@ ...@@ -19,11 +19,10 @@
#%] #%]
[%# INTERFACE: [%# INTERFACE:
# milestone: object; the milestone being edited.
# product: object; Bugzilla::Product object representing the product to # product: object; Bugzilla::Product object representing the product to
# which the milestone belongs. # which the milestone belongs.
# # changes: hashref; contains changes made to the milestone.
# 'updated_XXX' variables are booleans, and are defined if the
# 'XXX' field was updated during the edit just being handled.
#%] #%]
[% title = BLOCK %]Updating Milestone '[% milestone.name FILTER html %]' of Product [% title = BLOCK %]Updating Milestone '[% milestone.name FILTER html %]' of Product
...@@ -32,15 +31,15 @@ ...@@ -32,15 +31,15 @@
title = title title = title
%] %]
[% IF updated_name %] [% IF changes.value.defined %]
<p>Updated Milestone name to: '[% milestone.name FILTER html %]'.</p> <p>Updated Milestone name to: '[% milestone.name FILTER html %]'.</p>
[% END %] [% END %]
[% IF updated_sortkey %] [% IF changes.sortkey.defined %]
<p>Updated Milestone sortkey to: '[% milestone.sortkey FILTER html %]'.</p> <p>Updated Milestone sortkey to: '[% milestone.sortkey FILTER html %]'.</p>
[% END %] [% END %]
[% UNLESS updated_sortkey || updated_name %] [% UNLESS changes.value.defined || changes.sortkey.defined %]
<p>Nothing changed for milestone '[% milestone.name FILTER html %]'.</p> <p>Nothing changed for milestone '[% milestone.name FILTER html %]'.</p>
[% END %] [% END %]
......
...@@ -895,9 +895,9 @@ ...@@ -895,9 +895,9 @@
[% title = "Default milestone not deletable" %] [% title = "Default milestone not deletable" %]
[% admindocslinks = {'products.html' => 'Administering products', [% admindocslinks = {'products.html' => 'Administering products',
'milestones.html' => 'About Milestones'} %] 'milestones.html' => 'About Milestones'} %]
Sorry, but [% milestone.name FILTER html %] is the default milestone Sorry, but [% milestone.name FILTER html %] is the default milestone
for product '[% product.name FILTER html %]', and so it can not be for the '[% milestone.product.name FILTER html %]' product, and so
deleted. it cannot be deleted.
[% ELSIF error == "milestone_name_too_long" %] [% ELSIF error == "milestone_name_too_long" %]
[% title = "Milestone Name Is Too Long" %] [% title = "Milestone Name Is Too Long" %]
...@@ -913,9 +913,9 @@ ...@@ -913,9 +913,9 @@
[% ELSIF error == "milestone_sortkey_invalid" %] [% ELSIF error == "milestone_sortkey_invalid" %]
[% title = "Invalid Milestone Sortkey" %] [% title = "Invalid Milestone Sortkey" %]
The sortkey '[% sortkey FILTER html %]' for milestone ' The sortkey '[% sortkey FILTER html %]' is not in the range
[% name FILTER html %]' is not in the range -32768 &le; sortkey [%+ constants.MIN_SMALLINT FILTER html %] &le; sortkey &le;
&le; 32767. [%+ constants.MAX_SMALLINT FILTER html %].
[% ELSIF error == "misarranged_dates" %] [% ELSIF error == "misarranged_dates" %]
[% title = "Misarranged Dates" %] [% title = "Misarranged Dates" %]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment