diff --git a/api/migrations/0026_remove_judgingcriteria_devpost_name.py b/api/migrations/0026_remove_judgingcriteria_devpost_name.py new file mode 100644 index 0000000..81080f7 --- /dev/null +++ b/api/migrations/0026_remove_judgingcriteria_devpost_name.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-02-19 08:56 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0025_auto_20170219_0255'), + ] + + operations = [ + migrations.RemoveField( + model_name='judgingcriteria', + name='devpost_name', + ), + ] diff --git a/api/migrations/0027_auto_20170219_0407.py b/api/migrations/0027_auto_20170219_0407.py new file mode 100644 index 0000000..fc4fa1a --- /dev/null +++ b/api/migrations/0027_auto_20170219_0407.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-02-19 09:07 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0026_remove_judgingcriteria_devpost_name'), + ] + + operations = [ + migrations.AddField( + model_name='judgingassignment', + name='judge', + field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, to='api.JudgeInfo'), + preserve_default=False, + ), + migrations.AlterField( + model_name='hack', + name='extra_judging_criteria', + field=models.ManyToManyField(blank=True, to='api.JudgingCriteria'), + ), + migrations.AlterField( + model_name='judgingcriteria', + name='criteria_type', + field=models.SmallIntegerField(choices=[(0, 'Overall'), (1, 'Superlative'), (2, 'Manual')]), + ), + ] diff --git a/api/models/hack.py b/api/models/hack.py index 8460851..2ccb6e7 100644 --- a/api/models/hack.py +++ b/api/models/hack.py @@ -17,9 +17,9 @@ class Hack(models.Model): objects = HackManager() hackathon = models.ForeignKey(to=Hackathon, on_delete=models.CASCADE) table_number = models.IntegerField() - name = models.CharField(max_length=100) # Devpost "Submission Title" - description = models.TextField() # Devpost "Plain Description" - extra_judging_criteria = models.ManyToManyField(to=JudgingCriteria) # Devpost "Desired Prizes" + name = models.CharField(max_length=100) # Devpost "Submission Title" + description = models.TextField() # Devpost "Plain Description" + extra_judging_criteria = models.ManyToManyField(to=JudgingCriteria, blank=True) # Devpost "Desired Prizes" def get_expo(self): expo = JudgingExpo.objects.filter( @@ -37,6 +37,15 @@ def get_expo_name(self) -> str: return 'N/A' return expo.name + def get_criteria_names(self) -> str: + names = [] + for criteria in self.extra_judging_criteria.all(): + names.append(criteria.name) + return ', '.join(names) + + def __str__(self): + return self.name + @admin.register(Hack, site=hackfsu_admin) class HackAdmin(admin.ModelAdmin): @@ -52,8 +61,5 @@ def expo(obj: Hack): return obj.get_expo_name() @staticmethod - def extra_criteria(obj: Hack): - names = [] - for criteria in obj.extra_judging_criteria.all(): - names.append(criteria.name) - return ', '.join(names) + def extra_criteria(obj: Hack) -> str: + return obj.get_criteria_names() diff --git a/api/models/judging_assignment.py b/api/models/judging_assignment.py index 68c7905..c823a77 100644 --- a/api/models/judging_assignment.py +++ b/api/models/judging_assignment.py @@ -16,7 +16,7 @@ class JudgingAssignment(models.Model): hackathon = models.ForeignKey(to=Hackathon, on_delete=models.CASCADE) hack = models.ForeignKey(to=Hack, on_delete=models.CASCADE) - judge = models.ForeignKey(to=JudgeInfo, on_delete=models.CASCADE), + judge = models.ForeignKey(to=JudgeInfo, on_delete=models.CASCADE) status = models.SmallIntegerField(choices=STATUS_CHOICES, default=STATUS_PENDING) diff --git a/api/models/judging_criteria.py b/api/models/judging_criteria.py index 2e7e177..f51f82e 100644 --- a/api/models/judging_criteria.py +++ b/api/models/judging_criteria.py @@ -5,7 +5,7 @@ class JudgingCriteria(models.Model): - CRITERIA_TYPE_OVERALL = 0, + CRITERIA_TYPE_OVERALL = 0 CRITERIA_TYPE_SUPERLATIVE = 1 CRITERIA_TYPE_MANUAL = 2 CRITERIA_TYPE = ( @@ -15,18 +15,20 @@ class JudgingCriteria(models.Model): ) hackathon = models.ForeignKey(to=Hackathon, on_delete=models.CASCADE) - name = models.CharField(max_length=50) + name = models.CharField(max_length=50) # Must correspond EXACTLY with Devpost name point_contribution = models.IntegerField() criteria_type = models.SmallIntegerField(choices=CRITERIA_TYPE) description_long = models.CharField(max_length=500) # Displayed in instructions description_short = models.CharField(max_length=100) # Displayed next to score entry - devpost_name = models.CharField(max_length=200) # The name that should correspond with devpost csv 'Desired Prizes' + + def __str__(self): + return self.name @admin.register(JudgingCriteria, site=hackfsu_admin) class JudgingCriteriaAdmin(admin.ModelAdmin): list_filter = ('hackathon', 'criteria_type') - list_display = ('id', 'criteria_type', 'point_contribution', 'name', 'description_long', 'description_short') + list_display = ('id', 'criteria_type', 'name', 'point_contribution', 'description_long', 'description_short') list_editable = ('criteria_type', 'point_contribution', 'name', 'description_long', 'description_short') list_display_links = ('id',) search_fields = ('name',) diff --git a/api/views/judge/get/grades.py b/api/views/judge/get/grades.py index c0cff09..ab1043c 100644 --- a/api/views/judge/get/grades.py +++ b/api/views/judge/get/grades.py @@ -14,22 +14,22 @@ def average_criteria_result(result): """ Averages a single criteria section and determines point value """ - result['contribution'] = 1.0 * result['running_total'] / result['times_graded'] * result['max_contribution'] + result['contribution'] = 1.0 * result['running_total'] / result['times_graded'] / 100 * result['max_contribution'] -def average_criteria_results(results): +def average_criteria_results(results: dict): # Get point contributions for every criteria - for grade_group in results: - for criteria_result in grade_group: + for type_id, grade_group in results.items(): + for result_id, criteria_result in grade_group.items(): average_criteria_result(criteria_result) # Add overall grade (sum of overall criteria contributions) final_grade = 0.0 - for criteria_result in results[JudgingCriteria.CRITERIA_TYPE_OVERALL]: + for criteria_id, criteria_result in results[JudgingCriteria.CRITERIA_TYPE_OVERALL].items(): final_grade += criteria_result['contribution'] results[JudgingCriteria.CRITERIA_TYPE_OVERALL][CRITERIA_OVERALL_ID] = { 'running_total': final_grade, - 'times_graded': results[JudgingCriteria.CRITERIA_TYPE_OVERALL][0]['times_graded'], + 'times_graded': next(iter(results[JudgingCriteria.CRITERIA_TYPE_OVERALL].values()))['times_graded'], 'max_contribution': 100, 'contribution': final_grade } @@ -52,10 +52,12 @@ def work(self, request, req, res): current_hack = None current_hack_results = None for grade in JudgingGrade.objects.filter(hackathon=hackathon).order_by('hack').all(): + print('{} {} {}'.format(grade.hack.table_number, grade.criteria.name, grade.grade)) if current_hack != grade.hack: if current_hack is not None: # Accumulate and save final results average_criteria_results(current_hack_results) + print('hack results', current_hack_results) graded_hacks.append({ 'hack': { 'name': current_hack.name, @@ -84,6 +86,19 @@ def work(self, request, req, res): result['running_total'] += grade.grade result['times_graded'] += 1 + # Do last hack + if current_hack is not None and current_hack_results is not None: + # Accumulate and save final results + average_criteria_results(current_hack_results) + print('hack results', current_hack_results) + graded_hacks.append({ + 'hack': { + 'name': current_hack.name, + 'table_number': current_hack.table_number + }, + 'results': current_hack_results + }) + res['graded_hacks'] = graded_hacks # Map criteria id to name @@ -91,7 +106,10 @@ def work(self, request, req, res): JudgingCriteria.CRITERIA_TYPE_OVERALL: dict(), JudgingCriteria.CRITERIA_TYPE_SUPERLATIVE: dict() } - for criteria in JudgingCriteria.objects.filter(hackathon=hackathon).all(): + for criteria in JudgingCriteria.objects.filter( + hackathon=hackathon, + criteria_type__in=[JudgingCriteria.CRITERIA_TYPE_OVERALL, JudgingCriteria.CRITERIA_TYPE_SUPERLATIVE] + ).distinct().all(): criteria_names[criteria.criteria_type][criteria.id] = criteria.name res['criteria_names'] = criteria_names diff --git a/api/views/judge/get/hacks_with_criteria.py b/api/views/judge/get/hacks_with_criteria.py index 1185523..fb83f78 100644 --- a/api/views/judge/get/hacks_with_criteria.py +++ b/api/views/judge/get/hacks_with_criteria.py @@ -19,14 +19,10 @@ class HacksWithCriteriaView(ApiView): def work(self, request, req, res): hacks = [] for hack in Hack.objects.filter(hackathon=Hackathon.objects.current()): - criteria_names = [] - for criteria in hack.extra_judging_criteria.filter(status=JudgingCriteria.CRITERIA_TYPE_MANUAL)\ - .order_by('id').all(): - criteria_names.append(criteria.name) hacks.append({ 'table_number': hack.table_number, 'name': hack.name, 'expo': hack.get_expo_name(), - 'criteria': ', '.join(criteria_names) + 'criteria': hack.get_criteria_names() }) res['hacks'] = hacks diff --git a/webapp/urls.py b/webapp/urls.py index 5ae227d..640e8d6 100644 --- a/webapp/urls.py +++ b/webapp/urls.py @@ -54,6 +54,8 @@ def static_redirect(path): url(r'^links/$', views.LinksPage.as_view()), + url(r'^hacks/$', views.HacksPage.as_view()), + # Shortcuts url(r'^register/$', RedirectView.as_view(url='/registration/user')), url(r'^register/hacker/$', RedirectView.as_view(url='/registration/user?attendee_type=hacker')), diff --git a/webapp/views/__init__.py b/webapp/views/__init__.py index 6dec9ce..e9c999f 100644 --- a/webapp/views/__init__.py +++ b/webapp/views/__init__.py @@ -7,6 +7,7 @@ from .index import IndexPage from .help import HelpPage from .links.index import LinksPage +from .hacks.index import HacksPage from django.shortcuts import render diff --git a/webapp/views/hacks/__init__.py b/webapp/views/hacks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/webapp/views/hacks/index.pug b/webapp/views/hacks/index.pug new file mode 100644 index 0000000..22ac49f --- /dev/null +++ b/webapp/views/hacks/index.pug @@ -0,0 +1,18 @@ +extends ../../_pug/standardLayout.pug + +block vars + - + var pageName = "Submitted Hacks" + var viewDir = "/static/views/hacks" + var DataTables = true + +block append scripts + script(src=`${viewDir}/script.js`) + +block content + h1 Submitted Hacks + p If you do not see yourself or something is wrong, notify an organizer! This list was exported from Devpost and then imported into our internal judging system. Our system will ensure that your hack is graded in a fair and timely manner. Good luck! + p When it is not your turn to expo, check out the other hackers' projects! + p: b: i IF YOU ARE NOT AT YOUR TABLE YOU MAY BE DISQUALIFIED, STAY THERE. + + table#hacks.table.table-striped.table-bordered(cellspacing="0" width="100%") diff --git a/webapp/views/hacks/index.py b/webapp/views/hacks/index.py new file mode 100644 index 0000000..eba56e4 --- /dev/null +++ b/webapp/views/hacks/index.py @@ -0,0 +1,9 @@ +""" + Public hack roster +""" + +from hackfsu_com.views.generic import PageView + + +class HacksPage(PageView): + template_name = 'hacks/index.html' diff --git a/webapp/views/hacks/script.js b/webapp/views/hacks/script.js new file mode 100644 index 0000000..4c023e0 --- /dev/null +++ b/webapp/views/hacks/script.js @@ -0,0 +1,81 @@ +/** + * Mentor help request view page + */ + +(function($) { + 'use strict'; + + var table = $('table#hacks'); + + var COLS = { + 'Table Number': { data: 'table_number' }, + 'Hack Name': { data: 'name' }, + 'Expo': { data: 'expo' }, + 'Opt-In Prizes' : { data: 'criteria'} + }; + + /** + * Formats the table html + */ + function createTable(table, colData) { + var tableHead = $(''); + var tableHeadRow = tableHead.find('tr'); + + Object.keys(colData).forEach(function(columnName) { + tableHeadRow.append(''+columnName+''); + }); + + table.append(tableHead); + table.append(''); + } + + /** + * Retrieves data + */ + function getData() { + var dfd = $.Deferred(); + + $.ajaxGet({ + url: '/api/judge/get/hacks_with_criteria' + }) + .done(function(res) { + dfd.resolve(res['hacks']); + }) + .fail(function(err) { + dfd.reject(err); + }); + + return dfd.promise(); + } + + /** + * Populates the DataTable with help requests + */ + function populateTable(table, colData, data) { + var colNames = Object.keys(colData); + + table.DataTable({ + dom: + "<'row flex-align-center flex-wrap'<'col-sm-6'l><'col-sm-6'f>>" + + "<'row'<'col-sm-12'tr>>" + + "<'row'<'col-sm-5'i><'col-sm-7'p>>", + responsive: true, + lengthMenu: [[25, 50, 100, -1], [25, 50, 100, 'All']], + data: data, + columns: $.map(colData, function(data) { + return data; + }), + order: [ + [colNames.indexOf('Table Number'), 'asc'] + ] + }); + } + + // Create page + createTable(table, COLS); + getData().done(function(data) { + populateTable(table, COLS, data); + }); + + +})(jQuery);