Skip to main content

Overview

Assignments are the core of Wecode’s functionality, allowing instructors to create timed programming contests with multiple problems, automatic grading, and scoreboards.

Assignment Model

The Assignment model contains comprehensive configuration options:
Assignment.php:26-40
protected $fillable = [
    "name",
    "total_submits",
    "open",
    "score_board",
    "javaexceptions",
    "start_time",
    "finish_time",
    "extra_time",
    "late_rule",
    "participants",
    "description",
    "user_id",
    "language_ids",
];

Key Fields

Timing

  • start_time: When assignment becomes available
  • finish_time: Official deadline
  • extra_time: Additional time allowed (in seconds)

Configuration

  • open: Whether students can submit (0/1)
  • score_board: Enable public scoreboard (0/1)
  • late_rule: Formula for late penalty calculation
  • language_ids: Comma-separated allowed language IDs

Creating Assignments

Route: POST /assignments

Basic Creation

assignment_controller.php:154-197
public function store(Request $request)
{
    if ( !in_array( Auth::user()->role->name, ['admin', 'head_instructor']) )
        abort(403,'You do not have permission to add assignment');

    $validated = $request->validate([
        'name' => ['required','max:150'],
        'pdf_file' => 'mimes:pdf',
    ]);

    $input = $request->input();
    $this->_process_form($input);

    $assignment = new Assignment;
    $assignment->fill($input);
    $assignment->user_id = Auth::user()->id;

    $assignment->save();
    
    // Attach problems
    foreach ($request->problem_id as $i => $id)
    {
        if ($id == -1) continue;
        $assignment->problems()->attach([
            $id => [
                'problem_name' => $request->problem_name[$i], 
                'score' => $request->problem_score[$i], 
                'ordering' => $i
            ],
        ]);
    }

    // Attach classes (lops)
    if ($request->lop_id != NULL)
    {
        foreach ($request->lop_id as $i => $id)
        {
            $assignment->lops()->attach($id);
        }
    }

    return redirect('assignments');
}

Time Processing

assignment_controller.php:134-138
$extra_time = 1;
foreach( explode('*',$request['extra_time'] ) as $t){
    $extra_time *= $t;
}
$request['extra_time'] = $extra_time;
Example: 2*60*60 = 2 hours = 7200 seconds

Adding Problems to Assignments

Problems are attached with custom names and scores:
assignment_controller.php:180-185
foreach ($request->problem_id as $i => $id)
{
    if ($id == -1) continue;
    $assignment->problems()->attach([
        $id => ['problem_name' => $request->problem_name[$i], 'score' => $request->problem_score[$i], 'ordering' => $i],
    ]);
}

Problem Pivot Data

Assignment.php:46-51
public function problems()
{
    return $this->belongsToMany("App\Models\Problem")
        ->withPivot("score", "ordering", "problem_name")
        ->withTimestamps();
}
  • problem_name: Custom name for problem in this assignment
  • score: Maximum points for this problem
  • ordering: Display order in assignment

Opening and Closing Assignments

Route: POST /assignment/check_open
assignment_controller.php:574-591
public function check_open(Request $request)
{
    $assignment_id = $request->assignment_id;
    $assignment = Assignment::find($assignment_id);
    if ($assignment != NULL){

        if (($t = $assignment->cannot_edit(Auth::user())) !== false){
            echo "error, " . $t;
            return;
        }
        $assignment->open=!$assignment->open;
        $assignment->save();
        echo "success";
            return;
    }
    else
        echo "error";
}

Submission Rules

Assignments enforce strict submission rules:
Assignment.php:98-155
public function can_submit(User $user, Problem $problem = null)
{
    $result = new class {};
    $result->error_message = "Unknown error";
    $result->can_submit = false;

    // Check if trial account expired
    if (
        $user->trial_time &&
        in_array($user->role->name, ["student"]) &&
        $user->created_at->addHours($user->trial_time) <= Carbon::now()
    ) {
        $user->role_id = 5; //Convert to guest
        $user->save();
    }

    if (in_array($user->role->name, ["guest"])) {
        $result->error_message = "Guest can not make submissions";
    } 
    elseif (in_array($user->role->name, ["student"]) && $this->open == 0) {
        $result->error_message = "You cannot submit, selected assignment is closed.";
    } 
    elseif (!$this->started() && in_array($user->role->name, ["student"])) {
        $result->error_message = "You cannot submit, Selected assignment has not started.";
    } 
    elseif (
        $this->start_time < $this->finish_time &&
        Carbon::now() > $this->finish_time->addSeconds($this->extra_time)
    ) {
        $result->error_message = "You cannot submit, Selected assignment has finished.";
    } 
    elseif (!$this->is_participant($user)) {
        $result->error_message = "You cannot submit, You are not registered.";
    } 
    else {
        $result->error_message = "none";
        $result->can_submit = true;
    }
    return $result;
}

Late Submission Penalties

Calculate score penalties using custom formulas:
Assignment.php:181-207
private function _eval_coefficient($delay = 0, $submit_time = 0)
{
    try {
        $extra_time = $this->extra_time;
        $expressionLanguage = new ExpressionLanguage();
        $expressionLanguage->registerProvider(new my_expression_language_provider());

        $coefficient = $expressionLanguage->evaluate($this->late_rule, [
            'delay' => $delay,
            'extra_time' => $extra_time,
        ]);

        $coefficient = round($coefficient, 1);
        if ($coefficient < 0)
            $coefficient = max(-10000, $coefficient);
        else
            $coefficient = min(10000, $coefficient);

    } catch (\Throwable $e) {
        $coefficient = "error";
    }
    if (!isset($coefficient) || !is_numeric($coefficient)) {
        $coefficient = "error";
    }
    return $coefficient;
}
delay = seconds after finish_time
extra_time = assignment.extra_time
submit_time = seconds after start_time

Downloading Submissions

By User or Problem

Route: GET /assignment/download_submissions/{type}/{assignment_id}
assignment_controller.php:514-555
public function download_submissions($type, $assignment_id)
{
    if ( ! in_array( Auth::user()->role->name, ['admin', 'head_instructor', 'instructor']) )
        abort(403);
        
    $final_subs = Submission::get_final_submissions($assignment_id);

    $zip = new ZipArchive;
    if ($type === 'by_user')
        $zip_name = $assignments_root . "/assignment" . (string)$assignment_id . "_submissions_by_user_" . (string)date('Y-m-d_H-i') . ".zip";
    elseif ($type === 'by_problem')
        $zip_name = $assignments_root . "/assignment" . (string)$assignment_id . "_submissions_by_problem_" . (string)date('Y-m-d_H-i') . ".zip";
        
    $zip->open($zip_name, ZipArchive::CREATE | ZipArchive::OVERWRITE);
    
    foreach ($final_subs as $final_sub)
    {
        $file_path = Submission::get_path($final_sub->username, $assignment_id, $final_sub->problem_id)
        . "/" . (string)$final_sub->file_name . "." .(string)Language::find($final_sub->language_id)->extension;
        
        if ( ! file_exists($file_path))
            continue;
            
        $file = file_get_contents($file_path);
        if ($type === 'by_user')
            $zip->addFromString("{$final_sub->username}/problem_{$final_sub->problem_id}." . (string)Language::find($final_sub->language_id)->extension, $file);
        elseif ($type === 'by_problem')
            $zip->addFromString("problem_{$final_sub->problem_id}/{$final_sub->username}." . (string)Language::find($final_sub->language_id)->extension, $file);
    }
    
    $zip->close();
    return response()->download($zip_name)->deleteFileAfterSend();
}

All Submissions and Tests

Route: GET /assignment/download_all_submissions/{assignment_id}
assignment_controller.php:497-512
public function download_all_submissions($assignment_id)
{
    if ( ! in_array( Auth::user()->role->name, ['admin', 'head_instructor', 'instructor']) )
        abort(403);
        
    $ass = Assignment::find($assignment_id) ;
    if ($ass == null)
        abort(404);

    if ($ass->submissions->count() == 0) abort(404);

    $assignments_root = Setting::get("assignments_root");
    $zipFile = $assignments_root . "/assignment" . (string)$assignment_id . "." . (string)date('Y-m-d_H-i') . ".zip";
    $pathdir = $assignments_root . '/assignment_' . $assignment_id . "/";
    shell_exec("zip -r $zipFile $pathdir");
    return response()->download($zipFile)->deleteFileAfterSend();
}

Duplicating Assignments

Route: GET /assignment/duplicate/{assignment}
assignment_controller.php:327-341
public function duplicate(Assignment $assignment){
    if (($t = $assignment->cannot_edit(Auth::user())) !== false){
        abort(403, $t);
    }

    $new = $assignment->replicate();
    $new->user_id = Auth::user()->id;
    $new->total_submits = 0;
    $new->save();

    foreach ($assignment->problems as $p) {
        $new->problems()->attach($p->id, [
            'score' => $p->pivot->score, 
            'problem_name' => $p->pivot->problem_name, 
            'ordering' =>$p->pivot->ordering
        ]);
    }
    return redirect()->route('assignments.index');
}

Assignment Routes

MethodRouteActionPermission
GET/assignmentsList assignmentsauthenticated
GET/assignments/createCreate formadmin, head_instructor
POST/assignmentsStore assignmentadmin, head_instructor
GET/assignment/{id}/{problem}View problemparticipant
GET/assignment/{id}/{problem}/pdfDownload PDFparticipant
GET/assignments/{id}/editEdit formowner, admin
PUT/assignments/{id}Updateowner, admin
DELETE/assignments/{id}Deleteowner, admin
POST/assignment/check_openToggle open/closeowner, admin
GET/assignment/duplicate/{id}Duplicateowner, admin
GET/assignment/download_submissions/{type}/{id}Download finalsinstructor+
GET/assignment/download_all_submissions/{id}Download allinstructor+
GET/assignment/reload_scoreboard/{id}Reload scoresinstructor+

Edit Permissions

Assignment.php:72-96
public function cannot_edit(User $actor)
{
    if ($actor->role->name == "admin") {
        return false;
    } elseif ($actor->role->name == "head_instructor") {
        if (
            $this->user->id != $actor->id &&
            !$actor
                ->lops()
                ->with("assignments")
                ->get()
                ->pluck("assignments")
                ->collapse()
                ->pluck("id")
                ->contains($this->id)
        ) {
            return "You can only edit assignment you created or assignment belongs to one of your classes";
        } else {
            return false;
        }
    } else {
        return "You do not have permission to edit assignment";
    }
}

Best Practices

Timing

  • Set start_time before finish_time
  • Use extra_time for grace period
  • Test late_rule formulas before use
  • Consider timezone of students

Problems

  • Order problems by difficulty
  • Set appropriate scores
  • Test all problems before opening
  • Provide clear problem names

Configuration

  • Limit allowed languages if needed
  • Enable scoreboard appropriately
  • Keep assignment closed until ready
  • Set reasonable extra_time

Management

  • Monitor submissions regularly
  • Download backups after deadline
  • Review scoreboard for anomalies
  • Communicate deadline clearly

Build docs developers (and LLMs) love