diff --git a/contest/models.py b/contest/models.py index e0484555..a54619eb 100644 --- a/contest/models.py +++ b/contest/models.py @@ -50,7 +50,7 @@ class Contest(models.Model): # 没有开始 返回1 return CONTEST_NOT_START elif self.end_time < now(): - # 已经结束 返回0 + # 已经结束 返回-1 return CONTEST_ENDED else: # 正在进行 返回0 diff --git a/contest/views.py b/contest/views.py index b38161a5..1869bb5f 100644 --- a/contest/views.py +++ b/contest/views.py @@ -463,15 +463,12 @@ class ContestTimeAPIView(APIView): 获取比赛开始或者结束的倒计时,返回毫秒数字 """ def get(self, request): - t = request.GET.get("type", "start") contest_id = request.GET.get("contest_id", -1) try: contest = Contest.objects.get(id=contest_id) except Contest.DoesNotExist: return error_response(u"比赛不存在") - if t == "start": - # 距离开始还有多长时间 - return success_response(int((contest.start_time - now()).total_seconds() * 1000)) - else: - # 距离结束还有多长时间 - return success_response(int((contest.end_time - now()).total_seconds() * 1000)) + return success_response({"start": int((contest.start_time - now()).total_seconds() * 1000), + "end": int((contest.end_time - now()).total_seconds() * 1000), + "status": contest.status}) + diff --git a/oj/server_settings.py b/oj/server_settings.py index 77ca1fd0..9f8f084e 100644 --- a/oj/server_settings.py +++ b/oj/server_settings.py @@ -41,3 +41,5 @@ STATICFILES_DIRS = [os.path.join(BASE_DIR, "static/release/"), os.path.join(BASE # 模板文件夹 TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'template/release/')] + +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') diff --git a/oj/urls.py b/oj/urls.py index 5b65e4c0..d1b84d5b 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -89,7 +89,6 @@ urlpatterns = [ url(r'^contests/$', "contest.views.contest_list_page", name="contest_list_page"), url(r'^contests/(?P\d+)/$', "contest.views.contest_list_page", name="contest_list_page"), - url(r'^contest/(?P\d+)/$', "contest.views.contest_page", name="contest_page"), url(r'^problem/(?P\d+)/$', "problem.views.problem_page", name="problem_page"), diff --git a/static/src/css/contest/countdown.css b/static/src/css/contest/countdown.css new file mode 100644 index 00000000..7f7fafd4 --- /dev/null +++ b/static/src/css/contest/countdown.css @@ -0,0 +1,14 @@ +#timer { + margin: 20px auto; +} +.timer-section { + display: inline-block; +} +.timer-section span { + background-color: #ffffff; + padding: 5px 4px; + border-radius: 5px; +} +.time { + font-weight: bold; +} \ No newline at end of file diff --git a/static/src/css/oj.css b/static/src/css/oj.css index cecc0b50..07c13899 100644 --- a/static/src/css/oj.css +++ b/static/src/css/oj.css @@ -2,6 +2,7 @@ @import url("bootstrap/todc-bootstrap.min.css"); @import url("codeMirror/codemirror.css"); @import url("global.css"); +@import url("contest/countdown.css"); #language-selector { width: 130px; diff --git a/static/src/js/app/oj/contest/contestCountdown.js b/static/src/js/app/oj/contest/contestCountdown.js new file mode 100644 index 00000000..97807d3a --- /dev/null +++ b/static/src/js/app/oj/contest/contestCountdown.js @@ -0,0 +1,40 @@ +require(["jquery", "jcountdown"], function($, jcountdown){ + + function getServerTime(){ + var contestId = location.pathname.split("/")[2]; + + var time = 0; + $.ajax({ + url: "/api/contest/time/?contest_id=" + contestId, + dataType: "json", + method: "get", + async: false, + success: function(data){ + if(!data.code){ + time = data.data; + } + } + }); + return time; + } + + var time = getServerTime(); + + if(time["status"] == 1){ + countdown(time["start"]) + } + else if(time["status"] == 0){ + countdown(time["end"]) + } + + function countdown(t){ + $("#timer").countdown({ + serverDiff: t, + date: "september 21, 2015 21:59", + yearsAndMonths: false, + template: $('#template').html() + }).on("countComplete", function(){ + location.reload(); + }); + } +}); \ No newline at end of file diff --git a/static/src/js/app/oj/problem/problem.js b/static/src/js/app/oj/problem/problem.js index b018f523..8f76fbec 100644 --- a/static/src/js/app/oj/problem/problem.js +++ b/static/src/js/app/oj/problem/problem.js @@ -142,9 +142,6 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"], if(!data.code){ time = data.data; } - }, - error: function(){ - time = new Date().getTime(); } }); return time; @@ -153,13 +150,16 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert", "ZeroClipboard"], if(location.href.indexOf("contest") > -1) { setInterval(function () { var time = getServerTime(); - var minutes = parseInt(time / (1000 * 60)); - if(minutes == 0){ - bsAlert("比赛即将结束"); - } - else if(minutes > 0 && minutes <= 5){ - bsAlert("比赛还剩" + minutes.toString() + "分钟"); + if(time["status"] == 0){ + var minutes = parseInt(time["end"] / (1000 * 60)); + if(minutes == 0){ + bsAlert("比赛即将结束"); + } + else if(minutes > 0 && minutes <= 5){ + bsAlert("比赛还剩" + minutes.toString() + "分钟"); + } } + }, 1000 * 60); } diff --git a/static/src/js/config.js b/static/src/js/config.js index ce6e5085..e318d973 100644 --- a/static/src/js/config.js +++ b/static/src/js/config.js @@ -3,6 +3,7 @@ var require = { baseUrl: "/static/js/", paths: { jquery: "lib/jquery/jquery", + jcountdown: "lib/jcountdown/jcountdown", avalon: "lib/avalon/avalon", editor: "utils/editor", uploader: "utils/uploader", diff --git a/static/src/js/lib/jcountdown/jcountdown.js b/static/src/js/lib/jcountdown/jcountdown.js new file mode 100644 index 00000000..370c8fd6 --- /dev/null +++ b/static/src/js/lib/jcountdown/jcountdown.js @@ -0,0 +1,727 @@ +/*! jCountdown jQuery Plugin - v2.0.0 - 2015-06-28 + * https://github.com/tomgrohl/jCountdown/ + * Copyright (c) 2015 Tom Ellis; Licensed MIT */ +!function(root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery'], factory); + } else { + factory(root.jQuery); + } +}(this, function($) { + + $.fn.countdown = function( method /*, options*/ ) { + + var msPerHr = 3600000, + secPerYear = 31557600, + secPerMonth = 2629800, + secPerWeek = 604800, + secPerDay = 86400, + secPerHr = 3600, + secPerMin = 60, + secPerSec = 1, + rTemplateTokens = /%y|%m|%w|%d|%h|%i|%s|%ty|%tm|%tw|%td|%th|%ti|%ts/g, + rDigitGlobal = /\d/g, + localNumber = function( numToConvert, settings ) { + + var arr = numToConvert.toString().match(rDigitGlobal), + localeNumber = ""; + + $.each( arr, function(i,num) { + num = Number(num); + localeNumber += (""+ settings.digits[num]) || ""+num; + }); + + return localeNumber; + }, + dateNow = function( $this ) { + var now = new Date(), // Default to local time + settings = $this.data("jcdData"); + + if ( !settings ) { + return new Date(); + } + + if ( settings.offset !== null ) { + now = getTimezoneDate( settings.offset ); + } + + now.setMilliseconds(0); + return now; + }, + getTimezoneDate = function( offset ) { + // Returns date now based on timezone/offset + var hrs, + dateMS, + curHrs, + tmpDate = new Date(); + + if ( offset !== null ) { + hrs = offset * msPerHr; + curHrs = tmpDate.getTime() - ( ( -tmpDate.getTimezoneOffset() / 60 ) * msPerHr ) + hrs; + dateMS = tmpDate.setTime( curHrs ); + } + + return new Date( dateMS ); + }, + addEventHandlers = function($element, options) { + + // Add event handlers where set + if ( options.onStart ) { + $element.on("start.jcdevt", options.onStart ); + } + + if ( options.onChange ) { + $element.on("change.jcdevt", options.onChange ); + } + + if ( options.onComplete ) { + $element.on("complete.jcdevt", options.onComplete ); + } + + if ( options.onPause ) { + $element.on("pause.jcdevt", options.onPause ); + } + + if ( options.onResume ) { + $element.on("resume.jcdevt", options.onResume ); + } + + if ( options.onLocaleChange ) { + $element.on("locale.jcdevt", options.onLocaleChange ); + } + }, + + generateTemplate = function( settings ) { + var template = settings.template, + yearsLeft = settings.yearsLeft, + monthsLeft = settings.monthsLeft, + weeksLeft = settings.weeksLeft, + daysLeft = settings.daysLeft, + hrsLeft = settings.hrsLeft, + minsLeft = settings.minsLeft, + secLeft = settings.secLeft, + hideYears = false, + hideMonths = false, + hideWeeks = false, + hideDays = false, + hideHours = false, + hideMins = false; + + if (settings.isRTL) { + template = settings.rtlTemplate; + } + + if ( settings.omitZero ) { + + if ( settings.yearsAndMonths ) { + + if( !settings.yearsLeft ) { + hideYears = true; + } + if( !settings.monthsLeft ) { + hideMonths = true; + } + } + + if ( settings.weeks && ( ( settings.yearsAndMonths && hideMonths && !settings.weeksLeft ) || ( !settings.yearsAndMonths && !settings.weeksLeft ) ) ) { + hideWeeks = true; + } + + if ( hideWeeks && !daysLeft ) { + hideDays = true; + } + + if ( hideDays && !hrsLeft ) { + hideHours = true; + } + + if ( hideHours && !minsLeft ) { + hideMins = true; + } + + } + + if ( settings.leadingZero ) { + + if ( yearsLeft < 10 ) { + yearsLeft = "0" + yearsLeft; + } + + if ( monthsLeft < 10 ) { + monthsLeft = "0" + monthsLeft; + } + + if ( weeksLeft < 10 ) { + weeksLeft = "0" + weeksLeft; + } + + if ( daysLeft < 10 ) { + daysLeft = "0" + daysLeft; + } + + if ( hrsLeft < 10 ) { + hrsLeft = "0" + hrsLeft; + } + + if ( minsLeft < 10 ) { + minsLeft = "0" + minsLeft; + } + + if ( secLeft < 10 ) { + secLeft = "0" + secLeft; + } + } + + yearsLeft = localNumber( yearsLeft, settings ); + monthsLeft = localNumber( monthsLeft, settings ); + weeksLeft = localNumber( weeksLeft, settings ); + daysLeft = localNumber( daysLeft, settings ); + hrsLeft = localNumber( hrsLeft, settings ); + minsLeft = localNumber( minsLeft, settings ); + secLeft = localNumber( secLeft, settings ); + + if ( settings.yearsAndMonths ) { + + if ( !settings.omitZero || !hideYears ) { + template = template.replace('%y', yearsLeft); + template = template.replace('%ty', (yearsLeft == 1 && settings.yearSingularText) ? settings.yearSingularText : settings.yearText); + } + + //Only hide months if years is at 0 as well as months + if ( !settings.omitZero || ( !hideYears && monthsLeft ) || ( !hideYears && !hideMonths ) ) { + template = template.replace('%m', monthsLeft); + template = template.replace('%tm', (monthsLeft == 1 && settings.monthSingularText) ? settings.monthSingularText : settings.monthText); + } + } + + if ( settings.weeks && !hideWeeks ) { + template = template.replace('%w', weeksLeft); + template = template.replace('%tw', (weeksLeft == 1 && settings.weekSingularText) ? settings.weekSingularText : settings.weekText); + + } + + if ( !hideDays ) { + template = template.replace('%d', daysLeft); + template = template.replace('%td', (daysLeft == 1 && settings.daySingularText) ? settings.daySingularText : settings.dayText); + } + + if ( !hideHours ) { + template = template.replace('%h', hrsLeft); + template = template.replace('%th', (hrsLeft == 1 && settings.hourSingularText) ? settings.hourSingularText : settings.hourText); + } + + if ( !hideMins ) { + template = template.replace('%i', minsLeft); + template = template.replace('%ti', (minsLeft == 1 && settings.minSingularText) ? settings.minSingularText : settings.minText); + } + + template = template.replace('%s', secLeft); + template = template.replace('%ts', (secLeft == 1 && settings.secSingularText) ? settings.secSingularText : settings.secText); + + // Remove un-used tokens + template = template.replace(rTemplateTokens,''); + + return template; + }, + updateCallback = function() { + + var $this = this, + template, + now, + date, + timeLeft, + yearsLeft = 0, + monthsLeft = 0, + weeksLeft = 0, + daysLeft, + hrsLeft, + minsLeft, + secLeft, + time = "", + diff, + extractSection = function( numSecs ) { + var amount; + + amount = Math.floor( diff / numSecs ); + diff -= amount * numSecs; + + return amount; + }, + settings = $this.data("jcdData"); + + // No settings so return + if ( !settings ) { + return false; + } + + now = dateNow( $this ); + + if ( null !== settings.serverDiff ) { + date = new Date( settings.serverDiff + settings.clientdateNow.getTime() ); + } + else { + date = settings.dateObj; + } + + date.setMilliseconds(0); + + timeLeft = ( settings.direction === "down" ) ? date.getTime() - now.getTime() : now.getTime() - date.getTime(); + + diff = Math.round( timeLeft / 1000 ); + + daysLeft = extractSection( secPerDay ); + hrsLeft = extractSection( secPerHr ); + minsLeft = extractSection( secPerMin ); + secLeft = extractSection( secPerSec ); + + if ( settings.yearsAndMonths ) { + + //Add days back on so we can calculate years easier + diff += ( daysLeft * secPerDay ); + + yearsLeft = extractSection( secPerYear ); + monthsLeft = extractSection( secPerMonth ); + daysLeft = extractSection( secPerDay ); + } + + if ( settings.weeks ) { + //Add days back on so we can calculate weeks easier + diff += ( daysLeft * secPerDay ); + + weeksLeft = extractSection( secPerWeek ); + daysLeft = extractSection( secPerDay ); + } + + /** + * The following 3 option should never be used together! + * MAKE them work for any time + */ + + if ( settings.hoursOnly || settings.minsOnly || settings.secsOnly ) + { + + if ( settings.yearsAndMonths ) { + //Add years, months, weeks and days back on so we can calculate dates easier + diff += ( yearsLeft * secPerYear ); + diff += ( monthsLeft * secPerMonth ); + + yearsLeft = monthsLeft = 0; + } + + if ( settings.weeks ) { + diff += ( weeksLeft * secPerWeek ); + + weeksLeft = 0; + } + + } + + // Assumes you are using dates within a month ( ~ 30 days ) + // as years and months aren't taken into account + if ( settings.hoursOnly ) { + + // Add days back on + diff += ( daysLeft * secPerDay ); + + // Add hours back on + diff += ( hrsLeft * secPerHr ); + hrsLeft = extractSection( secPerHr ); + + } + + // Assumes you are only using dates in the near future ( <= 24 hours ) + // as years and months aren't taken into account + if ( settings.minsOnly ) { + + + // Add days back on + diff += ( daysLeft * secPerDay ); + daysLeft = 0; + + // Add hours back on + diff += ( hrsLeft * secPerHr ); + hrsLeft = 0; + + diff += ( minsLeft * secPerMin ); + + minsLeft = extractSection( secPerMin ); + + } + + // Assumes you are only using dates in the near future ( <= 60 minutes ) + // as years, months and days aren't taken into account + if ( settings.secsOnly ) { + + + // Add days back on + diff += ( daysLeft * secPerDay ); + daysLeft = 0; + + // Add hours back on + diff += ( hrsLeft * secPerHr ); + hrsLeft = 0; + + // Add minutes back on + diff += ( minsLeft * secPerMin ); + minsLeft = 0; + + // Add seconds back on + diff += secLeft; + + secLeft = extractSection( secPerSec ); + } + + settings.yearsLeft = yearsLeft; + settings.monthsLeft = monthsLeft; + settings.weeksLeft = weeksLeft; + settings.daysLeft = daysLeft; + settings.hrsLeft = hrsLeft; + settings.minsLeft = minsLeft; + settings.secLeft = secLeft; + + $this.data("jcdData", settings); + + // Check if the countdown has finished + if ( !( settings.direction === "down" && ( now < date || settings.minus ) ) || !( settings.direction === "up" && ( date < now || settings.minus ) ) ) { + //settings.yearsLeft = settings.monthsLeft = settings.weeksLeft = settings.daysLeft = settings.hrsLeft = settings.minsLeft = settings.secLeft = 0; + //settings.hasCompleted = true; + } + + // We've got the time sections set so we can now do the templating + + if ( ( settings.direction === "down" && ( now < date || settings.minus ) ) || ( settings.direction === "up" && ( date < now || settings.minus ) ) ) { + time = generateTemplate( settings ); + } + else { + settings.yearsLeft = settings.monthsLeft = settings.weeksLeft = settings.daysLeft = settings.hrsLeft = settings.minsLeft = settings.secLeft = 0; + + time = generateTemplate( settings ); + settings.hasCompleted = true; + } + + $this.html( time ).triggerMulti("change.jcdevt,countChange", [settings]); + + if ( settings.hasCompleted ) { + $this.triggerMulti("complete.jcdevt,countComplete"); + clearInterval( settings.timer ); + } + + }, + methods = { + init: function( options ) { + var opts = $.extend( {}, $.fn.countdown.defaults, options ), + testDate, + testString, + settings = {}; + + return this.each(function() { + + var $this = $(this), + intervalCallback; + + // If this element already has a countdown timer + // just change the settings + if ( $this.data("jcdData") ) { + $this.countdown("changeSettings", options, true); + opts = $this.data("jcdData"); + } + + if ( opts.date === null && opts.dataAttr === null ) { + $.error("No Date passed to jCountdown. date option is required."); + return true; + } + + if ( opts.date ) { + testString = opts.date; + } else { + testString = $this.data(opts.dataAttr); + } + + testDate = new Date(testString); + + if( testDate.toString() === "Invalid Date" ) { + $.error("Invalid Date passed to jCountdown: " + testString); + } + + // Setup any event handlers + addEventHandlers($this, options); + + // Create a settings object based off the plugin options + settings = $.extend({}, opts); + + // Cache DOM elements for templating + settings.dom = {}; + + settings.dom.$time = $("<"+settings.timeWrapElement+">").addClass(settings.timeWrapClass); + settings.dom.$text = $("<"+settings.textWrapElement+">").addClass(settings.textWrapClass); + + settings.clientdateNow = new Date(); + settings.clientdateNow.setMilliseconds(0); + settings.originalHTML = $this.html(); + settings.dateObj = new Date( testString ); + settings.dateObj.setMilliseconds(0); + settings.hasCompleted = false; + settings.timer = 0; + settings.yearsLeft = settings.monthsLeft = settings.weeksLeft = settings.daysLeft = settings.hrsLeft = settings.minsLeft = settings.secLeft = 0; + settings.difference = null; + + // Create a callback for the interval + intervalCallback = $.proxy(updateCallback, $this); + settings.timer = setInterval(intervalCallback, settings.updateTime); + + $this.data("jcdData", settings).triggerMulti("start.jcdevt,countStart", [settings]); + + intervalCallback(); + + }); + }, + changeSettings: function( options, internal ) { + + // Like resume but with resetting/changing options + return this.each(function() { + var $this = $(this), + settings, + testDate, + func = $.proxy( updateCallback, $this ); + + if ( !$this.data("jcdData") ) { + return true; + } + + settings = $.extend( {}, $this.data("jcdData"), options ); + + if ( options.hasOwnProperty("date") ) { + testDate = new Date(options.date); + + if ( testDate.toString() === "Invalid Date" ) { + $.error("Invalid Date passed to jCountdown: " + options.date); + } + } + + settings.completed = false; + settings.dateObj = new Date( options.date ); + + // Clear the timer, as it might not be needed + clearInterval( settings.timer ); + $this.off(".jcdevt").data("jcdData", settings); + + // As this can be accessed via the init method as well, + // we need to check how this method is being accessed + if ( !internal ) { + + addEventHandlers($this, settings); + + settings.timer = setInterval( func, settings.updateTime ); + $this.data("jcdData", settings); + func(); //Needs to run straight away when changing settings + } + + settings = null; + }); + + }, + resume: function() { + + // Resumes a countdown timer + return this.each(function() { + var $this = $(this), + settings = $this.data("jcdData"), + func = $.proxy( updateCallback, $this ); + + if ( !settings ) { + return true; + } + + $this.data("jcdData", settings).triggerMulti("resume.jcdevt,countResume", [settings] ); + // We only want to resume a countdown that hasn't finished + if ( !settings.hasCompleted ) { + settings.timer = setInterval( func, settings.updateTime ); + + if ( settings.stopwatch && settings.direction === "up" ) { + + var t = dateNow( $this ).getTime() - settings.pausedAt.getTime(), + d = new Date(); + d.setTime( settings.dateObj.getTime() + t ); + + settings.dateObj = d; //This is internal date + } + + func(); + } + }); + + }, + pause: function() { + // Pause a countdown timer + return this.each(function() { + var $this = $(this), + settings = $this.data("jcdData"); + + if ( !settings ) { + return true; + } + + if ( settings.stopwatch ) { + settings.pausedAt = dateNow( $this ); + } + // Clear interval (Will be started on resume) + clearInterval( settings.timer ); + // Trigger pause event handler + $this.data("jcdData", settings).triggerMulti("pause.jcdevt,countPause", [settings] ); + }); + }, + complete: function() { + return this.each(function() { + var $this = $(this), + settings = $this.data("jcdData"); + + if ( !settings ) { + return true; + } + // Clear timer + clearInterval( settings.timer ); + settings.hasCompleted = true; + // Update setting, trigger complete event handler, then unbind all events + // We don"t delete the settings in case they need to be checked later on + + $this.data("jcdData", settings).triggerMulti("complete.jcdevt,countComplete", [settings]); + }); + }, + destroy: function() { + return this.each(function() { + var $this = $(this), + settings = $this.data("jcdData"); + + if ( !settings ) { + return true; + } + // Clear timer + clearInterval( settings.timer ); + // Unbind all events, remove data and put DOM Element back to its original state (HTML wise) + $this.off(".jcdevt").removeData("jcdData").html( settings.originalHTML ); + }); + }, + getSettings: function( name ) { + var $this = $(this), + settings = $this.data("jcdData"); + + // If an individual setting is required + if ( name && settings ) { + // If it exists, return it + if ( settings.hasOwnProperty( name ) ) { + return settings[name]; + } + return undefined; + } + // Return all settings or undefined + return settings; + }, + changeLocale: function( locale ) { + var $this = $(this), + settings = $this.data("jcdData"); + + // If no locale exists error and return false + if ( !$.fn.countdown.locale[locale] ) { + $.error("Locale '" + locale + "' does not exist"); + return false; + } + + $.extend( settings, $.fn.countdown.locale[locale] ); + + $this.data("jcdData", settings).triggerMulti("locale.jcdevt,localeChange", [settings]); + + return true; + } + }; + + if( methods[ method ] ) { + return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) ); + } else if ( typeof method === "object" || !method ) { + return methods.init.apply( this, arguments ); + } else { + $.error("Method "+ method +" does not exist in the jCountdown Plugin"); + } + }; + +// Expose defaults so we can easily override settings +// Useful for locale plugins + $.fn.countdown.defaults = { + date: null, + dataAttr: null, + updateTime: 1000, + yearText: 'years', + monthText: 'months', + weekText: 'weeks', + dayText: '天', + hourText: '小时', + minText: '分', + secText: '秒', + yearSingularText: 'year', + monthSingularText: 'month', + weekSingularText: 'week', + daySingularText: 'day', + hourSingularText: 'hour', + minSingularText: 'min', + secSingularText: 'sec', + digits : [0,1,2,3,4,5,6,7,8,9], + isRTL: false, + minus: false, + onStart: null, + onChange: null, + onComplete: null, + onResume: null, + onPause: null, + onLocaleChange: null, + leadingZero: false, + offset: null, + serverDiff:null, + hoursOnly: false, + minsOnly: false, + secsOnly: false, + weeks: false, + hours: false, + yearsAndMonths: false, + direction: "down", + stopwatch: false, + omitZero: false, + + rtlTemplate: '%ts %s : %ti %i : %th %h : %tm %m : %ty %y', + template: '%y %ty : %m %tm : %h %th : %i %ti : %s %ts' + }; + +// Locale cache + $.fn.countdown.locale = []; + +// Store default english locale so we can switch easier + $.fn.countdown.locale.en = { + yearText: 'years', + monthText: 'months', + weekText: 'weeks', + dayText: 'days', + hourText: 'hours', + minText: 'mins', + secText: 'sec', + yearSingularText: 'year', + monthSingularText: 'month', + weekSingularText: 'week', + daySingularText: 'day', + hourSingularText: 'hour', + minSingularText: 'min', + secSingularText: 'sec', + isRTL: false + }; + +// + $.fn.triggerMulti = function( eventTypes, extraParameters ) { + var events = eventTypes.split(","); + + return this.each(function() { + var $this = $(this); + + for ( var i = 0; i < events.length; i++) { + $this.trigger( events[i], extraParameters ); + } + }); + }; + +}); diff --git a/submission/views.py b/submission/views.py index 396cdfe8..88c6d0dc 100644 --- a/submission/views.py +++ b/submission/views.py @@ -11,7 +11,7 @@ from rest_framework.views import APIView from judge.judger_controller.tasks import judge from judge.judger_controller.settings import redis_config from account.decorators import login_required -from account.models import SUPER_ADMIN +from account.models import SUPER_ADMIN, User from problem.models import Problem from contest.models import ContestProblem, Contest @@ -162,7 +162,11 @@ def my_submission_list_page(request, page=1): 我的所有提交的列表页 """ submissions = Submission.objects.filter(user_id=request.user.id, contest_id__isnull=True). \ - values("id", "problem_id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time") + values("id", "user_id", "problem_id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time") + + # 显示所有人的提交 这是管理员的调试功能 + show_all = request.GET.get("show_all", False) == "true" + language = request.GET.get("language", None) filter = None if language: @@ -173,14 +177,21 @@ def my_submission_list_page(request, page=1): submissions = submissions.filter(result=int(result)) filter = {"name": "result", "content": result} - # 为 submission 查询题目 因为提交页面经常会有重复的题目,缓存一下查询结果 - cache_result = {} + # 因为提交页面经常会有重复的题目和用户,缓存一下查询结果 + cache_result = {"problem": {}, "user": {}} for item in submissions: problem_id = item["problem_id"] - if problem_id not in cache_result: + if problem_id not in cache_result["problem"]: problem = Problem.objects.get(id=problem_id) - cache_result[problem_id] = problem.title - item["title"] = cache_result[problem_id] + cache_result["problem"][problem_id] = problem.title + item["title"] = cache_result["problem"][problem_id] + + if show_all: + user_id = item["user_id"] + if user_id not in cache_result["user"]: + user = User.objects.get(id=user_id) + cache_result["user"][user_id] = user + item["user"] = cache_result["user"][user_id] paginator = Paginator(submissions, 20) try: @@ -197,13 +208,10 @@ def my_submission_list_page(request, page=1): except Exception: pass - # 右侧的公告列表 - announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time") - return render(request, "oj/submission/my_submissions_list.html", {"submissions": current_page, "page": int(page), "previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20, - "announcements": announcements, "filter": filter}) + "filter": filter, "show_all": show_all}) class SubmissionShareAPIView(APIView): @@ -245,5 +253,6 @@ class SubmissionRejudgeAdminAPIView(APIView): # 增加redis 中判题队列长度的计数器 r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"]) r.incr("judge_queue_length") + return success_response(u"任务提交成功") else: return serializer_invalid_response(serializer) \ No newline at end of file diff --git a/template/src/oj/contest/_contest_header.html b/template/src/oj/contest/_contest_header.html index bcf4cabe..3fcac447 100644 --- a/template/src/oj/contest/_contest_header.html +++ b/template/src/oj/contest/_contest_header.html @@ -1,6 +1,8 @@ {% load contest %}

{{ contest.title }}

+
+
diff --git a/template/src/oj/contest/contest_index.html b/template/src/oj/contest/contest_index.html index 7b977a03..0c98523d 100644 --- a/template/src/oj/contest/contest_index.html +++ b/template/src/oj/contest/contest_index.html @@ -20,4 +20,26 @@ {% include "oj/contest/_contest_header.html" %} -{% endblock %} \ No newline at end of file +{% endblock %} +{% block js_block %} + + +{% endblock %} diff --git a/template/src/oj/contest/no_contest_permission.html b/template/src/oj/contest/no_contest_permission.html index dedc81fd..f7418894 100644 --- a/template/src/oj/contest/no_contest_permission.html +++ b/template/src/oj/contest/no_contest_permission.html @@ -39,5 +39,25 @@ {% endblock %} {% block js_block %} + + {% endblock %} \ No newline at end of file diff --git a/template/src/oj/problem/my_submissions_list.html b/template/src/oj/problem/my_submissions_list.html index dd915a17..4f69d6fd 100644 --- a/template/src/oj/problem/my_submissions_list.html +++ b/template/src/oj/problem/my_submissions_list.html @@ -27,7 +27,10 @@ {% for item in submissions %} - +
{{ forloop.counter }} + {{ forloop.counter }} + {% if show_all %}{{ item.user.username }}{% endif %} + {{ item.create_time }} {{ item.language|translate_language }}