Parent Directory
|
Revision Log
This commit was manufactured by cvs2svn to create branch 'rel-2-1-patches'.
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader: webwork2/lib/WeBWorK/DBv3.pm,v 1.2 2004/11/25 05:50:01 sh002i Exp $ 5 # 6 # This program is free software; you can redistribute it and/or modify it under 7 # the terms of either: (a) the GNU General Public License as published by the 8 # Free Software Foundation; either version 2, or (at your option) any later 9 # version, or (b) the "Artistic License" which comes with this package. 10 # 11 # This program is distributed in the hope that it will be useful, but WITHOUT 12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the 14 # Artistic License for more details. 15 ################################################################################ 16 17 package WeBWorK::DBv3; 18 use base 'Class::DBI'; 19 use WeBWorK::DBv3::NormalizerMixin; 20 use Class::DBI::Plugin::AbstractCount; 21 22 =head1 NAME 23 24 WeBWorK::DBv3 - Class::DBI interface to WWDBv3. 25 26 =head1 SYNOPSIS 27 28 use WeBWorK::DBv3; 29 30 my $wwdbv3_settings = { 31 dsn => "dbi:mysql:wwdbv3", 32 user => "wwdbv3", 33 pass => "SeCrEt", 34 attr => { }, # optional 35 upgrade_lock => "/path/to/wwdbv3_upgrade.lock", # prevent concurrent schema upgrades 36 }; 37 38 WeBWorK::DBv3::init($wwdbv3_settings); 39 40 # --- any time after init() as been called... --- 41 42 my $course = WeBWorK::DBv3::Course->find({name => "Sam's course"}) 43 or die "course not found!"; 44 45 my @participants = $course->participants; 46 47 my $participant = WeBWorK::DBv3::Participant->find({login_name => "sam.hathaway"}); 48 $participant->first_name("Sam"); 49 $participant->last_name("Hathaway"); 50 $participant->update; 51 52 my @set_assignments = $participant->set_assignments; 53 54 =head1 DESCRIPTION 55 56 WeBWorK::DBv3 provides a Class::DBI-based interface to the third-generation 57 WeBWorK database. (WeBWorK::DB provided an interface to the second-generation 58 database, the first-generation database was that used by WeBWorK 1.x.) 59 60 The database schema is described at 61 <http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/DatabaseSchemaV3>. 62 63 WeBWorK::DBv3 supports automatic schema upgrades by checking the value of the 64 C<db_version> record in the C<setting> table and applying SQL deltas to the 65 database. 66 67 =cut 68 69 use strict; 70 use warnings; 71 use vars qw/$dbh $dt_format/; 72 use Data::Dumper; 73 use DateTime::Format::DBI; 74 use WeBWorK::DBv3::Utils; 75 use WeBWorK::Utils qw/fisher_yates_shuffle/; 76 77 =head1 INITIALIZATION 78 79 The init($wwdbv3_settings) function allows the user to set up the details of the 80 database connection at runtime rather than at compile time. This lets us use 81 values from the WeBWorK::CourseEnvironment (loaded at runtime) in specifying the 82 database connection. 83 84 $wwdbv3_settings is a reference to a hash containing the following values: 85 86 dsn => The DBI data source, e.g. "dbi:mysql:wwdbv3" 87 user => The user name with which to connect. 88 pass => The password to supply to connect. 89 attr => A reference to a hash containing DBI attributes. 90 See L<DBI> for more information. 91 upgrade_lock => Path to a file which is flock()'d while performing 92 database upgrades. 93 94 =cut 95 96 sub init { 97 my ($wwdbv3_settings) = @_; 98 99 my $dsn = $wwdbv3_settings->{dsn}; 100 my $user = $wwdbv3_settings->{user}; 101 my $pass = $wwdbv3_settings->{pass}; 102 my %attr = ( 103 RootClass => "DBIx::ContextualFetch", # this is supposedly important to Class::DBI 104 RaiseError => 1, # we don't want to have to test return values 105 %{ $wwdbv3_settings->{attr} }, # allow user-specified attributes to override 106 ); 107 108 $dbh = DBI->connect_cached($dsn, $user, $pass, \%attr); 109 110 $dt_format = new DateTime::Format::DBI($dbh); 111 112 my $lockfile = $wwdbv3_settings->{upgrade_lock}; 113 upgrade_schema($dbh, $lockfile); 114 } 115 116 # override db_Main to get database handle initialized in init() above. note that 117 # Class::DBI->connection() is never called. 118 sub db_Main { 119 return $dbh; 120 } 121 122 ################################################################################ 123 124 =head1 PUBLIC CLASS::DBI EXTENSIONS 125 126 WeBWorK::DBv3 extends Class::DBI to provide several features useful to users to 127 the WWDBv3 system. 128 129 =head2 Table locking 130 131 When using a table type that doesn't support transactions, we need to be able to 132 do table-level locks. The currently implementations are from 133 Class::DBI::Extension and are MySQL-specific. 134 135 =over 136 137 =item lock_table() 138 139 Write-lock the current table. 140 141 =cut 142 143 __PACKAGE__->set_sql(LockTable => "LOCK TABLES %s WRITE"); 144 145 sub lock_table { 146 my $class = shift; 147 $class->sql_LockTable($class->table)->execute; 148 } 149 150 =item unlock_table() 151 152 Unlock I<all> locked tables. 153 154 =cut 155 156 __PACKAGE__->set_sql(UnlockTable => "UNLOCK TABLES"); 157 158 sub unlock_table { 159 my $class = shift; 160 $class->sql_UnlockTable->execute; 161 } 162 163 =back 164 165 =cut 166 167 ################################################################################ 168 169 =head1 INTERNAL IMPROVEMENTS 170 171 WeBWorK::DBv3 extends Class::DBI to provide several features useful in the 172 definition of table classes. 173 174 =head2 DateTime support 175 176 The method has_a_datetime() has been defined as a shortcut to specifying that a 177 DATETIME column should be inflated to and deflated from a DateTime.pm object. 178 179 __PACKAGE__->has_a_datetime("open_date"); 180 181 =cut 182 183 sub _datetime_inflate { 184 my $dt = $dt_format->parse_datetime($_[0]) or _croak("invalid date: '$_[0]'"); 185 return $dt->set_time_zone("UTC"); 186 } 187 188 sub _datetime_deflate { 189 my $dt = $_[0]->clone->set_time_zone("UTC"); # clone to avoid changing timezone of original object 190 return $dt_format->format_datetime($dt); 191 } 192 193 # this declares a column to be of type DateTime and defines inflation/deflation 194 # subroutines for it 195 sub has_a_datetime { 196 my ($class, $field) = @_; 197 return unless $field; 198 199 $class->has_a( 200 $field => "DateTime", 201 inflate => \&_datetime_inflate, 202 deflate => \&_datetime_deflate, 203 ); 204 } 205 206 =head2 Per-column normalization support 207 208 WeBWorK::DBv3 adds per-column normalization support to Class::DBI via 209 WeBWorK::DBv3::NormalizerMixin. The interface and implementation are similar to 210 that of Class::DBI triggers (via Class::Trigger). 211 212 To add a normalizer to a field: 213 214 __PACKAGE__->add_normalizer(field => \&normalizer_sub); 215 216 &normalizer_sub takes one argument, the value to be normalized. It should return 217 the normalized value. For example: 218 219 sub _bool_normalizer { $_[0] ? 1 : 0 } 220 WeBWorK::DBv3::Course->add_normalizer(visible => \&_bool_normalizer); 221 222 Like triggers, multiple normalizers can be added for a single field. However, 223 you cannot specify the order in which they will be run. 224 225 =cut 226 227 sub normalize_column_values { 228 my ($self, $column_values) = @_; 229 230 my @errors; 231 232 foreach my $column (keys %$column_values) { 233 #warn "callig normalizers for column '$column'.\n"; 234 eval { $self->call_normalizer($column_values, $column) }; 235 push @errors, $column => $@ if $@; 236 } 237 238 return unless @errors; 239 $self->_croak( 240 "normalize_column_values error: " . join(" ", @errors), 241 method => "normalize_column_values", 242 data => { @errors }, 243 ); 244 } 245 246 =head3 Predefined normalizers 247 248 Several normalizers are conveniently predefined using a syntax similar to that 249 of has_a() relationship declarations. 250 251 =over 252 253 =item has_a_boolean($field) 254 255 True values will be normalized to C<1>, false values to C<0>. 256 257 =cut 258 259 sub _bool_normalizer { $_[0] ? 1 : 0 } 260 sub has_a_boolean { 261 my ($class, $field) = @_; 262 return unless $field; 263 264 $class->add_normalizer($field => \&_bool_normalizer); 265 } 266 267 =back 268 269 =head2 set_creation_date() 270 271 The subroutine set_creation_date() is available for use in setting the 272 C<creation_date> in newly created records using a C<before_create> trigger. 273 274 __PACKAGE__->add_trigger(before_create => \&WeBWorK::DBv3::set_creation_date); 275 276 set_creation_date() uses the DateTime->now() constructor. 277 278 set_creation_date() should probably not be used when defining table classes or 279 in client code. Instead, only refer to it when adding triggers. 280 281 =cut 282 283 sub set_creation_date { 284 $_[0]->creation_date(DateTime->now); 285 } 286 287 #=head2 Macros for uniqueness constraints 288 # 289 #has_unique_columns() allows you do define uniqueness constraints by listing the 290 #fields that must be unique: 291 # 292 # __PACKAGE__->has_unique_columns($name => qw/field1 field2 field3/); 293 # 294 #$name gives a name to this constraint, which is included in the error message 295 #given when the conditions of the constraint are not met. 296 # 297 #=cut 298 # 299 ## FIXME this is broken -- it doesn't allow multiple NULL values! I'd rather just 300 ## catch the DBI uniqneness violation errors and munge them in some way to get a 301 ## useful error message out. Is there some way to do that? Would it me MySQL 302 ## specific? 303 ## 304 ## <NULL> and <NULL> - OK 305 ## <NULL, foo> and <NULL, foo> - not OK 306 # 307 #sub has_unique_columns { 308 # my ($class, $name, @columns) = @_; 309 # 310 # $class->_invalid_object_method('has_unique_columns()') if ref $class; 311 # $name or $class->_croak("has_unique_columns needs a name"); 312 # 313 # foreach my $column (@columns) { 314 # # normalize columns, and croak on any invalid columns 315 # my $normalized_column = $class->find_column($column) 316 # or $class->_croak("has_unique_columns: '$column' is not a valid column"); 317 # $column = $normalized_column; 318 # } 319 # 320 # # closure over @columns, $name 321 # my $unique_columns = sub { 322 # my ($self) = @_; 323 # my %search_spec = map { $_ => $self->$_ } @columns; 324 # $search_spec{id} = { '!=', $self->id }; 325 # unless ($self->count_search_where(%search_spec) == 0) { 326 # my $columns = join(",", @columns); 327 # my $values = join(",", map "'$search_spec{$_}'", @columns); 328 # my $fail = @columns == 1 ? "fails" : "fail"; 329 # return $self->_croak("$class ($columns) $fail uniqueness constraint '$name' with ($values)"); 330 # } 331 # }; 332 # 333 # $class->add_trigger(before_create => $unique_columns); 334 # $class->add_trigger(before_update => $unique_columns); 335 #} 336 337 =head2 Comma-separated list handling 338 339 has_a_list() allows you to define a column as containing a comma-separated list 340 of values. It will add an accessor/modifier to the invocant's class with the 341 suffix C<_list>. 342 343 Note that this handling is pretty dumb, and cannot deal with embedded commas. 344 This is typically OK, since list fields are usually used to store list of record 345 IDs or strings that are valid identifiers. (If you really need to store strings 346 with embedded commas, you may URL-encode them or whatever you like. Just make 347 sure you decode them on the way out!) 348 349 __PACKAGE__->has_a_list("problem_order"); 350 351 # results in this method being added to __PACKAGE__ 352 sub problem_order_list { 353 my ($self, @list) = @_; 354 if (@list) { 355 return $self->problem_order(join(",", @list)); 356 } else { 357 return split(",", $self->problem_order); 358 } 359 } 360 361 =cut 362 363 sub has_a_list { 364 my ($class, $field) = @_; 365 return unless $field; 366 367 # closure over $field 368 my $cs_list = sub { 369 my ($self, @list) = @_; 370 if (@list) { 371 return $self->$field(join(",", @list)); 372 } else { 373 return split(",", $self->$field); 374 } 375 }; 376 377 my $method_name = "${class}::${field}_list"; 378 379 no strict 'refs'; 380 *$method_name = $cs_list; 381 } 382 383 =head2 Ordered has_many() relationships 384 385 has_ordering() allows you to impose a constant ordering on records returned by a 386 has_many() accessor. The ordering can be specified by a cs_list field accessor 387 (i.e. C<problem_order_list>). This accessor must contain a list of IDs that can 388 be used to order the 389 390 __PACKAGE__->has_many(abstract_problems => "WeBWorK::DBv3::AbstractProblem"); 391 __PACKAGE__->has_a_list("problem_order"); 392 __PACKAGE__->has_explicit_order(abstract_problems => "problem_order_list"); 393 394 The above example will add an accessor ordered_abstract_problems() to the class 395 that uses the method problem_order_list() to order the records returned by the 396 abstract_problems() accessor. 397 398 The records reference by the has_many relationship and the cs_list field must be 399 kept in sync, or a warning is emitted. 400 401 =cut 402 403 sub has_explicit_order { 404 my ($class, $relation, $cs_list) = @_; 405 return unless $relation && $cs_list; 406 407 #warn "$class->has_explicit_order($relation => $cs_list)\n"; 408 409 my $cs_list_method = "${cs_list}_list"; 410 $class->_croak("has_explicit_order() needs a cs_list field.") 411 unless $class->can($cs_list_method); 412 413 # closure over: $class, $relation, $cs_list, $cs_list_method 414 my $ordered = sub { 415 my ($self, @args) = @_; 416 my @order = $self->$cs_list_method; 417 my %data_by_id; 418 419 my $iter; 420 # set up %data_by_ids to map IDs to data 421 if (wantarray) { 422 # if we're dealing with real objects, the data is objects. 423 my @Objs = $self->$relation; 424 my @ids = map { $_->id } @Objs; 425 @data_by_id{@ids} = @Objs; 426 } else { 427 # if we're dealing with an iterator, the data is a field inside 428 # Class::DBI::Iterator. messing with this data structure is probably not 429 # a good idea. Instead, we should probably be creating our own 430 # relationship type, OrderedHasMany, which would use a specified field 431 # to order the related records. 432 $iter = $self->$relation; 433 my @data = @{$iter->{_data}}; 434 my @ids = map { $_->{id} } @data; 435 @data_by_id{@ids} = @data; 436 } 437 438 # populate @sorted_data by taking the values of %data_by_id in order by @order 439 my @sorted_data; 440 foreach my $id (@order) { 441 if (exists $data_by_id{$id}) { 442 push @sorted_data, $data_by_id{$id}; 443 delete $data_by_id{$id}; 444 } else { 445 my $this_class = ref $class || $class; 446 my $f_class = "[foreign class]"; 447 $self->_carp("Warning: $f_class ID '$id' exists in $cs_list," 448 . " but it does not correspond to an $f_class of $this_class" 449 . " '" . $self->name . "'. Probably $cs_list was not updated" 450 . " when this problem was deleted."); 451 } 452 } 453 if (my @unlisted_ids = keys %data_by_id) { 454 my $this_class = ref $class || $class; 455 my $f_class = "[foreign class]"; 456 $self->_carp("Warning: $this_class '" . $self->name . "' is referenced by" 457 . " $f_class that do not appear in $cs_list. Probably" 458 . " $cs_list was not updated when the following $f_class" 459 . " were added: '@unlisted_ids'."); 460 } 461 462 # now dispose of the data appropriately 463 if (wantarray) { 464 # @sorted_data contains real objects, so we just return it here 465 return @sorted_data; 466 } else { 467 # store new order for _data field into iterator, and return iterator 468 $iter->{_data} = \@sorted_data; 469 return $iter; 470 } 471 }; 472 473 my $method_name = "${class}::ordered_$relation"; 474 475 no strict 'refs'; 476 *$method_name = $ordered; 477 } 478 479 ################################################################################ 480 # Table classes: each table in the database is a subclass of WeBWorK::DBv3. 481 # (http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/DatabaseSchemaV3) 482 # 483 # These are in the reverse order from the order in DatabaseSchemaV3, to ensure 484 # that the has_a() part of a relationship occurs before the has_many() part. 485 # 486 # From C<Class::DBI/has_many>: 487 # 488 # When setting up the relationship we examine the foreign class's has_a() 489 # declarations to discover which of its columns reference our class. (Note that 490 # because this happens at compile time, if the foreign class is defined in the 491 # same file, the class with the has_a() must be defined earlier than the class 492 # with the has_many(). If the classes are in different files, Class::DBI should 493 # be able to do the right thing). 494 ################################################################################ 495 496 package WeBWorK::DBv3::ProblemAttempt; 497 use base 'WeBWorK::DBv3'; 498 499 __PACKAGE__->table("problem_attempt"); 500 __PACKAGE__->columns(All => qw/id problem_version creation_date score data/); 501 502 __PACKAGE__->has_a(problem_version => "WeBWorK::DBv3::ProblemVersion"); 503 __PACKAGE__->has_a_datetime("creation_date"); 504 505 __PACKAGE__->add_trigger(before_create => \&WeBWorK::DBv3::set_creation_date); 506 507 ################################################################################ 508 509 package WeBWorK::DBv3::ProblemVersion; 510 use base 'WeBWorK::DBv3'; 511 512 __PACKAGE__->table("problem_version"); 513 __PACKAGE__->columns(All => qw/id set_version problem_assignment creation_date 514 source_file seed/); 515 516 __PACKAGE__->has_a(set_version => "WeBWorK::DBv3::SetVersion"); 517 __PACKAGE__->has_a(problem_assignment => "WeBWorK::DBv3::ProblemAssignment"); 518 __PACKAGE__->has_a_datetime("creation_date"); 519 520 __PACKAGE__->has_many(problem_attempts => "WeBWorK::DBv3::ProblemAttempt"); 521 522 __PACKAGE__->add_trigger(before_create => &WeBWorK::DBv3::set_creation_date); 523 524 ################################################################################ 525 526 package WeBWorK::DBv3::SetVersion; 527 use base 'WeBWorK::DBv3'; 528 529 __PACKAGE__->table("set_version"); 530 __PACKAGE__->columns(All => qw/id set_assignment problem_order creation_date/); 531 532 __PACKAGE__->has_a(set_assignment => "WeBWorK::DBv3::SetAssignment"); 533 __PACKAGE__->has_a_datetime("creation_date"); 534 __PACKAGE__->has_a_list("problem_order"); 535 536 __PACKAGE__->has_many(problem_versions => "WeBWorK::DBv3::ProblemVersion"); 537 538 __PACKAGE__->add_trigger(before_create => &WeBWorK::DBv3::set_creation_date); 539 540 ################################################################################ 541 542 package WeBWorK::DBv3::ProblemOverride; 543 use base 'WeBWorK::DBv3'; 544 545 __PACKAGE__->table("problem_override"); 546 __PACKAGE__->columns(All => qw/id abstract_problem section recitation 547 participant source_type source_file source_group_set_id weight 548 max_attempts_per_version version_creation_interval versions_per_interval 549 version_due_date_offset version_answer_date_offset/); 550 551 __PACKAGE__->has_a(abstract_problem => "WeBWorK::DBv3::AbstractProblem"); 552 __PACKAGE__->has_a(section => "WeBWorK::DBv3::Section"); 553 __PACKAGE__->has_a(recitation => "WeBWorK::DBv3::Recitation"); 554 __PACKAGE__->has_a(participant => "WeBWorK::DBv3::Participant"); 555 556 # FIXME need to make version_due_date_offset/version_answer_date_offset 557 # DateTime::Offset objects 558 559 ################################################################################ 560 561 package WeBWorK::DBv3::SetOverride; 562 use base 'WeBWorK::DBv3'; 563 564 __PACKAGE__->table("set_override"); 565 __PACKAGE__->columns(All => qw/id abstract_set section recitation participant 566 set_header hardcopy_header open_date due_date answer_date published 567 problem_order reorder_type reorder_subset_size atomicity 568 max_attempts_per_version version_creation_interval versions_per_interval 569 version_due_date_offset version_answer_date_offset/); 570 571 __PACKAGE__->has_a(abstract_set => "WeBWorK::DBv3::AbstractSet"); 572 __PACKAGE__->has_a(section => "WeBWorK::DBv3::Section"); 573 __PACKAGE__->has_a(recitation => "WeBWorK::DBv3::Recitation"); 574 __PACKAGE__->has_a(participant => "WeBWorK::DBv3::Participant"); 575 __PACKAGE__->has_a_datetime("open_date"); 576 __PACKAGE__->has_a_datetime("due_date"); 577 __PACKAGE__->has_a_datetime("answer_date"); 578 __PACKAGE__->has_a_list("problem_order"); 579 580 # FIXME need to make version_due_date_offset/version_answer_date_offset 581 # DateTime::Offset objects 582 583 ################################################################################ 584 585 package WeBWorK::DBv3::ProblemAssignment; 586 use base 'WeBWorK::DBv3'; 587 588 __PACKAGE__->table("problem_assignment"); 589 __PACKAGE__->columns(All => qw/id set_assignment abstract_problem source_file/); 590 591 __PACKAGE__->has_a(set_assignment => "WeBWorK::DBv3::SetAssignment"); 592 __PACKAGE__->has_a(abstract_problem => "WeBWorK::DBv3::AbstractProblem"); 593 594 __PACKAGE__->has_many(problem_overrides => "WeBWorK::DBv3::ProblemOverride"); 595 __PACKAGE__->has_many(problem_versions => "WeBWorK::DBv3::ProblemVersion"); 596 597 ################################################################################ 598 599 package WeBWorK::DBv3::SetAssignment; 600 use base 'WeBWorK::DBv3'; 601 602 __PACKAGE__->table("set_assignment"); 603 __PACKAGE__->columns(All => qw/id abstract_set participant problem_order/); 604 605 __PACKAGE__->has_a(abstract_set => "WeBWorK::DBv3::AbstractSet"); 606 __PACKAGE__->has_a(participant => "WeBWorK::DBv3::Participant"); 607 __PACKAGE__->has_a_list("problem_order"); 608 609 __PACKAGE__->has_many(problem_assignments => "WeBWorK::DBv3::ProblemAssignment"); 610 __PACKAGE__->has_many(set_overrides => "WeBWorK::DBv3::SetOverride"); 611 __PACKAGE__->has_many(set_versions => "WeBWorK::DBv3::SetVersion"); 612 613 __PACKAGE__->has_explicit_order(problem_assignments => "problem_order"); 614 615 ################################################################################ 616 617 package WeBWorK::DBv3::AbstractProblem; 618 use base 'WeBWorK::DBv3'; 619 620 __PACKAGE__->table("abstract_problem"); 621 __PACKAGE__->columns(All => qw/id abstract_set name source_type source_file 622 source_group_set_id source_group_select_time weight max_attempts_per_version 623 version_creation_interval versions_per_interval version_due_date_offset 624 version_answer_date_offset/); 625 626 __PACKAGE__->has_a(abstract_set => "WeBWorK::DBv3::AbstractSet"); 627 628 __PACKAGE__->has_many(problem_assignments => "WeBWorK::DBv3::ProblemAssignment"); 629 630 # FIXME need to make version_due_date_offset/version_answer_date_offset 631 # DateTime::Offset objects 632 633 ################################################################################ 634 635 package WeBWorK::DBv3::AbstractSet; 636 use base 'WeBWorK::DBv3'; 637 638 __PACKAGE__->table("abstract_set"); 639 __PACKAGE__->columns(All => qw/id course name set_header hardcopy_header 640 open_date due_date answer_date published problem_order reorder_type 641 reorder_subset_size reorder_time atomicity max_attempts_per_version 642 version_creation_interval versions_per_interval version_due_date_offset 643 version_answer_date_offset/); 644 645 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course"); 646 __PACKAGE__->has_a_datetime("open_date"); 647 __PACKAGE__->has_a_datetime("due_date"); 648 __PACKAGE__->has_a_datetime("answer_date"); 649 __PACKAGE__->has_a_boolean("published"); 650 __PACKAGE__->has_a_list("problem_order"); 651 652 __PACKAGE__->has_many(abstract_problems => "WeBWorK::DBv3::AbstractProblem"); 653 __PACKAGE__->has_many(set_assignments => "WeBWorK::DBv3::SetAssignment"); 654 655 __PACKAGE__->has_explicit_order(abstract_problems => "problem_order"); 656 657 # FIXME need to make version_due_date_offset/version_answer_date_offset 658 # DateTime::Offset objects 659 660 sub assign_to_participant { 661 my ($self, $Participant) = @_; 662 663 my $SetAssignment = eval { 664 create WeBWorK::DBv3::SetAssignment({ 665 abstract_set => $self, 666 participant => $Participant, 667 }) 668 }; 669 $@ =~ /Duplicate entry/ and die "Abstract set '", $self->name, 670 "' is already assigned to participant '", $Participant->user->name, "'.\n"; 671 $@ and die $@; 672 673 # order in which to assign abstract problems to participant 674 my @problem_assignment_order = $self->problem_order_list; 675 676 # if it's time to reorder, we do that now 677 if ($self->reorder_time eq "assignment") { 678 679 # randomize? 680 if ($self->reorder_type eq "none") { 681 # leave order alone 682 } elsif ($self->reorder_type eq "random") { 683 # randomize 684 fisher_yates_shuffle(\@problem_assignment_order); 685 } 686 687 # take a subset? 688 if (defined $self->reorder_subset_size) { 689 my $range = $self->reorder_subset_size; 690 if ($range < @problem_assignment_order) { 691 @problem_assignment_order = @problem_assignment_order[0 .. $range-1]; 692 } 693 } 694 } 695 696 # order of assigned problems 697 my @assigned_problem_order; 698 699 foreach my $abs_prob_id (@problem_assignment_order) { 700 701 } 702 } 703 704 ################################################################################ 705 706 package WeBWorK::DBv3::Participant; 707 use base 'WeBWorK::DBv3'; 708 709 __PACKAGE__->table("participant"); 710 __PACKAGE__->columns(All => qw/id course user status role section recitation 711 last_access comment/); 712 713 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course"); 714 __PACKAGE__->has_a(user => "WeBWorK::DBv3::User"); 715 __PACKAGE__->has_a(status => "WeBWorK::DBv3::Status"); 716 __PACKAGE__->has_a(role => "WeBWorK::DBv3::Role"); 717 __PACKAGE__->has_a(section => "WeBWorK::DBv3::Section"); 718 __PACKAGE__->has_a(recitation => "WeBWorK::DBv3::Recitation"); 719 720 __PACKAGE__->has_many(set_assignments => "WeBWorK::DBv3::SetAssignment"); 721 __PACKAGE__->has_many(set_overrides => "WeBWorK::DBv3::SetOverride"); 722 __PACKAGE__->has_many(problem_overrides => "WeBWorK::DBv3::ProblemOverride"); 723 724 ################################################################################ 725 726 package WeBWorK::DBv3::Recitation; 727 use base 'WeBWorK::DBv3'; 728 729 __PACKAGE__->table("recitation"); 730 __PACKAGE__->columns(All => qw/id course name/); 731 732 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course"); 733 734 __PACKAGE__->has_many(participants => "WeBWorK::DBv3::Participant"); 735 __PACKAGE__->has_many(set_overrides => "WeBWorK::DBv3::SetOverride"); 736 __PACKAGE__->has_many(problem_overrides => "WeBWorK::DBv3::ProblemOverride"); 737 738 ################################################################################ 739 740 package WeBWorK::DBv3::Section; 741 use base 'WeBWorK::DBv3'; 742 743 __PACKAGE__->table("section"); 744 __PACKAGE__->columns(All => qw/id course name/); 745 746 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course"); 747 748 __PACKAGE__->has_many(participants => "WeBWorK::DBv3::Participant"); 749 __PACKAGE__->has_many(set_overrides => "WeBWorK::DBv3::SetOverride"); 750 __PACKAGE__->has_many(problem_overrides => "WeBWorK::DBv3::ProblemOverride"); 751 752 ################################################################################ 753 754 package WeBWorK::DBv3::Role; 755 use base 'WeBWorK::DBv3'; 756 757 __PACKAGE__->table("role"); 758 __PACKAGE__->columns(All => qw/id course name privs/); 759 760 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course"); 761 __PACKAGE__->has_a_list("privs"); 762 763 __PACKAGE__->has_many(participants => "WeBWorK::DBv3::Participant"); 764 765 ################################################################################ 766 767 package WeBWorK::DBv3::Status; 768 use base 'WeBWorK::DBv3'; 769 770 __PACKAGE__->table("status"); 771 __PACKAGE__->columns(All => qw/id course name allow_course_access 772 include_in_assignment include_in_stats include_in_scoring/); 773 774 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course"); 775 __PACKAGE__->has_a_boolean("allow_course_access"); 776 __PACKAGE__->has_a_boolean("include_in_assignment"); 777 __PACKAGE__->has_a_boolean("include_in_stats"); 778 __PACKAGE__->has_a_boolean("include_in_scoring"); 779 780 __PACKAGE__->has_many(participants => "WeBWorK::DBv3::Participant"); 781 782 ################################################################################ 783 784 package WeBWorK::DBv3::User; 785 use base 'WeBWorK::DBv3'; 786 787 __PACKAGE__->table("user"); 788 __PACKAGE__->columns(All => qw/id first_name last_name email_address student_id 789 login_id password display_mode show_old_answers/); 790 791 __PACKAGE__->has_a_boolean("show_old_answers"); 792 793 __PACKAGE__->has_many(participants => "WeBWorK::DBv3::Participant"); 794 795 sub name { 796 return $_[0]->first_name . " " . $_[0]->last_name; 797 } 798 799 ################################################################################ 800 801 package WeBWorK::DBv3::Course; 802 use base 'WeBWorK::DBv3'; 803 804 __PACKAGE__->table("course"); 805 __PACKAGE__->columns(All => qw/id name visible locked archived/); 806 807 __PACKAGE__->has_a_boolean("visible"); 808 __PACKAGE__->has_a_boolean("locked"); 809 __PACKAGE__->has_a_boolean("archived"); 810 811 __PACKAGE__->has_many(statuses => "WeBWorK::DBv3::Status"); 812 __PACKAGE__->has_many(roles => "WeBWorK::DBv3::Role"); 813 __PACKAGE__->has_many(sections => "WeBWorK::DBv3::Section"); 814 __PACKAGE__->has_many(recitations => "WeBWorK::DBv3::Recitation"); 815 __PACKAGE__->has_many(participants => "WeBWorK::DBv3::Participant"); 816 __PACKAGE__->has_many(abstract_sets => "WeBWorK::DBv3::AbstractSet"); 817 818 ################################################################################ 819 820 package WeBWorK::DBv3::EquationCache; 821 use base 'WeBWorK::DBv3'; 822 823 __PACKAGE__->table("equation_cache"); 824 __PACKAGE__->columns(All => qw/id tex width height depth/); 825 826 ################################################################################ 827 828 package WeBWorK::DBv3::Setting; 829 use base 'WeBWorK::DBv3'; 830 831 __PACKAGE__->table("setting"); 832 __PACKAGE__->columns(All => qw/name val/); 833 834 ################################################################################ 835 836 =head1 AUTHOR 837 838 Written by Sam Hathaway, sh002i (at) math.rochester.edu. 839 840 =cut 841 842 1; 843 844 __END__ 845
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |