├── cpdrills ├── landing │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── admin.py │ ├── apps.py │ ├── urls.py │ └── views.py ├── accounts │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_userprofile_read_terms.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── apps.py │ ├── admin.py │ ├── tokens.py │ ├── signals.py │ ├── models.py │ ├── urls.py │ ├── forms.py │ └── views.py ├── cpdrills │ ├── __init__.py │ ├── settings │ │ ├── __init__.py │ │ ├── development.py │ │ ├── production.py │ │ └── base.py │ ├── wsgi.py │ ├── asgi.py │ └── urls.py ├── training │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── templatetags │ │ ├── __init__.py │ │ └── training_extras.py │ ├── apps.py │ ├── urls.py │ ├── tests.py │ ├── admin.py │ ├── models.py │ ├── forms.py │ └── views.py ├── templates │ ├── accounts │ │ ├── activate_failed.html │ │ ├── email_verify.html │ │ ├── sign_up.html │ │ ├── profile_edit.html │ │ └── sign_in.html │ ├── landing │ │ ├── privacy_policy.html │ │ ├── contact_us.html │ │ └── index.html │ ├── training │ │ └── main_pages │ │ │ ├── speedtrain_problem.html │ │ │ ├── train.html │ │ │ ├── speedtrain_form.html │ │ │ └── practice.html │ └── base.html ├── static │ ├── images │ │ ├── logo.png │ │ ├── solves.png │ │ ├── analytics.png │ │ ├── dynamic_prog.png │ │ ├── speedtrain.png │ │ ├── landing_computer.svg │ │ └── signup_computer.svg │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── Simple-Line-Icons.eot │ │ ├── Simple-Line-Icons.ttf │ │ ├── Simple-Line-Icons.woff │ │ ├── Simple-Line-Icons.woff2 │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ └── simple-line-icons.min.css │ ├── javascript │ │ ├── training │ │ │ └── main_pages │ │ │ │ ├── bs-init.js │ │ │ │ └── theme.js │ │ ├── bs-init.js │ │ ├── stopwatch.js │ │ └── HackTimer.js │ ├── css │ │ └── training │ │ │ └── authentication │ │ │ └── sign_up.css │ └── bootstrap │ │ └── css2 │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.rtl.min.css │ │ ├── bootstrap-reboot.rtl.css │ │ └── bootstrap-reboot.css ├── manage.py └── utils │ ├── problems.py │ ├── codeforces.py │ └── graph_plots.py ├── env_template.txt ├── requirements.txt ├── README.md ├── .gitignore └── cf_api_server.py /cpdrills/landing/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpdrills/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpdrills/cpdrills/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpdrills/training/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpdrills/cpdrills/settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpdrills/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpdrills/landing/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpdrills/training/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpdrills/training/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpdrills/templates/accounts/activate_failed.html: -------------------------------------------------------------------------------- 1 | Invalid activation link... -------------------------------------------------------------------------------- /cpdrills/accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /cpdrills/landing/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /cpdrills/landing/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /cpdrills/landing/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /cpdrills/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/images/logo.png -------------------------------------------------------------------------------- /cpdrills/static/images/solves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/images/solves.png -------------------------------------------------------------------------------- /cpdrills/static/images/analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/images/analytics.png -------------------------------------------------------------------------------- /cpdrills/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /cpdrills/static/images/dynamic_prog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/images/dynamic_prog.png -------------------------------------------------------------------------------- /cpdrills/static/images/speedtrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/images/speedtrain.png -------------------------------------------------------------------------------- /cpdrills/static/fonts/Simple-Line-Icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/fonts/Simple-Line-Icons.eot -------------------------------------------------------------------------------- /cpdrills/static/fonts/Simple-Line-Icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/fonts/Simple-Line-Icons.ttf -------------------------------------------------------------------------------- /cpdrills/static/fonts/Simple-Line-Icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/fonts/Simple-Line-Icons.woff -------------------------------------------------------------------------------- /cpdrills/static/fonts/Simple-Line-Icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/fonts/Simple-Line-Icons.woff2 -------------------------------------------------------------------------------- /cpdrills/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /cpdrills/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /cpdrills/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /cpdrills/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arujbansal/cpdrills/HEAD/cpdrills/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /cpdrills/landing/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class LandingConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'landing' 7 | -------------------------------------------------------------------------------- /cpdrills/training/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TrainingConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'training' 7 | -------------------------------------------------------------------------------- /cpdrills/cpdrills/settings/development.py: -------------------------------------------------------------------------------- 1 | import os 2 | from cpdrills.settings.base import * 3 | 4 | DEBUG = True 5 | 6 | STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] 7 | 8 | ALLOWED_HOSTS = ['*'] 9 | 10 | -------------------------------------------------------------------------------- /cpdrills/accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'accounts' 7 | 8 | def ready(self): 9 | import accounts.signals 10 | -------------------------------------------------------------------------------- /cpdrills/landing/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('', views.home, name='home'), 6 | path('privacy-policy', views.privacy_policy, name='privacy_policy'), 7 | path('contact-us', views.contact_us, name='contact') 8 | ] 9 | -------------------------------------------------------------------------------- /cpdrills/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import UserProfile 3 | 4 | 5 | class UserProfileAdmin(admin.ModelAdmin): 6 | list_display = ('user', 'country', 'gender', 'codeforces_handle') 7 | 8 | 9 | admin.site.register(UserProfile, UserProfileAdmin) 10 | -------------------------------------------------------------------------------- /cpdrills/static/javascript/training/main_pages/bs-init.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | 3 | var charts = document.querySelectorAll('[data-bss-chart]'); 4 | 5 | for (var chart of charts) { 6 | chart.chart = new Chart(chart, JSON.parse(chart.dataset.bssChart)); 7 | } 8 | }, false); -------------------------------------------------------------------------------- /env_template.txt: -------------------------------------------------------------------------------- 1 | DJANGO_SETTINGS_MODULE = cpdrills.settings.development 2 | SECRET_KEY = 3 | 4 | API_URL = http://0.0.0.0:8080 5 | 6 | You don't need to fill these fields to run the app: 7 | EMAIL_HOST = 8 | EMAIL_PORT = 9 | EMAIL_USE_TLS = 10 | EMAIL_HOST_USER = 11 | EMAIL_HOST_PASSWORD = 12 | EMAIL_BACKEND = 13 | DEFAULT_FROM_EMAIL = -------------------------------------------------------------------------------- /cpdrills/accounts/tokens.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.tokens import PasswordResetTokenGenerator 2 | import six 3 | 4 | 5 | class TokenGenerator(PasswordResetTokenGenerator): 6 | def _make_hash_value(self, user, timestamp): 7 | return six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active) 8 | 9 | 10 | generate_token = TokenGenerator() 11 | -------------------------------------------------------------------------------- /cpdrills/templates/accounts/email_verify.html: -------------------------------------------------------------------------------- 1 | Hi, {{ user.username }}! 2 | 3 | You're receiving this email because you created an account with this email at www.cpdrills.com. 4 | 5 | Please click the link below to verify your email: 6 | 7 | http://{{ domain }}{% url 'activate' uidb64=uid token=token %} 8 | 9 | Thanks for using CP Drills! 10 | 11 | Aruj Bansal 12 | Founder of CP Drills 13 | -------------------------------------------------------------------------------- /cpdrills/cpdrills/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for cpdrills project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /cpdrills/training/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('train', views.train, name='train'), 6 | path('train/practice//', views.practice, name='practice'), 7 | path('train/speed', views.speed_train, name='speed_train'), 8 | path('ajax/update_status', views.problem_status_update, name='problem_status_update'), 9 | ] 10 | -------------------------------------------------------------------------------- /cpdrills/accounts/signals.py: -------------------------------------------------------------------------------- 1 | from .models import UserProfile 2 | from django.contrib.auth.models import User 3 | from django.db.models.signals import post_save 4 | from django.dispatch import receiver 5 | 6 | 7 | @receiver(post_save, sender=User) 8 | def create_and_save_user_profile(sender, instance, created, **kwargs): 9 | if created: 10 | UserProfile.objects.create(user=instance) 11 | instance.userprofile.save() 12 | 13 | -------------------------------------------------------------------------------- /cpdrills/templates/landing/privacy_policy.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block title %} 5 | Privacy Policy | CP Drills 6 | {% endblock title %} 7 | 8 | {% block content %} 9 |
10 |
11 | 12 |
13 |
14 | {% endblock content %} -------------------------------------------------------------------------------- /cpdrills/training/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.contrib.auth.models import User 3 | 4 | 5 | class UserProfileTests(TestCase): 6 | """ 7 | Tests creation of a UserProfile post the creation of the respective 'User' model. 8 | """ 9 | 10 | @classmethod 11 | def setUpTestData(cls): 12 | obj = User.objects.create() 13 | 14 | def test_profile(self): 15 | profile = User.objects.last().userprofile 16 | -------------------------------------------------------------------------------- /cpdrills/cpdrills/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for cpdrills project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cpdrills.settings.development') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /cpdrills/accounts/migrations/0002_userprofile_read_terms.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-01-14 07:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='userprofile', 15 | name='read_terms', 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /cpdrills/cpdrills/settings/production.py: -------------------------------------------------------------------------------- 1 | from cpdrills.settings.base import * 2 | 3 | DEBUG = False 4 | 5 | ALLOWED_HOSTS = [] 6 | 7 | CSRF_TRUSTED_ORIGINS = [] 8 | 9 | DATABASES = { 10 | 'default': { 11 | 'ENGINE': '', 12 | 'NAME': '', 13 | 'USER': '', 14 | 'PASSWORD': '', 15 | 'HOST': '', 16 | 'OPTIONS': { 17 | 'sql_mode': 'STRICT_TRANS_TABLES' 18 | }, 19 | } 20 | } 21 | 22 | CSRF_COOKIE_SECURE = True 23 | SESSION_COOKIE_SECURE = True 24 | 25 | STATIC_ROOT = "/home/arujbansal/CP-Platform/cpdrills/static" 26 | -------------------------------------------------------------------------------- /cpdrills/landing/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.contrib.auth import get_user_model 3 | from accounts.models import UserProfile 4 | 5 | User = get_user_model() 6 | 7 | 8 | def home(request): 9 | user_cnt = User.objects.all().count() 10 | countries_cnt = UserProfile.objects.values("country").distinct().count() 11 | return render(request, 'landing/index.html', {"user_cnt" : user_cnt, 12 | "countries_cnt" : countries_cnt}) 13 | 14 | 15 | def privacy_policy(request): 16 | return render(request, 'landing/privacy_policy.html') 17 | 18 | 19 | def contact_us(request): 20 | return render(request, 'landing/contact_us.html') 21 | -------------------------------------------------------------------------------- /cpdrills/accounts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django_countries.fields import CountryField 3 | from django.contrib.auth.models import User 4 | 5 | GENDER_CHOICES = (('Male', 'Male'), ('Female', 'Female'), ('Other', 'Other')) 6 | 7 | 8 | class UserProfile(models.Model): 9 | """ 10 | Extended user profile. 11 | """ 12 | 13 | user = models.OneToOneField(User, on_delete=models.CASCADE) 14 | country = CountryField() 15 | gender = models.CharField(max_length=6, choices=GENDER_CHOICES) 16 | codeforces_handle = models.CharField(max_length=255, blank=True) 17 | read_terms = models.BooleanField(default=True) 18 | 19 | def __str__(self): 20 | return self.user.username 21 | -------------------------------------------------------------------------------- /cpdrills/training/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from . import models as my_models 3 | 4 | admin.site.site_header = 'CP Drills Administration' 5 | admin.site.site_title = 'CP Drills' 6 | 7 | admin.site.register(my_models.Problem) 8 | admin.site.register(my_models.ProblemTopic) 9 | admin.site.register(my_models.OnlineJudge) 10 | 11 | 12 | class ProblemStatusAdmin(admin.ModelAdmin): 13 | list_display = ('userprofile', 'problem', 'solve_time', 'solve_duration') 14 | 15 | 16 | class SubcategoryAdmin(admin.ModelAdmin): 17 | list_display = ('name', 'problem_topic') 18 | 19 | 20 | admin.site.register(my_models.ProblemStatus, ProblemStatusAdmin) 21 | admin.site.register(my_models.Subcategory, SubcategoryAdmin) 22 | -------------------------------------------------------------------------------- /cpdrills/templates/landing/contact_us.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block title %} 5 | Contact Us | CP Drills 6 | {% endblock title %} 7 | 8 | {% block head %} 9 | 14 | {% endblock head %} 15 | 16 | {% block content %} 17 |
18 |
19 | 22 |
23 |
24 | {% endblock content %} -------------------------------------------------------------------------------- /cpdrills/static/javascript/bs-init.js: -------------------------------------------------------------------------------- 1 | 2 | if (window.innerWidth < 768) { 3 | [].slice.call(document.querySelectorAll('[data-bss-disabled-mobile]')).forEach(function (elem) { 4 | elem.classList.remove('animated'); 5 | elem.removeAttribute('data-bss-hover-animate'); 6 | elem.removeAttribute('data-aos'); 7 | }); 8 | } 9 | 10 | document.addEventListener('DOMContentLoaded', function() { 11 | 12 | var hoverAnimationTriggerList = [].slice.call(document.querySelectorAll('[data-bss-hover-animate]')); 13 | var hoverAnimationList = hoverAnimationTriggerList.forEach(function (hoverAnimationEl) { 14 | hoverAnimationEl.addEventListener('mouseenter', function(e){ e.target.classList.add('animated', e.target.dataset.bssHoverAnimate) }); 15 | hoverAnimationEl.addEventListener('mouseleave', function(e){ e.target.classList.remove('animated', e.target.dataset.bssHoverAnimate) }); 16 | }); 17 | }, false); -------------------------------------------------------------------------------- /cpdrills/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | 5 | import sys 6 | 7 | from dotenv import load_dotenv 8 | from cpdrills.settings.base import BASE_DIR 9 | load_dotenv('../.env') 10 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', str(os.getenv('DJANGO_SETTINGS_MODULE'))) 11 | 12 | 13 | def main(): 14 | """Run administrative tasks.""" 15 | try: 16 | from django.core.management import execute_from_command_line 17 | except ImportError as exc: 18 | raise ImportError( 19 | "Couldn't import Django. Are you sure it's installed and " 20 | "available on your PYTHONPATH environment variable? Did you " 21 | "forget to activate a virtual environment?" 22 | ) from exc 23 | execute_from_command_line(sys.argv) 24 | 25 | 26 | if __name__ == '__main__': 27 | main() 28 | -------------------------------------------------------------------------------- /cpdrills/cpdrills/urls.py: -------------------------------------------------------------------------------- 1 | """cpdrills URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('', include('landing.urls')), 22 | path('', include('training.urls')), 23 | path('', include('accounts.urls')), 24 | ] 25 | -------------------------------------------------------------------------------- /cpdrills/accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | from django.contrib.auth import views as auth_views 4 | 5 | urlpatterns = [ 6 | path('signup', views.sign_up, name='sign_up'), 7 | path('signin', views.sign_in, name='sign_in'), 8 | path('logout', views.user_logout, name='logout'), 9 | path('edit-profile', views.profile_edit, name='profile_edit'), 10 | path('activate-user//', views.activate_user, name='activate'), 11 | path('password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'), 12 | path('password_change/done', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'), 13 | path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'), 14 | path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'), 15 | path('reset///', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'), 16 | path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete') 17 | ] 18 | -------------------------------------------------------------------------------- /cpdrills/accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-01-08 04:41 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | import django_countries.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='UserProfile', 20 | fields=[ 21 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('country', django_countries.fields.CountryField(max_length=2)), 23 | ('gender', models.CharField(choices=[('Male', 'Male'), ('Female', 'Female'), ('Other', 'Other')], max_length=6)), 24 | ('codeforces_handle', models.CharField(blank=True, max_length=255)), 25 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 26 | ], 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /cpdrills/training/templatetags/training_extras.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from django import template 4 | from training.models import Problem, ProblemStatus 5 | from datetime import datetime 6 | import time 7 | 8 | register = template.Library() 9 | 10 | 11 | @register.filter 12 | def check_solve(prob_code, cur_username): 13 | """ 14 | Returns the solved status of a problem for a user 15 | """ 16 | 17 | user_problem_status = ProblemStatus.objects.filter(problem_id=prob_code).filter( 18 | userprofile__user__username=cur_username).first() 19 | 20 | if user_problem_status is None: 21 | return "False", "" 22 | else: 23 | time = user_problem_status.solve_duration 24 | 25 | hours = str(math.floor(time / 60)) 26 | minutes = str(math.floor(time % 60)) 27 | seconds = str(round((time - math.floor(time)) * 60)) 28 | 29 | if len(hours) == 1: hours = "0" + hours 30 | if len(minutes) == 1: minutes = "0" + minutes 31 | if len(seconds) == 1: seconds = "0" + seconds; 32 | 33 | res = hours + ":" + minutes + ":" + seconds 34 | 35 | if time == -1: res = "Unknown" 36 | return "True", res 37 | 38 | 39 | @register.filter 40 | def get_js_timestamp(py_time): 41 | return datetime.timestamp(py_time) * 1000 42 | -------------------------------------------------------------------------------- /cpdrills/utils/problems.py: -------------------------------------------------------------------------------- 1 | import csv 2 | # from training.models import Problem, OnlineJudge, Subcategory 3 | 4 | # file = open('/Users/arujbansal/Desktop/CPPLAT/CP-Platform/cpdrills/utils/problems3.csv') 5 | 6 | # csvreader = csv.reader(file) 7 | 8 | rows = [] 9 | # for row in csvreader: 10 | # row = row[0].split(' - ') 11 | # rows.append(row) 12 | 13 | for i in range(0, 22): 14 | row = input().split(' - ') 15 | rows.append(row) 16 | # print(row) 17 | 18 | for row in rows: 19 | # oj = OnlineJudge.objects.get(name="Codeforces") 20 | 21 | contest_num = "" 22 | p_code = "" 23 | 24 | for i in range(0, len(row[0])): 25 | if row[0][i].isalpha(): break 26 | contest_num += row[0][i] 27 | 28 | if row[0][-1].isalpha(): 29 | p_code = row[0][-1] 30 | else: 31 | p_code = row[0][-2] + row[0][-1] 32 | 33 | p_link = "https://codeforces.com/problemset/problem/" + contest_num + '/' + p_code 34 | 35 | print(contest_num + p_code, row[1], p_link) 36 | # p = Problem(code="CF" + contest_num + p_code, name=row[1], source=oj, rating=1900, link=p_link, ordering_code=3000) 37 | # p.save() 38 | # 39 | # cat = Subcategory.objects.get(name="Level 6") 40 | # cat.problems.add(p) 41 | # cat.save() 42 | # 43 | # # print(rows) 44 | # 45 | # file.close() 46 | -------------------------------------------------------------------------------- /cpdrills/utils/codeforces.py: -------------------------------------------------------------------------------- 1 | rating_correspondence = {} 2 | 3 | 4 | def setup_rating_dict(): 5 | level = 1 6 | for rating in range(800, 3500, 100): 7 | rating_correspondence[rating] = level 8 | 9 | if rating % 2 != 0: 10 | level += 1 11 | 12 | 13 | setup_rating_dict() 14 | 15 | 16 | def code_extract_contest(problem_url): 17 | code = "" 18 | 19 | count = 0 20 | for i in range(len(problem_url) - 1, -1, -1): 21 | if problem_url[i] == '/': 22 | count += 1 23 | continue 24 | 25 | if count == 1: 26 | continue 27 | 28 | if count >= 3: 29 | break 30 | 31 | code += problem_url[i] 32 | 33 | return code[::-1] 34 | 35 | 36 | def code_extract_problemset(problem_url): 37 | code = "" 38 | 39 | count = 0 40 | for i in range(len(problem_url) - 1, -1, -1): 41 | if problem_url[i] == '/': 42 | count += 1 43 | continue 44 | 45 | if count >= 2: 46 | break 47 | 48 | code += problem_url[i] 49 | 50 | return code[::-1] 51 | 52 | 53 | def code_extractor(problem_url): 54 | if "contest" in problem_url: 55 | return "CF" + code_extract_contest(problem_url) 56 | else: 57 | return "CF" + code_extract_problemset(problem_url) 58 | -------------------------------------------------------------------------------- /cpdrills/static/css/training/authentication/sign_up.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | height: 100%; 3 | } 4 | 5 | body.my-login-page { 6 | background-color: #f7f9fb; 7 | font-size: 14px; 8 | } 9 | 10 | .my-login-page .brand { 11 | width: 90px; 12 | height: 90px; 13 | overflow: hidden; 14 | border-radius: 50%; 15 | margin: 40px auto; 16 | box-shadow: 0 4px 8px rgba(0,0,0,.05); 17 | position: relative; 18 | z-index: 1; 19 | } 20 | 21 | .my-login-page .brand img { 22 | width: 100%; 23 | } 24 | 25 | .my-login-page .card-wrapper { 26 | width: 400px; 27 | } 28 | 29 | .my-login-page .card { 30 | border-color: transparent; 31 | box-shadow: 0 4px 8px rgba(0,0,0,.05); 32 | } 33 | 34 | .my-login-page .card.fat { 35 | padding: 10px; 36 | } 37 | 38 | .my-login-page .card .card-title { 39 | margin-bottom: 30px; 40 | } 41 | 42 | .my-login-page .form-control { 43 | border-width: 2.3px; 44 | } 45 | 46 | .my-login-page .form-group label { 47 | width: 100%; 48 | } 49 | 50 | .my-login-page .btn.btn-block { 51 | padding: 12px 10px; 52 | } 53 | 54 | .my-login-page .footer { 55 | margin: 40px 0; 56 | color: #888; 57 | text-align: center; 58 | } 59 | 60 | @media screen and (max-width: 425px) { 61 | .my-login-page .card-wrapper { 62 | width: 90%; 63 | margin: 0 auto; 64 | } 65 | } 66 | 67 | @media screen and (max-width: 320px) { 68 | .my-login-page .card.fat { 69 | padding: 0; 70 | } 71 | 72 | .my-login-page .card.fat .card-body { 73 | padding: 15px; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /cpdrills/templates/accounts/sign_up.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_filters %} 3 | 4 | {% block title %} 5 | Sign Up | CP Drills 6 | {% endblock title %} 7 | 8 | {% block head %} 9 | 14 | {% endblock head %} 15 | 16 | {% block content %} 17 | 23 | 24 |
25 |
26 |
27 |
Sign up
28 |
29 |
30 | {% csrf_token %} 31 | {{ form | crispy }} 32 | 33 |
34 |
35 | Already have an account? 36 |
37 |
38 |
39 | {% endblock content %} -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.9.5 2 | aiosignal==1.3.1 3 | annotated-types==0.7.0 4 | anyio==4.4.0 5 | asgiref==3.7.2 6 | async-timeout==4.0.3 7 | attrs==23.1.0 8 | beautifulsoup4==4.12.2 9 | certifi==2023.7.22 10 | cffi==1.16.0 11 | charset-normalizer==3.3.0 12 | click==8.1.7 13 | crispy-bootstrap5==2024.2 14 | cryptography==41.0.4 15 | defusedxml==0.7.1 16 | Django==4.2.6 17 | django-bootstrap4==23.2 18 | django-countries==7.5.1 19 | django-crispy-forms==2.0 20 | django-utils-six==2.0 21 | dnspython==2.6.1 22 | email_validator==2.2.0 23 | fastapi==0.111.0 24 | fastapi-cli==0.0.4 25 | frozenlist==1.4.0 26 | h11==0.14.0 27 | httpcore==1.0.5 28 | httptools==0.6.1 29 | httpx==0.27.0 30 | idna==3.4 31 | Jinja2==3.1.4 32 | markdown-it-py==3.0.0 33 | MarkupSafe==2.1.5 34 | mdurl==0.1.2 35 | multidict==6.0.4 36 | numpy==1.26.0 37 | oauthlib==3.2.2 38 | orjson==3.10.6 39 | packaging==23.2 40 | pandas==2.1.1 41 | pip-review==1.3.0 42 | plotly==5.17.0 43 | pycparser==2.21 44 | pydantic==2.8.2 45 | pydantic_core==2.20.1 46 | Pygments==2.18.0 47 | PyJWT==2.8.0 48 | pyparsing==3.1.1 49 | python-dateutil==2.8.2 50 | python-dotenv==1.0.1 51 | python-multipart==0.0.9 52 | python3-openid==3.2.0 53 | pytz==2023.3.post1 54 | PyYAML==6.0.1 55 | requests==2.31.0 56 | requests-oauthlib==1.3.1 57 | rich==13.7.1 58 | setuptools==70.3.0 59 | shellingham==1.5.4 60 | six==1.16.0 61 | sniffio==1.3.1 62 | soupsieve==2.5 63 | sqlparse==0.4.4 64 | starlette==0.37.2 65 | tenacity==8.2.3 66 | typer==0.12.3 67 | typing_extensions==4.12.2 68 | tzdata==2024.1 69 | ujson==5.10.0 70 | urllib3==2.0.6 71 | uvicorn==0.30.1 72 | uvloop==0.19.0 73 | watchfiles==0.22.0 74 | websockets==12.0 75 | yarl==1.9.2 76 | -------------------------------------------------------------------------------- /cpdrills/static/images/landing_computer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpdrills/templates/accounts/profile_edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_filters %} 3 | 4 | {% block title %} 5 | Edit Profile | CP Drills 6 | {% endblock title %} 7 | 8 | {% block head %} 9 | 14 | {% endblock head %} 15 | 16 | {% block content %} 17 |
18 |
19 |
20 |
Edit Profile
21 | 22 | {% if messages %} 23 | {% for message in messages %} 24 | 26 | {% endfor %} 27 | {% endif %} 28 | 29 |
30 |
31 | {% csrf_token %} 32 |
33 | {{ user_form | crispy }} 34 |
35 | 36 |
37 | {{ userprofile_form | crispy }} 38 |
39 | 40 |
41 |
42 |
43 | Change 44 | Password 45 | 46 |
47 |
48 |
49 |
50 | {% endblock content %} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpdrills 2 | 3 | CP Drills (Competitive Programming Drills) is a training platform designed to help increase your problem-solving speed by giving you access to several analytics. I no longer host the website, so its source code is publicly available to be run locally! You can look at the platform temporarily hosted [here](https://arujbansal.pythonanywhere.com/) but it is likely that you cannot access practice features here. You can download and run the code locally by following the [usage guide](#usage-guide). 4 | 5 | If you fork this repository and work on your own version, I ask that you link this repository at the start of your README to give credit. 6 | 7 | ## Usage Guide 8 | 1. Clone this repository, create a python virtual environment, and install the requirements: 9 | ``` 10 | $ pip install -r requirements.txt 11 | ``` 12 | 3. Create a `.env` file using `env_template.txt` and fill in the required fields. 13 | 4. Initialise the database: 14 | ``` 15 | $ python manage.py migrate 16 | $ python manage.py makemigrations 17 | ``` 18 | 5. Create a super user to login to the website: 19 | ``` 20 | $ python manage.py createsuperuser 21 | ``` 22 | 6. Run the FastAPI server used to query the Codeforces API: 23 | ``` 24 | $ uvicorn cf_api_server:app --host 0.0.0.0 --port 8080 25 | ``` 26 | 7. Start the django app in the cpdrills directory: 27 | ``` 28 | $ cd cpdrills 29 | $ python manage.py runserver 30 | ``` 31 | 8. Open the website, login with your superuser account, and add your Codeforces handle to your profile. You may need to navigate to /admin and update your superuser profile's details if you cannot directly save your profile. 32 | 33 | Note: If something breaks, it is likely that a path or URL needs to be updated. Just follow the error and change the required paths. Check the FastAPI endpoint. 34 | -------------------------------------------------------------------------------- /cpdrills/static/images/signup_computer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /cpdrills/accounts/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import UserCreationForm 3 | from django.contrib.auth.models import User 4 | from django_countries.fields import CountryField 5 | from .models import UserProfile 6 | from django.utils.functional import lazy 7 | from django.urls import reverse 8 | 9 | 10 | class MyUserCreationForm(UserCreationForm): 11 | email = forms.EmailField(required=True) 12 | country = CountryField().formfield(help_text="Used only for calculating website user statistics.") 13 | gender = forms.ChoiceField(required=True, choices=(('Female', 'Female'), ('Male', 'Male'), ('Other', 'Other')), 14 | help_text="Used only for calculating website user statistics.") 15 | codeforces_handle = forms.CharField(widget=forms.TextInput(attrs={'class': 'mb-3'})) 16 | read_terms = forms.BooleanField(required=True, initial=True, 17 | label=lazy(lambda: ( 18 | "I acknowledge that I have read the privacy policy." % reverse( 19 | 'privacy_policy')))) 20 | 21 | class Meta: 22 | model = User 23 | fields = ('email', 'password1', 'password2', 'username', 'country', 'gender', 'codeforces_handle', 'read_terms') 24 | 25 | def __init__(self, *args, **kwargs): 26 | super().__init__(*args, **kwargs) 27 | self.fields["codeforces_handle"].required = False 28 | 29 | 30 | class UserEditForm(forms.ModelForm): 31 | class Meta: 32 | model = User 33 | fields = ('email', 'username', 'first_name', 'last_name') 34 | 35 | def __init__(self, *args, **kwargs): 36 | super().__init__(*args, **kwargs) 37 | self.fields["email"].disabled = True 38 | self.fields["username"].disabled = True 39 | 40 | 41 | class UserProfileEditForm(forms.ModelForm): 42 | class Meta: 43 | model = UserProfile 44 | fields = ('codeforces_handle', 'gender', 'country') 45 | 46 | def __init__(self, *args, **kwargs): 47 | super().__init__(*args, **kwargs) 48 | # self.fields["gender"].disabled = True 49 | self.fields["country"].disabled = True 50 | self.fields["codeforces_handle"].required = False 51 | self.fields["gender"].help_text = "Used only for calculating website user statistics." 52 | self.fields["country"].help_text = "Used only for calculating website user statistics." 53 | -------------------------------------------------------------------------------- /cpdrills/templates/accounts/sign_in.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_filters %} 3 | 4 | {% block title %} 5 | Sign In | CP Drills 6 | {% endblock title %} 7 | 8 | {{% block head %} 9 | 14 | {% endblock head %} 15 | 16 | {% block content %} 17 | 23 | 24 |
25 |
26 |
27 |
Sign In
28 | 29 | {% if messages %} 30 |
31 | {% for message in messages %} 32 | {% if message.tags == "error" %} 33 |
34 | {{ message }} 35 |
36 | {% else %} 37 |
38 | {{ message }} 39 |
40 | {% endif %} 41 | {% endfor %} 42 |
43 | {% endif %} 44 | 45 |
46 |
47 | {% csrf_token %} 48 | {{ form | crispy }} 49 | 50 |
51 |
52 | 59 |
60 |
61 |
62 | {% endblock content %} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | cpdrills/db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | .DS_Store 133 | .idea/ 134 | features.txt 135 | -------------------------------------------------------------------------------- /cpdrills/training/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django.utils import timezone 4 | from accounts.models import UserProfile 5 | 6 | 7 | class OnlineJudge(models.Model): 8 | """ 9 | Online judge consisting of problems. 10 | """ 11 | 12 | name = models.CharField(max_length=256) 13 | 14 | def __str__(self): 15 | return self.name 16 | 17 | 18 | class Problem(models.Model): 19 | """ 20 | Actual problem itself. 21 | """ 22 | 23 | code = models.CharField(max_length=20, primary_key=True) 24 | name = models.CharField(max_length=256) 25 | source = models.ForeignKey(OnlineJudge, on_delete=models.CASCADE) 26 | rating = models.IntegerField() 27 | link = models.TextField() 28 | # tags = models.TextField(required=False) 29 | # editorial = models.TextField(blank=True) 30 | solvers = models.ManyToManyField(UserProfile, through='ProblemStatus') # Users who have solved this problem 31 | ordering_code = models.PositiveSmallIntegerField(default=3000) 32 | 33 | def __str__(self): 34 | return self.name 35 | 36 | # class Meta: 37 | # ordering = [''] 38 | 39 | 40 | class ProblemStatus(models.Model): 41 | """ 42 | Information about a particular problem for a particular user. 43 | """ 44 | 45 | userprofile = models.ForeignKey(UserProfile, on_delete=models.CASCADE) 46 | problem = models.ForeignKey(Problem, on_delete=models.CASCADE) 47 | solve_time = models.DateTimeField(default=timezone.now) # Time at which the problem was solved 48 | solve_duration = models.FloatField(default=-1) # Time taken to solve the problem 49 | speed_attempt = models.BooleanField(default=False) 50 | 51 | class Meta: 52 | ordering = ['solve_time'] 53 | 54 | 55 | class ProblemTopic(models.Model): 56 | """ 57 | Main category of problem. 58 | E.g. Dynamic Programming, Graph Theory, Sorting and Searching 59 | """ 60 | 61 | name = models.CharField(max_length=256) 62 | 63 | def __str__(self): 64 | return self.name 65 | 66 | 67 | class Subcategory(models.Model): 68 | """ 69 | Tracks within problem categories. 70 | E.g. Beginner, Classical, Level 1, Level 2, Level 3 71 | """ 72 | 73 | name = models.CharField(max_length=256) 74 | problem_topic = models.ForeignKey(ProblemTopic, on_delete=models.CASCADE, related_name="subcategoryOf") 75 | problems = models.ManyToManyField(Problem) 76 | 77 | def __str__(self): 78 | return self.problem_topic.name + " - " + self.name 79 | 80 | class Meta: 81 | ordering = ['name'] 82 | -------------------------------------------------------------------------------- /cpdrills/static/javascript/training/main_pages/theme.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; // Start of use strict 3 | 4 | var sidebar = document.querySelector('.sidebar'); 5 | var sidebarToggles = document.querySelectorAll('#sidebarToggle, #sidebarToggleTop'); 6 | 7 | if (sidebar) { 8 | 9 | var collapseEl = sidebar.querySelector('.collapse'); 10 | var collapseElementList = [].slice.call(document.querySelectorAll('.sidebar .collapse')) 11 | var sidebarCollapseList = collapseElementList.map(function (collapseEl) { 12 | return new bootstrap.Collapse(collapseEl, { toggle: false }); 13 | }); 14 | 15 | for (var toggle of sidebarToggles) { 16 | 17 | // Toggle the side navigation 18 | toggle.addEventListener('click', function(e) { 19 | document.body.classList.toggle('sidebar-toggled'); 20 | sidebar.classList.toggle('toggled'); 21 | 22 | if (sidebar.classList.contains('toggled')) { 23 | for (var bsCollapse of sidebarCollapseList) { 24 | bsCollapse.hide(); 25 | } 26 | }; 27 | }); 28 | } 29 | 30 | // Close any open menu accordions when window is resized below 768px 31 | window.addEventListener('resize', function() { 32 | var vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); 33 | 34 | if (vw < 768) { 35 | for (var bsCollapse of sidebarCollapseList) { 36 | bsCollapse.hide(); 37 | } 38 | }; 39 | }); 40 | } 41 | 42 | // Prevent the content wrapper from scrolling when the fixed side navigation hovered over 43 | 44 | var fixedNaigation = document.querySelector('body.fixed-nav .sidebar'); 45 | 46 | if (fixedNaigation) { 47 | fixedNaigation.on('mousewheel DOMMouseScroll wheel', function(e) { 48 | var vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); 49 | 50 | if (vw > 768) { 51 | var e0 = e.originalEvent, 52 | delta = e0.wheelDelta || -e0.detail; 53 | this.scrollTop += (delta < 0 ? 1 : -1) * 30; 54 | e.preventDefault(); 55 | } 56 | }); 57 | } 58 | 59 | var scrollToTop = document.querySelector('.scroll-to-top'); 60 | 61 | if (scrollToTop) { 62 | 63 | // Scroll to top button appear 64 | window.addEventListener('scroll', function() { 65 | var scrollDistance = window.pageYOffset; 66 | 67 | //check if user is scrolling up 68 | if (scrollDistance > 100) { 69 | scrollToTop.style.display = 'block'; 70 | } else { 71 | scrollToTop.style.display = 'none'; 72 | } 73 | }); 74 | } 75 | 76 | })(); // End of use strict 77 | -------------------------------------------------------------------------------- /cpdrills/static/javascript/stopwatch.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | let seconds = 0; 3 | let hours = 0; 4 | let tens = 0; 5 | let minutes = 0; 6 | let appendTens = 0; 7 | let appendSeconds = document.getElementById("seconds") 8 | let appendMinutes = document.getElementById("minutes") 9 | let appendHours = document.getElementById("hours") 10 | let buttonStart = document.getElementById('button-start'); 11 | let buttonStop = document.getElementById('button-stop'); 12 | let buttonReset = document.getElementById('button-reset'); 13 | let problemLink = document.getElementById('problem_url'); 14 | let Interval; 15 | let running = false; 16 | let clicked_before = false; 17 | 18 | problemLink.onclick = function () { 19 | if (clicked_before) return; 20 | clicked_before = true; 21 | 22 | buttonStart.click() 23 | } 24 | 25 | buttonStart.onclick = function () { 26 | clearInterval(Interval); 27 | Interval = setInterval(startTimer, 10); 28 | running = true; 29 | 30 | console.log("clicked") 31 | 32 | seconds = parseInt(document.getElementById("seconds").value); 33 | minutes = parseInt(document.getElementById("minutes").value); 34 | hours = parseInt(document.getElementById("hours").value); 35 | } 36 | 37 | buttonStop.onclick = function () { 38 | clearInterval(Interval); 39 | running = false; 40 | } 41 | 42 | buttonReset.onclick = function () { 43 | clearInterval(Interval); 44 | tens = 0; 45 | seconds = 0; 46 | minutes = 0; 47 | hours = 0; 48 | appendTens.value = "00"; 49 | appendSeconds.value = "00"; 50 | appendMinutes.value = "00"; 51 | appendHours.value = "00"; 52 | } 53 | 54 | function startTimer() { 55 | tens++; 56 | 57 | if (tens <= 9) 58 | appendTens.value = "0" + tens; 59 | 60 | if (tens > 9) 61 | appendTens.value = tens; 62 | 63 | if (minutes > 59) { 64 | hours++; 65 | appendHours.value = hours; 66 | minutes = 0; 67 | appendMinutes.value = "0" + 0; 68 | } 69 | 70 | if (seconds > 59) { 71 | minutes++; 72 | appendMinutes.value = minutes; 73 | seconds = 0; 74 | appendSeconds.value = "0" + 0; 75 | } 76 | 77 | if (tens > 99) { 78 | console.log("seconds"); 79 | seconds++; 80 | appendSeconds.value = "0" + seconds; 81 | tens = 0; 82 | appendTens.value = "0" + 0; 83 | } 84 | 85 | if (seconds > 9) { 86 | appendSeconds.value = seconds; 87 | } 88 | 89 | if (minutes < 9) appendMinutes.value = "0" + minutes; 90 | } 91 | } -------------------------------------------------------------------------------- /cpdrills/training/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | TAG_CHOICES = [('dp', 'Dynamic Programming'), 4 | ('graphs', 'Graph Theory'), 5 | ('strings', 'Strings'), 6 | ('brute force', 'Brute Force'), 7 | ('dsu', 'Union Find Disjoint Set'), 8 | ('binary search', 'Binary Search'), 9 | ('data structures', 'Data Structures'), 10 | ('constructive algorithms', 'Constructive Algorithms'), 11 | ('2-sat', '2-Satisfiability'), 12 | ('bitmasks', 'Bitmasks'), 13 | ('chinese remainder theorem', 'Chinese Remainder Theorem'), 14 | ('combinatorics', 'Combinatorics'), 15 | ('dfs and similar', 'DFS and Similar'), 16 | ('expression parsing', 'Expression Parsing'), 17 | ('fft', 'Fast Fourier Transform'), 18 | ('flows', 'Flows'), 19 | ('games', 'Game Theory'), 20 | ('greedy', 'Greedy'), 21 | ('hashing', 'Hashing'), 22 | ('implementation', 'Implementation'), 23 | ('interactive', 'Interactive'), 24 | ('math', 'Math'), 25 | ('matrices', 'Matrices'), 26 | ('meet-in-the-middle', 'Meet In The Middle'), 27 | ('number theory', 'Number Theory'), 28 | ('probabilities', 'Probability'), 29 | ('schedules', 'Schedules'), 30 | ('shortest paths', 'Shortest Paths'), 31 | ('sortings', 'Sorting'), 32 | ('string suffix structures', 'String Suffix Structures'), 33 | ('strings', 'Strings'), 34 | ('ternary search', 'Ternary Search'), 35 | ('trees', 'Trees'), 36 | ('two pointers', 'Two Pointers'), 37 | ] 38 | 39 | RATING_CHOICES = ((800, 800), (900, 900)) 40 | 41 | CONTEST_CHOICES = (('Div. 1', 'Div. 1'), ('(Div. 2)', 'Div. 2'), ('Div. 3', 'Div. 3'), ('Edu', 'Div. 2 Edu'), 42 | ('ICPC', 'ICPC Related')) 43 | 44 | PROBLEM_CHOICES = (('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D'), ('E', 'E'), ('F', 'F'), ('G', 'G'), ('H', 'H'), 45 | ('I', 'I')) 46 | 47 | 48 | class SpeedTrainProblemForm(forms.Form): 49 | rating = forms.ChoiceField( 50 | help_text="Choose a codeforces rating from the dropdown. Selecting topics and contest type is optional.", 51 | choices=[(x, x) for x in range(800, 3600, 100)], required=True) 52 | 53 | topics = forms.MultipleChoiceField(choices=TAG_CHOICES, 54 | widget=forms.CheckboxSelectMultiple, required=False, 55 | help_text="Optional. Recommends problems of random topics if none selected.") 56 | 57 | contest_type = forms.MultipleChoiceField(choices=CONTEST_CHOICES, 58 | widget=forms.CheckboxSelectMultiple, required=False, 59 | label='Contest Type') 60 | 61 | problem_type = forms.MultipleChoiceField(choices=PROBLEM_CHOICES, 62 | widget=forms.CheckboxSelectMultiple, required=False, 63 | label='Problem Type') 64 | -------------------------------------------------------------------------------- /cpdrills/training/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-01-08 04:41 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import django.utils.timezone 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('accounts', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='OnlineJudge', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('name', models.CharField(max_length=256)), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name='Problem', 26 | fields=[ 27 | ('code', models.CharField(max_length=20, primary_key=True, serialize=False)), 28 | ('name', models.CharField(max_length=256)), 29 | ('rating', models.IntegerField()), 30 | ('link', models.TextField()), 31 | ('ordering_code', models.PositiveSmallIntegerField(default=3000)), 32 | ], 33 | ), 34 | migrations.CreateModel( 35 | name='ProblemTopic', 36 | fields=[ 37 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 38 | ('name', models.CharField(max_length=256)), 39 | ], 40 | ), 41 | migrations.CreateModel( 42 | name='Subcategory', 43 | fields=[ 44 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 45 | ('name', models.CharField(max_length=256)), 46 | ('problem_topic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subcategoryOf', to='training.problemtopic')), 47 | ('problems', models.ManyToManyField(to='training.Problem')), 48 | ], 49 | options={ 50 | 'ordering': ['name'], 51 | }, 52 | ), 53 | migrations.CreateModel( 54 | name='ProblemStatus', 55 | fields=[ 56 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 57 | ('solve_time', models.DateTimeField(default=django.utils.timezone.now)), 58 | ('solve_duration', models.FloatField(default=-1)), 59 | ('speed_attempt', models.BooleanField(default=False)), 60 | ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='training.problem')), 61 | ('userprofile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.userprofile')), 62 | ], 63 | options={ 64 | 'ordering': ['solve_time'], 65 | }, 66 | ), 67 | migrations.AddField( 68 | model_name='problem', 69 | name='solvers', 70 | field=models.ManyToManyField(through='training.ProblemStatus', to='accounts.UserProfile'), 71 | ), 72 | migrations.AddField( 73 | model_name='problem', 74 | name='source', 75 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='training.onlinejudge'), 76 | ), 77 | ] 78 | -------------------------------------------------------------------------------- /cpdrills/static/bootstrap/css2/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.0.2 (https://getbootstrap.com/) 3 | * Copyright 2011-2021 The Bootstrap Authors 4 | * Copyright 2011-2021 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /cpdrills/static/bootstrap/css2/bootstrap-reboot.rtl.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.0.2 (https://getbootstrap.com/) 3 | * Copyright 2011-2021 The Bootstrap Authors 4 | * Copyright 2011-2021 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */ -------------------------------------------------------------------------------- /cpdrills/cpdrills/settings/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for cpdrills project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.8. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | import os 15 | 16 | from dotenv import load_dotenv 17 | dotenv_path = ('../../.env') 18 | load_dotenv(dotenv_path) 19 | 20 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 21 | BASE_DIR = Path(__file__).resolve().parent.parent.parent 22 | 23 | TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates') 24 | 25 | # Quick-start development settings - unsuitable for production 26 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 27 | 28 | # SECURITY WARNING: keep the secret key used in production secret! 29 | SECRET_KEY = str(os.getenv('SECRET_KEY')) 30 | 31 | # SECURITY WARNING: don't run with debug turned on in production! 32 | DEBUG = True 33 | 34 | ALLOWED_HOSTS = [] 35 | 36 | # Application definition 37 | 38 | INSTALLED_APPS = [ 39 | 'django.contrib.admin', 40 | 'django.contrib.auth', 41 | 'django.contrib.contenttypes', 42 | 'django.contrib.sessions', 43 | 'django.contrib.messages', 44 | 'django.contrib.staticfiles', 45 | 'bootstrap4', 46 | 'crispy_forms', 47 | 'crispy_bootstrap5', 48 | 'training.apps.TrainingConfig', 49 | 'accounts.apps.AccountsConfig', 50 | 'landing.apps.LandingConfig', 51 | ] 52 | 53 | MIDDLEWARE = [ 54 | 'django.middleware.security.SecurityMiddleware', 55 | 'django.contrib.sessions.middleware.SessionMiddleware', 56 | 'django.middleware.common.CommonMiddleware', 57 | 'django.middleware.csrf.CsrfViewMiddleware', 58 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 59 | 'django.contrib.messages.middleware.MessageMiddleware', 60 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 61 | ] 62 | 63 | ROOT_URLCONF = 'cpdrills.urls' 64 | 65 | TEMPLATES = [ 66 | { 67 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 68 | 'DIRS': [TEMPLATES_DIR], 69 | 'APP_DIRS': True, 70 | 'OPTIONS': { 71 | 'context_processors': [ 72 | 'django.template.context_processors.debug', 73 | 'django.template.context_processors.request', 74 | 'django.contrib.auth.context_processors.auth', 75 | 'django.contrib.messages.context_processors.messages', 76 | 'django.template.context_processors.request', 77 | ], 78 | }, 79 | }, 80 | ] 81 | 82 | WSGI_APPLICATION = 'cpdrills.wsgi.application' 83 | 84 | # Database 85 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 86 | 87 | DATABASES = { 88 | 'default': { 89 | 'ENGINE': 'django.db.backends.sqlite3', 90 | 'NAME': BASE_DIR / 'db.sqlite3', 91 | } 92 | } 93 | 94 | # Password validation 95 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 96 | 97 | AUTH_PASSWORD_VALIDATORS = [ 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 103 | }, 104 | { 105 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 106 | }, 107 | { 108 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 109 | }, 110 | ] 111 | 112 | # Internationalization 113 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 114 | 115 | LANGUAGE_CODE = 'en-us' 116 | 117 | TIME_ZONE = 'Asia/Kolkata' 118 | 119 | USE_I18N = True 120 | 121 | USE_L10N = True 122 | 123 | USE_TZ = True 124 | 125 | # Static files (CSS, JavaScript, Images) 126 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 127 | 128 | STATIC_URL = '/static/' 129 | STATICFILES_DIRS = [] 130 | 131 | # Default primary key field type 132 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 133 | 134 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 135 | 136 | CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" 137 | CRISPY_TEMPLATE_PACK = 'bootstrap5' 138 | LOGIN_URL = 'sign_in' 139 | 140 | # Email Configuration 141 | EMAIL_HOST = str(os.getenv('EMAIL_HOST')) 142 | EMAIL_PORT = str(os.getenv('EMAIL_PORT')) 143 | EMAIL_USE_TLS = bool(os.getenv('EMAIL_USE_TLS')) 144 | EMAIL_HOST_USER = str(os.getenv('EMAIL_HOST_USER')) 145 | EMAIL_HOST_PASSWORD = str(os.getenv('EMAIL_HOST_PASSWORD')) 146 | EMAIL_BACKEND = str(os.getenv('EMAIL_BACKEND')) 147 | DEFAULT_FROM_EMAIL = str(os.getenv('DEFAULT_FROM_EMAIL')) 148 | 149 | CSRF_TRUSTED_ORIGINS = ['https://127.0.0.1'] -------------------------------------------------------------------------------- /cf_api_server.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | from fastapi import FastAPI, Query 3 | from typing import List 4 | import uvicorn 5 | from random import choice 6 | 7 | desc = 'API to recommend problem from codeforces' 8 | app = FastAPI(title="Gimme API", 9 | version="1.0.0", 10 | description=desc, 11 | docs_url='/') 12 | 13 | 14 | @app.on_event("startup") 15 | async def startup_event(): 16 | global data 17 | global contests 18 | global contest_qry 19 | contest_qry = {} 20 | 21 | async with aiohttp.ClientSession() as session: 22 | async with session.get( 23 | "https://codeforces.com/api/problemset.problems") as respose: 24 | data = await respose.json() 25 | data = data["result"]["problems"] 26 | 27 | async with aiohttp.ClientSession() as session: 28 | async with session.get( 29 | "https://codeforces.com/api/contest.list?gym=false" 30 | ) as respose: 31 | contests = await respose.json() 32 | contests = contests["result"] 33 | 34 | for contest in contests: 35 | contest_qry[contest["id"]] = contest 36 | 37 | 38 | @app.get("/gimme") 39 | async def gimme(handle: str, 40 | tags: List[str] = Query([]), 41 | rating: int = -1, 42 | contests: List[str] = Query([]), 43 | problem_types: List[str] = Query([])): 44 | async with aiohttp.ClientSession() as session: 45 | if rating == -1: 46 | print( 47 | f"Rating not provided. Querying codeforces for {handle}'s rating" 48 | ) 49 | async with session.get( 50 | f"https://codeforces.com/api/user.info?handles={handle}" 51 | ) as response: 52 | res = await response.json() 53 | if (res['status'] != "OK"): 54 | return res 55 | else: 56 | try: 57 | rating = round(res["result"][0]["rating"], -2) 58 | except KeyError: 59 | rating = 800 60 | if rating < 800: rating = 800 61 | if rating > 3500: rating = 3500 62 | print(f"Querying codeforces for {handle}'s submissions") 63 | async with session.get( 64 | f"https://codeforces.com/api/user.status?handle={handle}" 65 | ) as response: 66 | res = await response.json() 67 | if (res['status'] != "OK"): 68 | return res 69 | else: 70 | submissions = res['result'] 71 | solved = { 72 | sub['problem']['name'] 73 | for sub in submissions if sub["verdict"] == 'OK' 74 | } 75 | problems = [] 76 | for prob in data: 77 | try: 78 | if (problem_types or prob["rating"] == int(rating)) and prob["name"] not in solved and "*special" not in \ 79 | prob[ 80 | "tags"]: 81 | present = len(contests) == 0 82 | 83 | for contest_type in contests: 84 | if contest_type in contest_qry[ 85 | prob["contestId"]]["name"]: 86 | present = True 87 | break 88 | 89 | if present: 90 | problems.append(prob) 91 | 92 | except: 93 | pass 94 | if tags: 95 | problem = [] 96 | for prob in problems: 97 | try: 98 | if all(p in prob['tags'] for p in tags): 99 | problem.append(prob) 100 | except: 101 | pass 102 | problems = problem 103 | 104 | if problem_types: 105 | problem = [] 106 | for prob in problems: 107 | try: 108 | if prob['index'] in problem_types: 109 | problem.append(prob) 110 | except: 111 | pass 112 | problems = problem 113 | 114 | if not problems: 115 | return ({ 116 | "status": 117 | "FAILED", 118 | "comment": 119 | "Problems not found within the search parameters" 120 | }) 121 | select = choice(problems) 122 | print(select) 123 | return ({ 124 | "status": 125 | "OK", 126 | "name": 127 | select["name"], 128 | "rating": 129 | select["rating"], 130 | "tags": 131 | select["tags"], 132 | "url": 133 | "https://codeforces.com/contest/{}/problem/{}".format( 134 | select["contestId"], select["index"]) 135 | }) 136 | 137 | 138 | if __name__ == "__main__": 139 | uvicorn.run("main:app", host='0.0.0.0', port=8080) 140 | -------------------------------------------------------------------------------- /cpdrills/templates/training/main_pages/speedtrain_problem.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block head %} 5 | {% endblock head %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |
12 |
13 |

Problem

14 |
15 | 16 |
17 |

Name: {{ problem.name }}

18 |

Rating: {{ problem.rating }}

19 |

Link: {{ problem.url }}

20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |

Stopwatch

28 |
29 | 30 |
31 |

32 | 33 | : 34 | 35 | : 36 | 37 |

38 |
39 | 40 |
41 | 42 | 43 | 44 |
45 |
46 |
47 |
48 |
49 | {% endblock content %} 50 | 51 | {% block scripts %} 52 | 53 | 140 | {% endblock scripts %} -------------------------------------------------------------------------------- /cpdrills/static/javascript/HackTimer.js: -------------------------------------------------------------------------------- 1 | // Source: https://github.com/turuslan/HackTimer 2 | 3 | (function (workerScript) { 4 | if (!/MSIE 10/i.test(navigator.userAgent)) { 5 | try { 6 | var blob = new Blob(["\ 7 | var fakeIdToId = {};\ 8 | onmessage = function (event) {\ 9 | var data = event.data,\ 10 | name = data.name,\ 11 | fakeId = data.fakeId,\ 12 | time;\ 13 | if(data.hasOwnProperty('time')) {\ 14 | time = data.time;\ 15 | }\ 16 | switch (name) {\ 17 | case 'setInterval':\ 18 | fakeIdToId[fakeId] = setInterval(function () {\ 19 | postMessage({fakeId: fakeId});\ 20 | }, time);\ 21 | break;\ 22 | case 'clearInterval':\ 23 | if (fakeIdToId.hasOwnProperty (fakeId)) {\ 24 | clearInterval(fakeIdToId[fakeId]);\ 25 | delete fakeIdToId[fakeId];\ 26 | }\ 27 | break;\ 28 | case 'setTimeout':\ 29 | fakeIdToId[fakeId] = setTimeout(function () {\ 30 | postMessage({fakeId: fakeId});\ 31 | if (fakeIdToId.hasOwnProperty (fakeId)) {\ 32 | delete fakeIdToId[fakeId];\ 33 | }\ 34 | }, time);\ 35 | break;\ 36 | case 'clearTimeout':\ 37 | if (fakeIdToId.hasOwnProperty (fakeId)) {\ 38 | clearTimeout(fakeIdToId[fakeId]);\ 39 | delete fakeIdToId[fakeId];\ 40 | }\ 41 | break;\ 42 | }\ 43 | }\ 44 | "]); 45 | // Obtain a blob URL reference to our worker 'file'. 46 | workerScript = window.URL.createObjectURL(blob); 47 | } catch (error) { 48 | /* Blob is not supported, use external script instead */ 49 | } 50 | } 51 | var worker, 52 | fakeIdToCallback = {}, 53 | lastFakeId = 0, 54 | maxFakeId = 0x7FFFFFFF, // 2 ^ 31 - 1, 31 bit, positive values of signed 32 bit integer 55 | logPrefix = 'HackTimer.js by turuslan: '; 56 | if (typeof (Worker) !== 'undefined') { 57 | function getFakeId() { 58 | do { 59 | if (lastFakeId == maxFakeId) { 60 | lastFakeId = 0; 61 | } else { 62 | lastFakeId++; 63 | } 64 | } while (fakeIdToCallback.hasOwnProperty(lastFakeId)); 65 | return lastFakeId; 66 | } 67 | 68 | try { 69 | worker = new Worker(workerScript); 70 | window.setInterval = function (callback, time /* , parameters */) { 71 | var fakeId = getFakeId(); 72 | fakeIdToCallback[fakeId] = { 73 | callback: callback, 74 | parameters: Array.prototype.slice.call(arguments, 2) 75 | }; 76 | worker.postMessage({ 77 | name: 'setInterval', 78 | fakeId: fakeId, 79 | time: time 80 | }); 81 | return fakeId; 82 | }; 83 | window.clearInterval = function (fakeId) { 84 | if (fakeIdToCallback.hasOwnProperty(fakeId)) { 85 | delete fakeIdToCallback[fakeId]; 86 | worker.postMessage({ 87 | name: 'clearInterval', 88 | fakeId: fakeId 89 | }); 90 | } 91 | }; 92 | window.setTimeout = function (callback, time /* , parameters */) { 93 | var fakeId = getFakeId(); 94 | fakeIdToCallback[fakeId] = { 95 | callback: callback, 96 | parameters: Array.prototype.slice.call(arguments, 2), 97 | isTimeout: true 98 | }; 99 | worker.postMessage({ 100 | name: 'setTimeout', 101 | fakeId: fakeId, 102 | time: time 103 | }); 104 | return fakeId; 105 | }; 106 | window.clearTimeout = function (fakeId) { 107 | if (fakeIdToCallback.hasOwnProperty(fakeId)) { 108 | delete fakeIdToCallback[fakeId]; 109 | worker.postMessage({ 110 | name: 'clearTimeout', 111 | fakeId: fakeId 112 | }); 113 | } 114 | }; 115 | worker.onmessage = function (event) { 116 | var data = event.data, 117 | fakeId = data.fakeId, 118 | request, 119 | parameters, 120 | callback; 121 | if (fakeIdToCallback.hasOwnProperty(fakeId)) { 122 | request = fakeIdToCallback[fakeId]; 123 | callback = request.callback; 124 | parameters = request.parameters; 125 | if (request.hasOwnProperty('isTimeout') && request.isTimeout) { 126 | delete fakeIdToCallback[fakeId]; 127 | } 128 | } 129 | if (typeof (callback) === 'string') { 130 | try { 131 | callback = new Function(callback); 132 | } catch (error) { 133 | console.log(logPrefix + 'Error parsing callback code string: ', error); 134 | } 135 | } 136 | if (typeof (callback) === 'function') { 137 | callback.apply(window, parameters); 138 | } 139 | }; 140 | worker.onerror = function (event) { 141 | console.log(event); 142 | }; 143 | } catch (error) { 144 | console.log(logPrefix + 'Initialisation failed'); 145 | console.error(error); 146 | } 147 | } else { 148 | console.log(logPrefix + 'Initialisation failed - HTML5 Web Worker is not supported'); 149 | } 150 | })('HackTimerWorker.js'); -------------------------------------------------------------------------------- /cpdrills/accounts/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | from django.contrib.auth import login, authenticate, logout 3 | from django.contrib.auth.forms import AuthenticationForm 4 | from .forms import MyUserCreationForm, UserEditForm, UserProfileEditForm 5 | from django.contrib import messages 6 | from django.contrib.auth.decorators import login_required 7 | from django.contrib.sites.shortcuts import get_current_site 8 | from django.template.loader import render_to_string 9 | from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode 10 | from django.utils.encoding import force_bytes, force_str 11 | from .tokens import generate_token 12 | from django.contrib.auth.models import User 13 | from django.core.mail import EmailMessage 14 | from django.conf import settings 15 | import threading 16 | 17 | 18 | class EmailThread(threading.Thread): 19 | 20 | def __init__(self, email): 21 | self.email = email 22 | threading.Thread.__init__(self) 23 | 24 | def run(self): 25 | self.email.send(fail_silently=False) 26 | 27 | 28 | def send_activation_email(request, user): 29 | current_site = get_current_site(request) 30 | email_subject = "Activate your CP Drills account!" 31 | email_body = render_to_string("accounts/email_verify.html", { 32 | 'user': user, 33 | 'domain': current_site, 34 | 'uid': urlsafe_base64_encode(force_bytes(user.pk)), 35 | 'token': generate_token.make_token(user) 36 | }) 37 | 38 | email = EmailMessage(subject=email_subject, 39 | body=email_body, 40 | from_email="CP Drills <" + settings.EMAIL_HOST_USER + ">", 41 | to=[user.email]) 42 | 43 | # EmailThread(email).start() 44 | email.send(fail_silently=False) 45 | 46 | 47 | def activate_user(request, uidb64, token): 48 | try: 49 | uid = force_str(urlsafe_base64_decode(uidb64)) 50 | user = User.objects.get(pk=uid) 51 | except Exception as e: 52 | user = None 53 | 54 | if user and generate_token.check_token(user, token): 55 | user.is_active = True 56 | user.save() 57 | 58 | messages.success(request, "Email verified. You may log in now.") 59 | return redirect('sign_in') 60 | 61 | return render(request, 'accounts/activate_failed.html', {"user": user}) 62 | 63 | 64 | def sign_up(request): 65 | """ 66 | Custom user sign up 67 | """ 68 | 69 | if request.method == 'POST': 70 | form = MyUserCreationForm(request.POST) 71 | 72 | if form.is_valid(): 73 | user = form.save() 74 | user.refresh_from_db() # Load the profile instance created by the signal 75 | 76 | user.is_active = False 77 | 78 | user.userprofile.country = form.cleaned_data.get('country') 79 | user.userprofile.codeforces_handle = form.cleaned_data.get('codeforces_handle') 80 | user.userprofile.gender = form.cleaned_data.get('gender') 81 | user.userprofile.save() # Saving UserProfile object 82 | user.save() # Saving User object 83 | 84 | send_activation_email(request, user) 85 | 86 | # login(request, user) 87 | 88 | messages.success(request, 89 | "We have sent you an email to verify your account. Don't forget to check spam folders!") 90 | return redirect('home') 91 | else: 92 | form = MyUserCreationForm() 93 | 94 | return render(request, 'accounts/sign_up.html', {'form': form}) 95 | 96 | 97 | def user_logout(request): 98 | """ 99 | View for logging users out. 100 | """ 101 | 102 | logout(request) 103 | messages.success(request, 'Logged out successfully.') 104 | return redirect('home') 105 | 106 | 107 | def sign_in(request): 108 | """ 109 | Standard user sign in 110 | """ 111 | 112 | if request.method == "POST": 113 | form = AuthenticationForm(request, data=request.POST) 114 | 115 | if form.is_valid(): 116 | username = form.cleaned_data.get('username') 117 | password = form.cleaned_data.get('password') 118 | user = authenticate(username=username, password=password) 119 | 120 | if user is not None: 121 | login(request, user) 122 | messages.success(request, 123 | 'Welcome, ' + user.username + '! Please note that the training pages are not yet optimised for smaller screens.') 124 | return redirect('train') 125 | else: 126 | username = form.cleaned_data.get('username') 127 | user_obj = User.objects.filter(username=username) 128 | 129 | if user_obj.exists(): 130 | if not user_obj[0].is_active: 131 | messages.error(request, 'Please check your inbox and verify your email. A mail was sent again.') 132 | send_activation_email(request, user_obj[0]) 133 | return redirect('sign_in') 134 | else: 135 | messages.error(request, "Invalid username...") 136 | return redirect('sign_in') 137 | 138 | messages.error(request, 'Invalid password...') 139 | return redirect('sign_in') 140 | 141 | form = AuthenticationForm() 142 | return render(request, "accounts/sign_in.html", {"form": form}) 143 | 144 | 145 | @login_required 146 | def profile_edit(request): 147 | """ 148 | Lets user edit their profile. 149 | """ 150 | 151 | if request.method == "POST": 152 | user_edit_form = UserEditForm(request.POST, instance=request.user) 153 | userprofile_edit_form = UserProfileEditForm(request.POST, instance=request.user.userprofile) 154 | 155 | if user_edit_form.is_valid() and userprofile_edit_form.is_valid(): 156 | user_edit_form.save() 157 | userprofile_edit_form.save() 158 | messages.success(request, "Profile updated successfully.") 159 | else: 160 | messages.error(request, "An error occurred.") 161 | 162 | return redirect('profile_edit') 163 | 164 | user_edit_form = UserEditForm(instance=request.user) 165 | userprofile_edit_form = UserProfileEditForm(instance=request.user.userprofile) 166 | return render(request, "accounts/profile_edit.html", 167 | {"user_form": user_edit_form, "userprofile_form": userprofile_edit_form}) 168 | -------------------------------------------------------------------------------- /cpdrills/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block title %} {% endblock title %} 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 35 | 36 | {% block head %} 37 | 38 | {% endblock head %} 39 | 40 | 41 | 42 | 77 | 78 |
79 | {% block content %} 80 | 81 | {% endblock content %} 82 |
83 | 84 | 96 | 97 | {#
#} 98 | {# #} 109 | {#
#} 110 | 111 | {#
#} 112 | {#
#} 113 | {#

Sticky footer with fixed navbar

#} 114 | {#

Pin a footer to the bottom of the viewport in desktop browsers with this custom HTML and CSS. A#} 115 | {# fixed navbar has been added with padding-top: 60px; on the main#} 116 | {# > .container.

#} 117 | {#

Back to the default sticky footer minus the navbar.

#} 118 | {#
#} 119 | {#
#} 120 | 121 | {##} 122 | {##} 123 | {##} 124 | 126 | 129 | 130 | 131 | {% block scripts %} 132 | 133 | {% endblock scripts %} 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /cpdrills/training/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.shortcuts import render, redirect 4 | from .forms import SpeedTrainProblemForm 5 | from django.contrib import messages 6 | from .models import ProblemTopic, Subcategory, ProblemStatus, Problem, OnlineJudge 7 | from django.contrib.auth.decorators import login_required 8 | from django.core.paginator import Paginator 9 | from django.http import JsonResponse 10 | import requests 11 | from utils import codeforces, graph_plots 12 | from dotenv import load_dotenv 13 | load_dotenv('../../.env') 14 | 15 | API_URL = os.getenv('API_URL') + '/gimme' # Problem finder API 16 | 17 | 18 | @login_required 19 | def train(request): 20 | """ 21 | Renders the training page 22 | """ 23 | 24 | topics = ProblemTopic.objects.all() 25 | problems = ProblemStatus.objects.filter(userprofile__user=request.user).order_by('-solve_time') 26 | 27 | cnt = ProblemStatus.objects.filter(userprofile__user=request.user, 28 | problem__subcategory__problem_topic_id=1).count() 29 | 30 | total_cnt = problems.count() 31 | 32 | graph_solved_bar = graph_plots.stacked_bar_solve_count_graph(request.user, "null", False, True) 33 | graph_speed = graph_plots.average_speed_line_graph(request.user, "null", False, True) 34 | graph_time_date = graph_plots.average_speed_date_graph(request.user, "null", False, True) 35 | 36 | paginator = Paginator(problems, 20) 37 | page_number = request.GET.get('page') 38 | page_obj = paginator.get_page(page_number) 39 | 40 | return render(request, 'training/main_pages/train.html', 41 | {"topics": [topic.name for topic in topics], "dp_cnt": cnt, 42 | "graph_solved_bar": graph_solved_bar, 43 | "graph_speed": graph_speed, 44 | "graph_time_date": graph_time_date, 45 | 'page_obj': page_obj, 46 | "total_cnt": total_cnt}) 47 | 48 | 49 | @login_required 50 | def practice(request, topic, subcategory): 51 | """ 52 | Problem list page. 53 | """ 54 | 55 | topic_object = ProblemTopic.objects.get(name=topic) 56 | subcategory_object = Subcategory.objects.get(name=subcategory) 57 | 58 | subcategories = topic_object.subcategoryOf.all() 59 | 60 | problems_list = subcategory_object.problems.all().order_by('ordering_code') 61 | paginator = Paginator(problems_list, 10) 62 | page_number = request.GET.get('page') 63 | page_obj = paginator.get_page(page_number) 64 | 65 | graph1 = graph_plots.average_speed_line_graph(request.user, "Dynamic Programming", False, False) 66 | graph_bar_count = graph_plots.stacked_bar_solve_count_graph(request.user, "Dynamic Programming", False, False) 67 | graph_time_date = graph_plots.average_speed_date_graph(request.user, "Dynamic Programming", False, False) 68 | 69 | context = {"topic": topic, 70 | "subcategories": [subcategory.name for subcategory in subcategories], 71 | "cur_sub": subcategory, 72 | "page_obj": page_obj, 73 | "graph1": graph1, 74 | "graph_bar_count": graph_bar_count, 75 | "graph_time_date": graph_time_date} 76 | 77 | return render(request, 'training/main_pages/practice.html', context) 78 | 79 | 80 | @login_required 81 | def speed_train(request): 82 | """ 83 | Speed Training module. Using API for finding unsolved problems. 84 | """ 85 | 86 | graph1 = graph_plots.average_speed_line_graph(request.user, "null", True, False) 87 | graph_bar_count = graph_plots.stacked_bar_solve_count_graph(request.user, "null", True, False) 88 | graph_time_date = graph_plots.average_speed_date_graph(request.user, "null", True, False) 89 | 90 | context = {'form': SpeedTrainProblemForm(), "graph1": graph1, "graph_bar_count": graph_bar_count, 91 | "graph_time_date": graph_time_date} 92 | 93 | if 'problem' in request.session: 94 | try: 95 | context['problem'] = Problem.objects.get(code=request.session['problem']) 96 | except: 97 | del request.session['problem'] 98 | 99 | if request.method == "POST": 100 | form = SpeedTrainProblemForm(request.POST) 101 | 102 | if form.is_valid(): 103 | cur_handle = request.user.userprofile.codeforces_handle 104 | if not cur_handle: 105 | cur_handle = "DrowsyPanda" 106 | 107 | api_payload = {'handle': cur_handle, 108 | 'rating': form.cleaned_data['rating'], 109 | 'tags': form.cleaned_data['topics'], 110 | 'contests': form.cleaned_data['contest_type'], 111 | 'problem_types': form.cleaned_data['problem_type']} 112 | 113 | api_response = requests.get(API_URL, params=api_payload) 114 | 115 | if api_response.status_code != 200: 116 | messages.error(request, 'Codeforces might be unavailable at this moment. Please try again later.') 117 | return render(request, 'training/main_pages/speedtrain_form.html', context) 118 | 119 | api_data = api_response.json() 120 | 121 | if api_data['status'] != 'OK': 122 | messages.error(request, 123 | 'No problem found. Please also check whether the codeforces handle under your profile ' 124 | 'is valid. Ensure that it is a username and not a link.') 125 | return render(request, 'training/main_pages/speedtrain_form.html', context) 126 | 127 | codeforces_oj = OnlineJudge.objects.get(name="Codeforces") 128 | problem_code = codeforces.code_extractor(api_data['url']) 129 | problem = Problem.objects.get_or_create(code=problem_code, rating=api_data['rating'], 130 | source=codeforces_oj, name=api_data['name'], 131 | link=api_data['url']) 132 | 133 | # context['problem'] = problem[0] 134 | request.session['problem'] = problem[0].code 135 | 136 | return redirect('speed_train') 137 | # return render(request, 'training/main_pages/speedtrain_form.html', context) 138 | else: 139 | messages.error(request, 'Rating must be in the range [800, 3500].') 140 | 141 | return render(request, 'training/main_pages/speedtrain_form.html', context) 142 | 143 | 144 | @login_required 145 | def problem_status_update(request): 146 | """ 147 | Receives an AJAX call to update problem statuses for the logged-in user. 148 | """ 149 | 150 | if request.method == "POST" and request.headers.get('x-requested-with') == 'XMLHttpRequest': 151 | problem_status_tuple = ProblemStatus.objects.get_or_create(problem_id=request.POST['problem_code'], 152 | userprofile=request.user.userprofile) 153 | 154 | if request.POST['action'] == 'save': 155 | time_taken = int(request.POST['hours']) * 60 + int( 156 | request.POST['minutes']) + round( 157 | int(request.POST['seconds'])) / 60 158 | 159 | if time_taken == 0: 160 | time_taken = -1 161 | 162 | problem_status_tuple[0].solve_duration = time_taken 163 | speed_attempt = True if request.POST['speed_attempt'] == 'True' else False 164 | problem_status_tuple[0].speed_attempt = speed_attempt 165 | 166 | problem_status_tuple[0].save() 167 | 168 | return JsonResponse({'status': 'Success', 169 | 'msg': 'Successfully updated.', 170 | 'solve_time': problem_status_tuple[0].solve_time}) 171 | else: 172 | problem_status_tuple[0].delete() 173 | return JsonResponse({'status': 'Success', 174 | 'msg': 'Successfully deleted.'}) 175 | 176 | return JsonResponse({'status': 'Fail', 'msg': 'Something went wrong.'}) 177 | -------------------------------------------------------------------------------- /cpdrills/static/bootstrap/css2/bootstrap-reboot.rtl.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.0.2 (https://getbootstrap.com/) 3 | * Copyright 2011-2021 The Bootstrap Authors 4 | * Copyright 2011-2021 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | *, 9 | *::before, 10 | *::after { 11 | box-sizing: border-box; 12 | } 13 | 14 | @media (prefers-reduced-motion: no-preference) { 15 | :root { 16 | scroll-behavior: smooth; 17 | } 18 | } 19 | 20 | body { 21 | margin: 0; 22 | font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 23 | font-size: 1rem; 24 | font-weight: 400; 25 | line-height: 1.5; 26 | color: #212529; 27 | background-color: #fff; 28 | -webkit-text-size-adjust: 100%; 29 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 30 | } 31 | 32 | hr { 33 | margin: 1rem 0; 34 | color: inherit; 35 | background-color: currentColor; 36 | border: 0; 37 | opacity: 0.25; 38 | } 39 | 40 | hr:not([size]) { 41 | height: 1px; 42 | } 43 | 44 | h6, h5, h4, h3, h2, h1 { 45 | margin-top: 0; 46 | margin-bottom: 0.5rem; 47 | font-weight: 500; 48 | line-height: 1.2; 49 | } 50 | 51 | h1 { 52 | font-size: calc(1.375rem + 1.5vw); 53 | } 54 | @media (min-width: 1200px) { 55 | h1 { 56 | font-size: 2.5rem; 57 | } 58 | } 59 | 60 | h2 { 61 | font-size: calc(1.325rem + 0.9vw); 62 | } 63 | @media (min-width: 1200px) { 64 | h2 { 65 | font-size: 2rem; 66 | } 67 | } 68 | 69 | h3 { 70 | font-size: calc(1.3rem + 0.6vw); 71 | } 72 | @media (min-width: 1200px) { 73 | h3 { 74 | font-size: 1.75rem; 75 | } 76 | } 77 | 78 | h4 { 79 | font-size: calc(1.275rem + 0.3vw); 80 | } 81 | @media (min-width: 1200px) { 82 | h4 { 83 | font-size: 1.5rem; 84 | } 85 | } 86 | 87 | h5 { 88 | font-size: 1.25rem; 89 | } 90 | 91 | h6 { 92 | font-size: 1rem; 93 | } 94 | 95 | p { 96 | margin-top: 0; 97 | margin-bottom: 1rem; 98 | } 99 | 100 | abbr[title], 101 | abbr[data-bs-original-title] { 102 | -webkit-text-decoration: underline dotted; 103 | text-decoration: underline dotted; 104 | cursor: help; 105 | -webkit-text-decoration-skip-ink: none; 106 | text-decoration-skip-ink: none; 107 | } 108 | 109 | address { 110 | margin-bottom: 1rem; 111 | font-style: normal; 112 | line-height: inherit; 113 | } 114 | 115 | ol, 116 | ul { 117 | padding-right: 2rem; 118 | } 119 | 120 | ol, 121 | ul, 122 | dl { 123 | margin-top: 0; 124 | margin-bottom: 1rem; 125 | } 126 | 127 | ol ol, 128 | ul ul, 129 | ol ul, 130 | ul ol { 131 | margin-bottom: 0; 132 | } 133 | 134 | dt { 135 | font-weight: 700; 136 | } 137 | 138 | dd { 139 | margin-bottom: 0.5rem; 140 | margin-right: 0; 141 | } 142 | 143 | blockquote { 144 | margin: 0 0 1rem; 145 | } 146 | 147 | b, 148 | strong { 149 | font-weight: bolder; 150 | } 151 | 152 | small { 153 | font-size: 0.875em; 154 | } 155 | 156 | mark { 157 | padding: 0.2em; 158 | background-color: #fcf8e3; 159 | } 160 | 161 | sub, 162 | sup { 163 | position: relative; 164 | font-size: 0.75em; 165 | line-height: 0; 166 | vertical-align: baseline; 167 | } 168 | 169 | sub { 170 | bottom: -0.25em; 171 | } 172 | 173 | sup { 174 | top: -0.5em; 175 | } 176 | 177 | a { 178 | color: #0d6efd; 179 | text-decoration: underline; 180 | } 181 | a:hover { 182 | color: #0a58ca; 183 | } 184 | 185 | a:not([href]):not([class]), a:not([href]):not([class]):hover { 186 | color: inherit; 187 | text-decoration: none; 188 | } 189 | 190 | pre, 191 | code, 192 | kbd, 193 | samp { 194 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 195 | font-size: 1em; 196 | direction: ltr ; 197 | unicode-bidi: bidi-override; 198 | } 199 | 200 | pre { 201 | display: block; 202 | margin-top: 0; 203 | margin-bottom: 1rem; 204 | overflow: auto; 205 | font-size: 0.875em; 206 | } 207 | pre code { 208 | font-size: inherit; 209 | color: inherit; 210 | word-break: normal; 211 | } 212 | 213 | code { 214 | font-size: 0.875em; 215 | color: #d63384; 216 | word-wrap: break-word; 217 | } 218 | a > code { 219 | color: inherit; 220 | } 221 | 222 | kbd { 223 | padding: 0.2rem 0.4rem; 224 | font-size: 0.875em; 225 | color: #fff; 226 | background-color: #212529; 227 | border-radius: 0.2rem; 228 | } 229 | kbd kbd { 230 | padding: 0; 231 | font-size: 1em; 232 | font-weight: 700; 233 | } 234 | 235 | figure { 236 | margin: 0 0 1rem; 237 | } 238 | 239 | img, 240 | svg { 241 | vertical-align: middle; 242 | } 243 | 244 | table { 245 | caption-side: bottom; 246 | border-collapse: collapse; 247 | } 248 | 249 | caption { 250 | padding-top: 0.5rem; 251 | padding-bottom: 0.5rem; 252 | color: #6c757d; 253 | text-align: right; 254 | } 255 | 256 | th { 257 | text-align: inherit; 258 | text-align: -webkit-match-parent; 259 | } 260 | 261 | thead, 262 | tbody, 263 | tfoot, 264 | tr, 265 | td, 266 | th { 267 | border-color: inherit; 268 | border-style: solid; 269 | border-width: 0; 270 | } 271 | 272 | label { 273 | display: inline-block; 274 | } 275 | 276 | button { 277 | border-radius: 0; 278 | } 279 | 280 | button:focus:not(:focus-visible) { 281 | outline: 0; 282 | } 283 | 284 | input, 285 | button, 286 | select, 287 | optgroup, 288 | textarea { 289 | margin: 0; 290 | font-family: inherit; 291 | font-size: inherit; 292 | line-height: inherit; 293 | } 294 | 295 | button, 296 | select { 297 | text-transform: none; 298 | } 299 | 300 | [role=button] { 301 | cursor: pointer; 302 | } 303 | 304 | select { 305 | word-wrap: normal; 306 | } 307 | select:disabled { 308 | opacity: 1; 309 | } 310 | 311 | [list]::-webkit-calendar-picker-indicator { 312 | display: none; 313 | } 314 | 315 | button, 316 | [type=button], 317 | [type=reset], 318 | [type=submit] { 319 | -webkit-appearance: button; 320 | } 321 | button:not(:disabled), 322 | [type=button]:not(:disabled), 323 | [type=reset]:not(:disabled), 324 | [type=submit]:not(:disabled) { 325 | cursor: pointer; 326 | } 327 | 328 | ::-moz-focus-inner { 329 | padding: 0; 330 | border-style: none; 331 | } 332 | 333 | textarea { 334 | resize: vertical; 335 | } 336 | 337 | fieldset { 338 | min-width: 0; 339 | padding: 0; 340 | margin: 0; 341 | border: 0; 342 | } 343 | 344 | legend { 345 | float: right; 346 | width: 100%; 347 | padding: 0; 348 | margin-bottom: 0.5rem; 349 | font-size: calc(1.275rem + 0.3vw); 350 | line-height: inherit; 351 | } 352 | @media (min-width: 1200px) { 353 | legend { 354 | font-size: 1.5rem; 355 | } 356 | } 357 | legend + * { 358 | clear: right; 359 | } 360 | 361 | ::-webkit-datetime-edit-fields-wrapper, 362 | ::-webkit-datetime-edit-text, 363 | ::-webkit-datetime-edit-minute, 364 | ::-webkit-datetime-edit-hour-field, 365 | ::-webkit-datetime-edit-day-field, 366 | ::-webkit-datetime-edit-month-field, 367 | ::-webkit-datetime-edit-year-field { 368 | padding: 0; 369 | } 370 | 371 | ::-webkit-inner-spin-button { 372 | height: auto; 373 | } 374 | 375 | [type=search] { 376 | outline-offset: -2px; 377 | -webkit-appearance: textfield; 378 | } 379 | 380 | [type="tel"], 381 | [type="url"], 382 | [type="email"], 383 | [type="number"] { 384 | direction: ltr; 385 | } 386 | ::-webkit-search-decoration { 387 | -webkit-appearance: none; 388 | } 389 | 390 | ::-webkit-color-swatch-wrapper { 391 | padding: 0; 392 | } 393 | 394 | ::file-selector-button { 395 | font: inherit; 396 | } 397 | 398 | ::-webkit-file-upload-button { 399 | font: inherit; 400 | -webkit-appearance: button; 401 | } 402 | 403 | output { 404 | display: inline-block; 405 | } 406 | 407 | iframe { 408 | border: 0; 409 | } 410 | 411 | summary { 412 | display: list-item; 413 | cursor: pointer; 414 | } 415 | 416 | progress { 417 | vertical-align: baseline; 418 | } 419 | 420 | [hidden] { 421 | display: none !important; 422 | } 423 | /*# sourceMappingURL=bootstrap-reboot.rtl.css.map */ -------------------------------------------------------------------------------- /cpdrills/static/bootstrap/css2/bootstrap-reboot.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.0.2 (https://getbootstrap.com/) 3 | * Copyright 2011-2021 The Bootstrap Authors 4 | * Copyright 2011-2021 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | *, 9 | *::before, 10 | *::after { 11 | box-sizing: border-box; 12 | } 13 | 14 | @media (prefers-reduced-motion: no-preference) { 15 | :root { 16 | scroll-behavior: smooth; 17 | } 18 | } 19 | 20 | body { 21 | margin: 0; 22 | font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 23 | font-size: 1rem; 24 | font-weight: 400; 25 | line-height: 1.5; 26 | color: #212529; 27 | background-color: #fff; 28 | -webkit-text-size-adjust: 100%; 29 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 30 | } 31 | 32 | hr { 33 | margin: 1rem 0; 34 | color: inherit; 35 | background-color: currentColor; 36 | border: 0; 37 | opacity: 0.25; 38 | } 39 | 40 | hr:not([size]) { 41 | height: 1px; 42 | } 43 | 44 | h6, h5, h4, h3, h2, h1 { 45 | margin-top: 0; 46 | margin-bottom: 0.5rem; 47 | font-weight: 500; 48 | line-height: 1.2; 49 | } 50 | 51 | h1 { 52 | font-size: calc(1.375rem + 1.5vw); 53 | } 54 | @media (min-width: 1200px) { 55 | h1 { 56 | font-size: 2.5rem; 57 | } 58 | } 59 | 60 | h2 { 61 | font-size: calc(1.325rem + 0.9vw); 62 | } 63 | @media (min-width: 1200px) { 64 | h2 { 65 | font-size: 2rem; 66 | } 67 | } 68 | 69 | h3 { 70 | font-size: calc(1.3rem + 0.6vw); 71 | } 72 | @media (min-width: 1200px) { 73 | h3 { 74 | font-size: 1.75rem; 75 | } 76 | } 77 | 78 | h4 { 79 | font-size: calc(1.275rem + 0.3vw); 80 | } 81 | @media (min-width: 1200px) { 82 | h4 { 83 | font-size: 1.5rem; 84 | } 85 | } 86 | 87 | h5 { 88 | font-size: 1.25rem; 89 | } 90 | 91 | h6 { 92 | font-size: 1rem; 93 | } 94 | 95 | p { 96 | margin-top: 0; 97 | margin-bottom: 1rem; 98 | } 99 | 100 | abbr[title], 101 | abbr[data-bs-original-title] { 102 | -webkit-text-decoration: underline dotted; 103 | text-decoration: underline dotted; 104 | cursor: help; 105 | -webkit-text-decoration-skip-ink: none; 106 | text-decoration-skip-ink: none; 107 | } 108 | 109 | address { 110 | margin-bottom: 1rem; 111 | font-style: normal; 112 | line-height: inherit; 113 | } 114 | 115 | ol, 116 | ul { 117 | padding-left: 2rem; 118 | } 119 | 120 | ol, 121 | ul, 122 | dl { 123 | margin-top: 0; 124 | margin-bottom: 1rem; 125 | } 126 | 127 | ol ol, 128 | ul ul, 129 | ol ul, 130 | ul ol { 131 | margin-bottom: 0; 132 | } 133 | 134 | dt { 135 | font-weight: 700; 136 | } 137 | 138 | dd { 139 | margin-bottom: 0.5rem; 140 | margin-left: 0; 141 | } 142 | 143 | blockquote { 144 | margin: 0 0 1rem; 145 | } 146 | 147 | b, 148 | strong { 149 | font-weight: bolder; 150 | } 151 | 152 | small { 153 | font-size: 0.875em; 154 | } 155 | 156 | mark { 157 | padding: 0.2em; 158 | background-color: #fcf8e3; 159 | } 160 | 161 | sub, 162 | sup { 163 | position: relative; 164 | font-size: 0.75em; 165 | line-height: 0; 166 | vertical-align: baseline; 167 | } 168 | 169 | sub { 170 | bottom: -0.25em; 171 | } 172 | 173 | sup { 174 | top: -0.5em; 175 | } 176 | 177 | a { 178 | color: #0d6efd; 179 | text-decoration: underline; 180 | } 181 | a:hover { 182 | color: #0a58ca; 183 | } 184 | 185 | a:not([href]):not([class]), a:not([href]):not([class]):hover { 186 | color: inherit; 187 | text-decoration: none; 188 | } 189 | 190 | pre, 191 | code, 192 | kbd, 193 | samp { 194 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 195 | font-size: 1em; 196 | direction: ltr /* rtl:ignore */; 197 | unicode-bidi: bidi-override; 198 | } 199 | 200 | pre { 201 | display: block; 202 | margin-top: 0; 203 | margin-bottom: 1rem; 204 | overflow: auto; 205 | font-size: 0.875em; 206 | } 207 | pre code { 208 | font-size: inherit; 209 | color: inherit; 210 | word-break: normal; 211 | } 212 | 213 | code { 214 | font-size: 0.875em; 215 | color: #d63384; 216 | word-wrap: break-word; 217 | } 218 | a > code { 219 | color: inherit; 220 | } 221 | 222 | kbd { 223 | padding: 0.2rem 0.4rem; 224 | font-size: 0.875em; 225 | color: #fff; 226 | background-color: #212529; 227 | border-radius: 0.2rem; 228 | } 229 | kbd kbd { 230 | padding: 0; 231 | font-size: 1em; 232 | font-weight: 700; 233 | } 234 | 235 | figure { 236 | margin: 0 0 1rem; 237 | } 238 | 239 | img, 240 | svg { 241 | vertical-align: middle; 242 | } 243 | 244 | table { 245 | caption-side: bottom; 246 | border-collapse: collapse; 247 | } 248 | 249 | caption { 250 | padding-top: 0.5rem; 251 | padding-bottom: 0.5rem; 252 | color: #6c757d; 253 | text-align: left; 254 | } 255 | 256 | th { 257 | text-align: inherit; 258 | text-align: -webkit-match-parent; 259 | } 260 | 261 | thead, 262 | tbody, 263 | tfoot, 264 | tr, 265 | td, 266 | th { 267 | border-color: inherit; 268 | border-style: solid; 269 | border-width: 0; 270 | } 271 | 272 | label { 273 | display: inline-block; 274 | } 275 | 276 | button { 277 | border-radius: 0; 278 | } 279 | 280 | button:focus:not(:focus-visible) { 281 | outline: 0; 282 | } 283 | 284 | input, 285 | button, 286 | select, 287 | optgroup, 288 | textarea { 289 | margin: 0; 290 | font-family: inherit; 291 | font-size: inherit; 292 | line-height: inherit; 293 | } 294 | 295 | button, 296 | select { 297 | text-transform: none; 298 | } 299 | 300 | [role=button] { 301 | cursor: pointer; 302 | } 303 | 304 | select { 305 | word-wrap: normal; 306 | } 307 | select:disabled { 308 | opacity: 1; 309 | } 310 | 311 | [list]::-webkit-calendar-picker-indicator { 312 | display: none; 313 | } 314 | 315 | button, 316 | [type=button], 317 | [type=reset], 318 | [type=submit] { 319 | -webkit-appearance: button; 320 | } 321 | button:not(:disabled), 322 | [type=button]:not(:disabled), 323 | [type=reset]:not(:disabled), 324 | [type=submit]:not(:disabled) { 325 | cursor: pointer; 326 | } 327 | 328 | ::-moz-focus-inner { 329 | padding: 0; 330 | border-style: none; 331 | } 332 | 333 | textarea { 334 | resize: vertical; 335 | } 336 | 337 | fieldset { 338 | min-width: 0; 339 | padding: 0; 340 | margin: 0; 341 | border: 0; 342 | } 343 | 344 | legend { 345 | float: left; 346 | width: 100%; 347 | padding: 0; 348 | margin-bottom: 0.5rem; 349 | font-size: calc(1.275rem + 0.3vw); 350 | line-height: inherit; 351 | } 352 | @media (min-width: 1200px) { 353 | legend { 354 | font-size: 1.5rem; 355 | } 356 | } 357 | legend + * { 358 | clear: left; 359 | } 360 | 361 | ::-webkit-datetime-edit-fields-wrapper, 362 | ::-webkit-datetime-edit-text, 363 | ::-webkit-datetime-edit-minute, 364 | ::-webkit-datetime-edit-hour-field, 365 | ::-webkit-datetime-edit-day-field, 366 | ::-webkit-datetime-edit-month-field, 367 | ::-webkit-datetime-edit-year-field { 368 | padding: 0; 369 | } 370 | 371 | ::-webkit-inner-spin-button { 372 | height: auto; 373 | } 374 | 375 | [type=search] { 376 | outline-offset: -2px; 377 | -webkit-appearance: textfield; 378 | } 379 | 380 | /* rtl:raw: 381 | [type="tel"], 382 | [type="url"], 383 | [type="email"], 384 | [type="number"] { 385 | direction: ltr; 386 | } 387 | */ 388 | ::-webkit-search-decoration { 389 | -webkit-appearance: none; 390 | } 391 | 392 | ::-webkit-color-swatch-wrapper { 393 | padding: 0; 394 | } 395 | 396 | ::file-selector-button { 397 | font: inherit; 398 | } 399 | 400 | ::-webkit-file-upload-button { 401 | font: inherit; 402 | -webkit-appearance: button; 403 | } 404 | 405 | output { 406 | display: inline-block; 407 | } 408 | 409 | iframe { 410 | border: 0; 411 | } 412 | 413 | summary { 414 | display: list-item; 415 | cursor: pointer; 416 | } 417 | 418 | progress { 419 | vertical-align: baseline; 420 | } 421 | 422 | [hidden] { 423 | display: none !important; 424 | } 425 | 426 | /*# sourceMappingURL=bootstrap-reboot.css.map */ -------------------------------------------------------------------------------- /cpdrills/static/fonts/simple-line-icons.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:simple-line-icons;src:url(../fonts/Simple-Line-Icons.eot?v=2.4.0);src:url(../fonts/Simple-Line-Icons.eot?v=2.4.0#iefix) format('embedded-opentype'),url(../fonts/Simple-Line-Icons.woff2?v=2.4.0) format('woff2'),url(../fonts/Simple-Line-Icons.ttf?v=2.4.0) format('truetype'),url(../fonts/Simple-Line-Icons.woff?v=2.4.0) format('woff'),url(../fonts/Simple-Line-Icons.svg?v=2.4.0#simple-line-icons) format('svg');font-weight:400;font-style:normal}.icon-action-redo,.icon-action-undo,.icon-anchor,.icon-arrow-down,.icon-arrow-down-circle,.icon-arrow-left,.icon-arrow-left-circle,.icon-arrow-right,.icon-arrow-right-circle,.icon-arrow-up,.icon-arrow-up-circle,.icon-badge,.icon-bag,.icon-ban,.icon-basket,.icon-basket-loaded,.icon-bell,.icon-book-open,.icon-briefcase,.icon-bubble,.icon-bubbles,.icon-bulb,.icon-calculator,.icon-calendar,.icon-call-end,.icon-call-in,.icon-call-out,.icon-camera,.icon-camrecorder,.icon-chart,.icon-check,.icon-chemistry,.icon-clock,.icon-close,.icon-cloud-download,.icon-cloud-upload,.icon-compass,.icon-control-end,.icon-control-forward,.icon-control-pause,.icon-control-play,.icon-control-rewind,.icon-control-start,.icon-credit-card,.icon-crop,.icon-cup,.icon-cursor,.icon-cursor-move,.icon-diamond,.icon-direction,.icon-directions,.icon-disc,.icon-dislike,.icon-doc,.icon-docs,.icon-drawer,.icon-drop,.icon-earphones,.icon-earphones-alt,.icon-emotsmile,.icon-energy,.icon-envelope,.icon-envelope-letter,.icon-envelope-open,.icon-equalizer,.icon-event,.icon-exclamation,.icon-eye,.icon-eyeglass,.icon-feed,.icon-film,.icon-fire,.icon-flag,.icon-folder,.icon-folder-alt,.icon-frame,.icon-game-controller,.icon-ghost,.icon-globe,.icon-globe-alt,.icon-graduation,.icon-graph,.icon-grid,.icon-handbag,.icon-heart,.icon-home,.icon-hourglass,.icon-info,.icon-key,.icon-layers,.icon-like,.icon-link,.icon-list,.icon-location-pin,.icon-lock,.icon-lock-open,.icon-login,.icon-logout,.icon-loop,.icon-magic-wand,.icon-magnet,.icon-magnifier,.icon-magnifier-add,.icon-magnifier-remove,.icon-map,.icon-menu,.icon-microphone,.icon-minus,.icon-mouse,.icon-music-tone,.icon-music-tone-alt,.icon-mustache,.icon-note,.icon-notebook,.icon-options,.icon-options-vertical,.icon-organization,.icon-paper-clip,.icon-paper-plane,.icon-paypal,.icon-pencil,.icon-people,.icon-phone,.icon-picture,.icon-pie-chart,.icon-pin,.icon-plane,.icon-playlist,.icon-plus,.icon-power,.icon-present,.icon-printer,.icon-puzzle,.icon-question,.icon-refresh,.icon-reload,.icon-rocket,.icon-screen-desktop,.icon-screen-smartphone,.icon-screen-tablet,.icon-settings,.icon-share,.icon-share-alt,.icon-shield,.icon-shuffle,.icon-size-actual,.icon-size-fullscreen,.icon-social-behance,.icon-social-dribbble,.icon-social-dropbox,.icon-social-facebook,.icon-social-foursqare,.icon-social-github,.icon-social-google,.icon-social-instagram,.icon-social-linkedin,.icon-social-pinterest,.icon-social-reddit,.icon-social-skype,.icon-social-soundcloud,.icon-social-spotify,.icon-social-steam,.icon-social-stumbleupon,.icon-social-tumblr,.icon-social-twitter,.icon-social-vkontakte,.icon-social-youtube,.icon-speech,.icon-speedometer,.icon-star,.icon-support,.icon-symbol-female,.icon-symbol-male,.icon-tag,.icon-target,.icon-trash,.icon-trophy,.icon-umbrella,.icon-user,.icon-user-female,.icon-user-follow,.icon-user-following,.icon-user-unfollow,.icon-vector,.icon-volume-1,.icon-volume-2,.icon-volume-off,.icon-wallet,.icon-wrench{font-family:simple-line-icons;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-user:before{content:"\e005"}.icon-people:before{content:"\e001"}.icon-user-female:before{content:"\e000"}.icon-user-follow:before{content:"\e002"}.icon-user-following:before{content:"\e003"}.icon-user-unfollow:before{content:"\e004"}.icon-login:before{content:"\e066"}.icon-logout:before{content:"\e065"}.icon-emotsmile:before{content:"\e021"}.icon-phone:before{content:"\e600"}.icon-call-end:before{content:"\e048"}.icon-call-in:before{content:"\e047"}.icon-call-out:before{content:"\e046"}.icon-map:before{content:"\e033"}.icon-location-pin:before{content:"\e096"}.icon-direction:before{content:"\e042"}.icon-directions:before{content:"\e041"}.icon-compass:before{content:"\e045"}.icon-layers:before{content:"\e034"}.icon-menu:before{content:"\e601"}.icon-list:before{content:"\e067"}.icon-options-vertical:before{content:"\e602"}.icon-options:before{content:"\e603"}.icon-arrow-down:before{content:"\e604"}.icon-arrow-left:before{content:"\e605"}.icon-arrow-right:before{content:"\e606"}.icon-arrow-up:before{content:"\e607"}.icon-arrow-up-circle:before{content:"\e078"}.icon-arrow-left-circle:before{content:"\e07a"}.icon-arrow-right-circle:before{content:"\e079"}.icon-arrow-down-circle:before{content:"\e07b"}.icon-check:before{content:"\e080"}.icon-clock:before{content:"\e081"}.icon-plus:before{content:"\e095"}.icon-minus:before{content:"\e615"}.icon-close:before{content:"\e082"}.icon-event:before{content:"\e619"}.icon-exclamation:before{content:"\e617"}.icon-organization:before{content:"\e616"}.icon-trophy:before{content:"\e006"}.icon-screen-smartphone:before{content:"\e010"}.icon-screen-desktop:before{content:"\e011"}.icon-plane:before{content:"\e012"}.icon-notebook:before{content:"\e013"}.icon-mustache:before{content:"\e014"}.icon-mouse:before{content:"\e015"}.icon-magnet:before{content:"\e016"}.icon-energy:before{content:"\e020"}.icon-disc:before{content:"\e022"}.icon-cursor:before{content:"\e06e"}.icon-cursor-move:before{content:"\e023"}.icon-crop:before{content:"\e024"}.icon-chemistry:before{content:"\e026"}.icon-speedometer:before{content:"\e007"}.icon-shield:before{content:"\e00e"}.icon-screen-tablet:before{content:"\e00f"}.icon-magic-wand:before{content:"\e017"}.icon-hourglass:before{content:"\e018"}.icon-graduation:before{content:"\e019"}.icon-ghost:before{content:"\e01a"}.icon-game-controller:before{content:"\e01b"}.icon-fire:before{content:"\e01c"}.icon-eyeglass:before{content:"\e01d"}.icon-envelope-open:before{content:"\e01e"}.icon-envelope-letter:before{content:"\e01f"}.icon-bell:before{content:"\e027"}.icon-badge:before{content:"\e028"}.icon-anchor:before{content:"\e029"}.icon-wallet:before{content:"\e02a"}.icon-vector:before{content:"\e02b"}.icon-speech:before{content:"\e02c"}.icon-puzzle:before{content:"\e02d"}.icon-printer:before{content:"\e02e"}.icon-present:before{content:"\e02f"}.icon-playlist:before{content:"\e030"}.icon-pin:before{content:"\e031"}.icon-picture:before{content:"\e032"}.icon-handbag:before{content:"\e035"}.icon-globe-alt:before{content:"\e036"}.icon-globe:before{content:"\e037"}.icon-folder-alt:before{content:"\e039"}.icon-folder:before{content:"\e089"}.icon-film:before{content:"\e03a"}.icon-feed:before{content:"\e03b"}.icon-drop:before{content:"\e03e"}.icon-drawer:before{content:"\e03f"}.icon-docs:before{content:"\e040"}.icon-doc:before{content:"\e085"}.icon-diamond:before{content:"\e043"}.icon-cup:before{content:"\e044"}.icon-calculator:before{content:"\e049"}.icon-bubbles:before{content:"\e04a"}.icon-briefcase:before{content:"\e04b"}.icon-book-open:before{content:"\e04c"}.icon-basket-loaded:before{content:"\e04d"}.icon-basket:before{content:"\e04e"}.icon-bag:before{content:"\e04f"}.icon-action-undo:before{content:"\e050"}.icon-action-redo:before{content:"\e051"}.icon-wrench:before{content:"\e052"}.icon-umbrella:before{content:"\e053"}.icon-trash:before{content:"\e054"}.icon-tag:before{content:"\e055"}.icon-support:before{content:"\e056"}.icon-frame:before{content:"\e038"}.icon-size-fullscreen:before{content:"\e057"}.icon-size-actual:before{content:"\e058"}.icon-shuffle:before{content:"\e059"}.icon-share-alt:before{content:"\e05a"}.icon-share:before{content:"\e05b"}.icon-rocket:before{content:"\e05c"}.icon-question:before{content:"\e05d"}.icon-pie-chart:before{content:"\e05e"}.icon-pencil:before{content:"\e05f"}.icon-note:before{content:"\e060"}.icon-loop:before{content:"\e064"}.icon-home:before{content:"\e069"}.icon-grid:before{content:"\e06a"}.icon-graph:before{content:"\e06b"}.icon-microphone:before{content:"\e063"}.icon-music-tone-alt:before{content:"\e061"}.icon-music-tone:before{content:"\e062"}.icon-earphones-alt:before{content:"\e03c"}.icon-earphones:before{content:"\e03d"}.icon-equalizer:before{content:"\e06c"}.icon-like:before{content:"\e068"}.icon-dislike:before{content:"\e06d"}.icon-control-start:before{content:"\e06f"}.icon-control-rewind:before{content:"\e070"}.icon-control-play:before{content:"\e071"}.icon-control-pause:before{content:"\e072"}.icon-control-forward:before{content:"\e073"}.icon-control-end:before{content:"\e074"}.icon-volume-1:before{content:"\e09f"}.icon-volume-2:before{content:"\e0a0"}.icon-volume-off:before{content:"\e0a1"}.icon-calendar:before{content:"\e075"}.icon-bulb:before{content:"\e076"}.icon-chart:before{content:"\e077"}.icon-ban:before{content:"\e07c"}.icon-bubble:before{content:"\e07d"}.icon-camrecorder:before{content:"\e07e"}.icon-camera:before{content:"\e07f"}.icon-cloud-download:before{content:"\e083"}.icon-cloud-upload:before{content:"\e084"}.icon-envelope:before{content:"\e086"}.icon-eye:before{content:"\e087"}.icon-flag:before{content:"\e088"}.icon-heart:before{content:"\e08a"}.icon-info:before{content:"\e08b"}.icon-key:before{content:"\e08c"}.icon-link:before{content:"\e08d"}.icon-lock:before{content:"\e08e"}.icon-lock-open:before{content:"\e08f"}.icon-magnifier:before{content:"\e090"}.icon-magnifier-add:before{content:"\e091"}.icon-magnifier-remove:before{content:"\e092"}.icon-paper-clip:before{content:"\e093"}.icon-paper-plane:before{content:"\e094"}.icon-power:before{content:"\e097"}.icon-refresh:before{content:"\e098"}.icon-reload:before{content:"\e099"}.icon-settings:before{content:"\e09a"}.icon-star:before{content:"\e09b"}.icon-symbol-female:before{content:"\e09c"}.icon-symbol-male:before{content:"\e09d"}.icon-target:before{content:"\e09e"}.icon-credit-card:before{content:"\e025"}.icon-paypal:before{content:"\e608"}.icon-social-tumblr:before{content:"\e00a"}.icon-social-twitter:before{content:"\e009"}.icon-social-facebook:before{content:"\e00b"}.icon-social-instagram:before{content:"\e609"}.icon-social-linkedin:before{content:"\e60a"}.icon-social-pinterest:before{content:"\e60b"}.icon-social-github:before{content:"\e60c"}.icon-social-google:before{content:"\e60d"}.icon-social-reddit:before{content:"\e60e"}.icon-social-skype:before{content:"\e60f"}.icon-social-dribbble:before{content:"\e00d"}.icon-social-behance:before{content:"\e610"}.icon-social-foursqare:before{content:"\e611"}.icon-social-soundcloud:before{content:"\e612"}.icon-social-spotify:before{content:"\e613"}.icon-social-stumbleupon:before{content:"\e614"}.icon-social-youtube:before{content:"\e008"}.icon-social-dropbox:before{content:"\e00c"}.icon-social-vkontakte:before{content:"\e618"}.icon-social-steam:before{content:"\e620"} -------------------------------------------------------------------------------- /cpdrills/templates/landing/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block title %} CP Drills {% endblock title %} 5 | 6 | {% block content %} 7 | 8 | {% if messages %} 9 |
10 | {% for message in messages %} 11 | 13 | {% endfor %} 14 |
15 | {% endif %} 16 | 17 |
18 |
19 |
20 |
21 |
22 |

Master Competitive Programming

23 |

Learn fast. Solve faster.

24 |
25 | 26 | 27 |

Sometimes, there is a sharp rise in difficulty between two consecutive problems in a contest, which results in a large part of the ranklist being decided on the basis of speed on earlier problems.

28 |

CP Drills enables you to improve your speed by providing problems to solve against a stopwatch, and presents to you various analytics on your progress in the form of graphs.

29 |

It also contains curated, handpicked problem sets on popular topics such as dynamic programming.

30 |
31 | 32 | 33 |
34 | {% if not user.is_authenticated %} 35 | Sign 36 | Up 37 | Sign In 38 | {% else %} 39 |

Hi, {{ user.username }}. Head over to the training pages to start 40 | practising!

41 | {% endif %} 42 |
43 | 44 |
45 | 46 |
47 |
48 |
Important Note
49 |

50 | CP Drills is no longer maintained and therefore may not work. You can find the source code and usage guide here and run CP Drills locally. Good luck and have fun with your practice! 51 |

52 |
53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 |
63 |
65 |

Become Faster

66 |

Speed drills for all levels.

67 |
68 |
69 |
70 |
71 |
73 |

Monitor Your Progress

74 |

Powerful analytics for your skill and speed progress.

75 |
76 |
77 |
78 |
79 |
81 |

Practice Concepts

82 |

Curated list of high quality problems.

83 |
84 |
85 | {#
#} 86 | {#
#} 87 | {#
#} 89 | {#

Filter Codeforces Problems

#} 90 | {#

Find relevant codeforces problems specifying tags and contest#} 91 | {# divisions.

#} 92 | {#
#} 93 | {#
#} 94 | 95 | {#
#} 96 | {#
#} 97 | {#
#} 98 | {#
#} 100 | {#

Compare Against Others

#} 101 | {#

Your performance compared to benchmarks. (Coming Soon)

#} 102 | {#
#} 103 | {#
#} 104 | {##} 105 | {#
#} 106 | {#
#} 107 | {#
#} 109 | {#

Optimise Your Learning

#} 110 | {#

Personalised learning path with problems from multiple#} 111 | {# platforms. (Coming Soon)

#} 112 | {#
#} 113 | {#
#} 114 |
115 |
116 |
117 | 118 | {#
#} 119 | {#
#} 120 | {#
#} 121 | {##} 122 | {#
#} 123 | {#
#} 124 | {# Aruj Bansal#} 126 | {#
#} 127 | {##} 128 | {#
#} 129 | {#
Aruj Bansal
#} 130 | {# Founder#} 131 | {#
#} 132 | {# Indian National Olympiad in#} 133 | {# Informatics Silver Medalist#} 134 | {#

"CP Drills is one of the many ways by which I give#} 135 | {# back to the wonderful competitive programming community. Apart from writing educational#} 136 | {# blogs on Codeforces, I also teach on my YouTube channel Algorithms#} 138 | {# Conquered."#} 139 | {#

#} 140 | {#
#} 141 | {#
#} 142 | {##} 143 | {##} 144 | {#
#} 145 | {##} 146 | {#
#} 147 | {#

Statistics

#} 148 | {#
#} 149 | {##} 150 | {#
#} 151 | {#
#} 152 | {#

{{ user_cnt }} Users

#} 153 | {#
#} 154 | {##} 155 | {#
#} 156 | {#

{{ countries_cnt }} Countries

#} 157 | {#
#} 158 | {#
#} 159 | {#
#} 160 | {#
#} 161 | {#
#} 162 | {#
#} 163 | 164 |
165 |
166 |
167 |

Training Pages

168 |
169 | 170 |
171 |
172 | Speed Training 174 |
175 | 176 |
177 | Speed Training Analytics 179 |
180 |
181 | 182 |
183 |
184 | Dynamic Programming 186 |
187 | 188 |
189 | Solve History 191 |
192 |
193 |
194 |
195 | {% endblock content %} -------------------------------------------------------------------------------- /cpdrills/templates/training/main_pages/train.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% load training_extras %} 4 | 5 | {% block title %} 6 | Train | CP Drills 7 | {% endblock title %} 8 | 9 | {% block content %} 10 | {% if messages %} 11 |
12 | {% for message in messages %} 13 | {% if message.tags == "error" %} 14 |
15 | {{ message }} 16 |
17 | {% else %} 18 |
19 | {{ message }} 20 |
21 | {% endif %} 22 | {% endfor %} 23 |
24 | {% endif %} 25 | 26 |
27 | 28 |
29 |

Training

30 |
31 | 32 | 56 | 57 |
58 |

Topic Practice

59 |
60 | 61 |
62 | 63 | {% for topic in topics %} 64 | 74 | {% endfor %} 75 | 76 | {# #} 86 | {##} 87 | {##} 88 | {# #} 98 | {##} 99 | {# #} 109 | {##} 110 | {# #} 120 | 121 | 122 |
123 |

Analytics

124 |
125 | 126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | total problems solved 134 |
135 |
{{ total_cnt }}
136 |
137 |
138 |
139 |
140 | 141 |
142 | 143 |
144 | 145 |
146 | 147 |
148 |
149 | {{ graph_time_date|safe }} 150 |
151 | 152 |
153 | 154 |
155 |
156 | {{ graph_solved_bar|safe }} 157 |
158 | 159 |
160 |
161 | 162 |
163 |
164 |
165 | {{ graph_speed|safe }} 166 |
167 | 168 |
169 |
170 | 171 |
172 |
173 |
174 |
175 |
176 |

Solve History

177 |
178 |
180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | {# #} 190 | 191 | 192 | 193 | {% for prob in page_obj %} 194 | 195 | 198 | 199 | 201 | 202 | 203 | {% with problem_status=prob.problem.code|check_solve:user.username %} 204 | 205 | {% endwith %} 206 | 207 | 212 | 213 | {% endfor %} 214 | 215 |
ProblemSourceCodeforces RatingSpeed TrainingTime (HH:MM:SS)DateSolution
{{ prob.problem.name | capfirst }} 197 | {{ prob.problem.source }}{% if prob.problem.rating is -1 %} N.A. {% else %} 200 | {{ prob.problem.rating }} {% endif %}{{ prob.speed_attempt }}{{ problem_status.1 }} 208 | 211 |
216 |
217 |
218 |
219 |

221 | Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}

222 |
223 |
224 | 246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 | 254 | {% endblock content %} 255 | 256 | {% block scripts %} 257 | {# #} 258 | 259 | 264 | {% endblock scripts %} 265 | -------------------------------------------------------------------------------- /cpdrills/utils/graph_plots.py: -------------------------------------------------------------------------------- 1 | import plotly.graph_objs as go 2 | from training.models import ProblemStatus 3 | import pytz 4 | from django.utils import timezone 5 | import plotly.express as px 6 | 7 | from datetime import datetime 8 | 9 | # Generated using: 10 | # for rating in range(800, 3600, 100): 11 | # print(str(rating) + ": '" + '#'+''.join(random.sample(chars,6)) + '"') 12 | 13 | colors = {800: "#DC39A8", 14 | 900: "#D401F5", 15 | 1000: "#CE2958", 16 | 1100: "#FB48C1", 17 | 1200: "#0B5317", 18 | 1300: "#CA0B37", 19 | 1400: "#E689A2", 20 | 1500: "#3C4120", 21 | 1600: "#46B350", 22 | 1700: "#9C0F63", 23 | 1800: "#28D6E9", 24 | 1900: "#320581", 25 | 2000: "#0EF538", 26 | 2100: "#29C60B", 27 | 2200: "#E89132", 28 | 2300: "#C6409A", 29 | 2400: "#28A516", 30 | 2500: "#678E0C", 31 | 2600: "#92B8ED", 32 | 2700: "#BC1357", 33 | 2800: "#B1546D", 34 | 2900: "#F4381C", 35 | 3000: "#CA1324", 36 | 3100: "#5C0E8F", 37 | 3200: "#73DEA0", 38 | 3300: "#28DC17", 39 | 3400: "#DA12B7", 40 | 3500: "#813F7C", 41 | "Non Codeforces": "#AF43D0"} 42 | 43 | 44 | def convert_to_localtime(utctime): 45 | """ 46 | Converts datetime object to the user's local timezone. 47 | """ 48 | 49 | fmt = '%d/%m/%Y' 50 | utc = utctime.replace(tzinfo=pytz.UTC) 51 | localtz = utc.astimezone(timezone.get_current_timezone()) 52 | return localtz.strftime(fmt) 53 | 54 | 55 | def num_to_month(num): 56 | return datetime(1, num, 1).strftime("%B") 57 | 58 | 59 | def average_speed_line_graph(user, topic, speed_attempt, show_all): 60 | """ 61 | Graph: Time Taken v.s. Rating 62 | Line 1: Average time 63 | Scatter 1: Raw time 64 | """ 65 | 66 | if topic == "null": 67 | if show_all: 68 | problems = ProblemStatus.objects.filter(userprofile__user=user).all() 69 | else: 70 | problems = ProblemStatus.objects.filter(userprofile__user=user, speed_attempt=speed_attempt).all() 71 | else: 72 | if show_all: 73 | problems = ProblemStatus.objects.filter(userprofile__user=user, 74 | problem__subcategory__problem_topic_id=1).all() 75 | else: 76 | problems = ProblemStatus.objects.filter(userprofile__user=user, problem__subcategory__problem_topic_id=1, 77 | speed_attempt=speed_attempt).all() 78 | 79 | sum_dict = {} 80 | cnt_dict = {} 81 | x1 = [] 82 | y1 = [] 83 | x2 = [] 84 | y2 = [] 85 | 86 | finalise_order = [] 87 | 88 | for prob in problems: 89 | rating = prob.problem.rating 90 | 91 | if prob.solve_duration == -1 or rating == -1: 92 | continue 93 | 94 | x2.append(rating) 95 | y2.append(prob.solve_duration) 96 | 97 | if rating in cnt_dict: 98 | sum_dict[rating] += prob.solve_duration 99 | cnt_dict[rating] += 1 100 | else: 101 | sum_dict[rating] = prob.solve_duration 102 | cnt_dict[rating] = 1 103 | 104 | for k, v in sum_dict.items(): 105 | finalise_order.append((k, v / cnt_dict[k])) 106 | 107 | finalise_order.sort() 108 | 109 | for k, v in finalise_order: 110 | x1.append(k) 111 | y1.append(v) 112 | 113 | trace1 = go.Scatter(x=x1, y=y1, name='Average Time') 114 | trace2 = go.Scatter(x=x2, y=y2, name='Raw Time', mode='markers') 115 | 116 | title = "Speed (Speed Training)" if speed_attempt else "Speed" 117 | 118 | if topic != "null": 119 | title = "Speed (" + topic + ")" 120 | 121 | layout = go.Layout(title=title, 122 | xaxis={'title': 'Codeforces Problem Rating', 'autorange': True, 'tick0': '800', 'dtick': '100'}, 123 | yaxis={'title': 'Time (Minutes)', 'autorange': True}, 124 | autosize=True) 125 | 126 | figure = go.Figure(data=[trace2, trace1], layout=layout) 127 | figure.update_layout(autosize=True) 128 | 129 | figure.update_layout(yaxis_range=[0, 90]) 130 | figure.update_layout(xaxis_range=[800, 3500]) 131 | 132 | graph = figure.to_html(include_plotlyjs=False) 133 | return graph 134 | 135 | 136 | # def stacked_bar_solve_count_graph(user, topic, speed_attempt, show_all): 137 | # """ 138 | # Graph: Number of problems solved v.s. Date 139 | # Stacked bar graph - stack of ratings 140 | # """ 141 | # 142 | # if topic == "null": 143 | # if show_all: 144 | # problems = ProblemStatus.objects.filter(userprofile__user=user).all() 145 | # else: 146 | # problems = ProblemStatus.objects.filter(userprofile__user=user, speed_attempt=speed_attempt).all() 147 | # else: 148 | # if show_all: 149 | # problems = ProblemStatus.objects.filter(userprofile__user=user, problem__subcategory__problem_topic_id=1, 150 | # speed_attempt=speed_attempt).all() 151 | # else: 152 | # problems = ProblemStatus.objects.filter(userprofile__user=user, 153 | # problem__subcategory__problem_topic_id=1).all() 154 | # 155 | # cnt_dict = {} 156 | # rating_bars = {} 157 | # for rating in range(800, 3600, 100): 158 | # cnt_dict[rating] = {} 159 | # rating_bars[rating] = [[], []] 160 | # 161 | # mx = 15 162 | # 163 | # for problem in problems: 164 | # cur_date = str(convert_to_localtime(problem.solve_time)) 165 | # cur_rating = problem.problem.rating 166 | # 167 | # if cur_rating not in cnt_dict: 168 | # cnt_dict[cur_rating] = {} 169 | # 170 | # if cur_date in cnt_dict[cur_rating]: 171 | # cnt_dict[cur_rating][cur_date] += 1 172 | # mx = max(cnt_dict[cur_rating][cur_date], mx) 173 | # else: 174 | # cnt_dict[cur_rating][cur_date] = 1 175 | # 176 | # for k, v in cnt_dict.items(): 177 | # for k2, v2 in cnt_dict[k].items(): 178 | # if k not in rating_bars: 179 | # rating_bars[k] = [[], []] 180 | # 181 | # rating_bars[k][0].append(k2) 182 | # rating_bars[k][1].append(v2) 183 | # 184 | # title = "Solve Count (Speed Training)" if speed_attempt else "Solve Count" 185 | # 186 | # if topic != "null": 187 | # title = "Solve Count (" + topic + ")" 188 | # 189 | # layout = go.Layout(title=title, xaxis={'title': 'Date', }, 190 | # yaxis={'title': 'Number of Problems Solved'}) 191 | # 192 | # bar_traces = [] 193 | # 194 | # for k, v in rating_bars.items(): 195 | # name = "" 196 | # 197 | # if k == -1: 198 | # name = "Not Codeforces" 199 | # else: 200 | # name = str(k) 201 | # 202 | # trace = go.Bar(x=v[0], y=v[1], name=name) 203 | # bar_traces.append(trace) 204 | # 205 | # figure = go.Figure(data=bar_traces, layout=layout) 206 | # figure.update_layout(autosize=True) 207 | # figure.update_layout(barmode='stack') 208 | # figure.update_xaxes(automargin=True) 209 | # figure.update_layout(xaxis=dict(tickformat="%d-%b, %Y")) 210 | # 211 | # figure.update_layout(yaxis_range=[0, mx + 5]) 212 | # 213 | # graph = figure.to_html(include_plotlyjs=False) 214 | # return graph 215 | 216 | 217 | def average_speed_date_graph(user, topic, speed_attempt, show_all): 218 | """ 219 | Graph: Average time taken v.s. Date 220 | Line for every rating. 221 | """ 222 | 223 | if topic == "null": 224 | if show_all: 225 | problems = ProblemStatus.objects.filter(userprofile__user=user).all() 226 | else: 227 | problems = ProblemStatus.objects.filter(userprofile__user=user, speed_attempt=speed_attempt).all() 228 | else: 229 | if show_all: 230 | problems = ProblemStatus.objects.filter(userprofile__user=user, 231 | problem__subcategory__problem_topic_id=1).all() 232 | else: 233 | problems = ProblemStatus.objects.filter(userprofile__user=user, problem__subcategory__problem_topic_id=1, 234 | speed_attempt=speed_attempt).all() 235 | cur_month = datetime.now().month 236 | 237 | ratings = {} 238 | for rating in range(800, 3600, 100): 239 | ratings[rating] = {} 240 | 241 | for prob in problems: 242 | if prob.problem.source.name != "Codeforces" or prob.solve_duration <= 0: 243 | continue 244 | 245 | p_rating = prob.problem.rating 246 | solve_month = prob.solve_time.month 247 | solve_duration = prob.solve_duration 248 | 249 | if solve_month not in ratings[p_rating]: 250 | ratings[p_rating][solve_month] = [solve_duration, 1] 251 | else: 252 | ratings[p_rating][solve_month][0] += solve_duration 253 | ratings[p_rating][solve_month][1] += 1 254 | 255 | # x = [] 256 | # for month in range(1, 13, 1): 257 | # x.append(datetime(1, month, 1).strftime("%B")) 258 | 259 | traces = [] 260 | 261 | for rating in range(800, 3600, 100): 262 | total_time = 0 263 | total_cnt = 0 264 | 265 | x = [] 266 | y = [] 267 | 268 | for month in range(1, cur_month + 1, 1): 269 | if month in ratings[rating]: 270 | total_time += ratings[rating][month][0] 271 | total_cnt += ratings[rating][month][1] 272 | 273 | if total_cnt == 0: 274 | continue 275 | 276 | x.append(num_to_month(month)) 277 | y.append(total_time / total_cnt) 278 | 279 | trace = go.Scatter(x=x, y=y, name=rating, line=dict(color=colors[rating])) 280 | traces.append(trace) 281 | 282 | title = "Speed Over Time (Speed Training)" if speed_attempt else "Speed Over Time" 283 | 284 | if topic != "null": 285 | title = "Speed Over Time (" + topic + ")" 286 | 287 | layout = go.Layout(title=title, xaxis={'title': 'Month', 'autorange': True}, 288 | yaxis={'title': 'Time (Minutes)', 'autorange': True}, 289 | autosize=True) 290 | 291 | figure = go.Figure(data=traces, layout=layout) 292 | 293 | figure.update_layout(autosize=True) 294 | figure.update_layout(yaxis_range=[0, 90]) 295 | figure.update_layout(xaxis_range=[1, 12]) 296 | 297 | graph = figure.to_html(include_plotlyjs=False) 298 | return graph 299 | 300 | 301 | def stacked_bar_solve_count_graph(user, topic, speed_attempt, show_all): 302 | """ 303 | Graph: Number of problems solved v.s. Date 304 | Stacked bar graph - stack of ratings 305 | """ 306 | 307 | if topic == "null": 308 | if show_all: 309 | problems = ProblemStatus.objects.filter(userprofile__user=user).all() 310 | else: 311 | problems = ProblemStatus.objects.filter(userprofile__user=user, speed_attempt=speed_attempt).all() 312 | else: 313 | if show_all: 314 | problems = ProblemStatus.objects.filter(userprofile__user=user, 315 | problem__subcategory__problem_topic_id=1).all() 316 | else: 317 | problems = ProblemStatus.objects.filter(userprofile__user=user, problem__subcategory__problem_topic_id=1, 318 | speed_attempt=speed_attempt).all() 319 | problems = problems.distinct() 320 | 321 | cur_month = datetime.now().month 322 | mx = 15 323 | 324 | ratings = {} 325 | for rating in range(800, 3600, 100): 326 | ratings[rating] = {} 327 | 328 | ratings["Non Codeforces"] = {} 329 | 330 | to_iter = [rating for rating in range(800, 3600, 100)] 331 | to_iter.append("Non Codeforces") 332 | 333 | for prob in problems: 334 | p_rating = prob.problem.rating 335 | solve_month = prob.solve_time.month 336 | 337 | if prob.problem.source.name != "Codeforces": 338 | p_rating = "Non Codeforces" 339 | 340 | if solve_month not in ratings[p_rating]: 341 | ratings[p_rating][solve_month] = 1 342 | else: 343 | ratings[p_rating][solve_month] += 1 344 | mx = max(mx, ratings[p_rating][solve_month]) 345 | 346 | traces = [] 347 | 348 | for rating in to_iter: 349 | x = [] 350 | y = [] 351 | 352 | for month in range(1, cur_month + 1, 1): 353 | if month not in ratings[rating]: 354 | ratings[rating][month] = 0 355 | 356 | x.append(num_to_month(month)) 357 | y.append(ratings[rating][month]) 358 | 359 | trace = go.Bar(x=x, y=y, name=rating, marker=dict(color=colors[rating])) 360 | traces.append(trace) 361 | 362 | title = "Solve Count (Speed Training)" if speed_attempt else "Solve Count" 363 | 364 | if topic != "null": 365 | title = "Solve Count (" + topic + ")" 366 | 367 | layout = go.Layout(title=title, xaxis={'title': 'Month', }, 368 | yaxis={'title': 'Number of Problems Solved'}) 369 | 370 | figure = go.Figure(data=traces, layout=layout) 371 | figure.update_layout(autosize=True) 372 | figure.update_layout(barmode='stack') 373 | figure.update_xaxes(automargin=True) 374 | figure.update_layout(yaxis_range=[0, mx + 5]) 375 | 376 | graph = figure.to_html(include_plotlyjs=False) 377 | return graph 378 | -------------------------------------------------------------------------------- /cpdrills/templates/training/main_pages/speedtrain_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_filters %} 3 | {% load crispy_forms_tags %} 4 | {% load static %} 5 | 6 | {% block title %} 7 | Speed Training | CP Drills 8 | {% endblock title %} 9 | 10 | {% block head %} 11 | 23 | {% endblock head %} 24 | 25 | {% block content %} 26 | 30 | 31 |
32 | 33 |
34 |
35 |
36 |

Get a Problem

37 | Provide a valid codeforces handle under your profile to get personalised recommendations. Ignore this message if you have already done so. 39 | 40 |
41 |
42 | {% csrf_token %} 43 | 44 |
45 |
46 | 47 | 50 | 51 | 56 | 57 |
58 | 59 | {{ form.fields.rating.help_text }} 61 | 62 |
63 | 64 | {% if messages %} 65 |
66 | {% for message in messages %} 67 | {% if message.tags == "error" %} 68 |
69 | {{ message }} 70 |
71 | {% else %} 72 |
73 | {{ message }} 74 |
75 | {% endif %} 76 | {% endfor %} 77 |
78 | {% endif %} 79 | 80 |
81 | {# Choosing filters is optional.#} 83 |
84 |
85 | {{ form.topics|as_crispy_field }} 86 |
87 |
88 | 89 |
90 |
91 |

{{ form.contest_type|as_crispy_field }}

92 |
93 |
94 | 95 |
96 |
97 |

{{ form.problem_type|as_crispy_field }}

98 |
99 |
100 |
101 | 102 | 103 |
104 |
105 |
106 | 107 |
108 |
109 |
110 |
111 |
112 |

Solve a Problem

113 |
114 | 115 |
116 |

Name: {{ problem.name }}

117 |

Rating: {{ problem.rating }}

118 |

Link: {{ problem.link }} 120 |

121 |
122 |
123 |
124 |
125 | 126 |
127 |
128 |
129 |
130 |

Stopwatch

131 |
132 | 133 |
134 |

135 | 139 | : 140 | 143 | : 144 | 147 |

148 |
149 | 150 |
151 |

Press 'Save Progress' to save the time displayed on the 152 | stopwatch. Leave it at its default value if you do not wish to save the time. 153 |

154 |
155 |
156 | 157 | 158 | 159 | 162 | 165 |
166 |
167 |
168 |
169 |
170 | 171 |
172 |
173 |
174 | {{ graph_time_date|safe }} 175 |
176 |
177 | 178 |
179 |
180 | {{ graph_bar_count|safe }} 181 |
182 |
183 |
184 | 185 |
186 |
187 |
188 | {{ graph1|safe }} 189 |
190 |
191 | 192 |
193 |
194 |
195 | {% endblock content %} 196 | 197 | {% block scripts %} 198 | 199 | 320 | 321 | 323 | 324 | 393 | {% endblock scripts %} -------------------------------------------------------------------------------- /cpdrills/templates/training/main_pages/practice.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% load training_extras %} 4 | 5 | {% block title %} 6 | Dynamic Programming | CP Drills 7 | {% endblock title %} 8 | 9 | {% block content %} 10 | 15 | 16 |
17 |
18 |

{{ topic }}

19 |
20 | 21 |
22 |
23 |
24 |
25 |
26 |

Select A Problem

27 |
28 |
29 |
30 |
31 | 35 | 42 |
43 |
44 | 45 |
46 | 47 | Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }} 48 | 49 | 71 |
72 | 73 |
74 | 75 |
77 | 78 | 79 | 80 | 81 | 82 | {# #} 83 | 84 | 85 | {# #} 86 | 87 | 88 | 89 | {% for problem in page_obj %} 90 | {% with problem_status=problem.code|check_solve:user.username %} 91 | {% if problem_status.0 == "True" %} 92 | 94 | {% else %} 95 | 96 | {% endif %} 97 | 101 | 102 | {# #} 103 | 110 | 111 | 112 | {% endwith %} 113 | {% endfor %} 114 | 115 |
ProblemSourceTagsToggleTimeSolution
{{ problem.name | capfirst }} 100 | {{ problem.source }}{{ problem.tags }} 104 | 109 | {{ problem_status.1 }}
116 |
117 |
118 |
119 |

121 | Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}

122 |
123 |
124 | 146 |
147 |
148 |
149 |
150 |
151 | 152 |
153 |
154 |
155 |
156 |
157 |

Solve a Problem

158 |
159 | 160 |
161 |

Name:

162 |

Source:

163 | 166 |
167 |
168 |
169 |
170 | 171 |
172 |
173 |
174 |
175 |

Stopwatch

176 |
177 | 178 |
179 |

180 | 184 | : 185 | 188 | : 189 | 192 |

193 |
194 |
195 |

Select a problem and press 'Save Progress' to save 196 | the time 197 | displayed on the 198 | stopwatch. Leave the stopwatch at its default value if you do not wish 199 | to save 200 | the time. 201 |

202 |
203 | 204 |
205 | 206 | 207 | 208 | 213 | 217 |
218 |
219 |
220 |
221 | 222 |
223 |
224 | 225 |
226 |
227 |
228 | {{ graph_time_date|safe }} 229 |
230 |
231 | 232 |
233 |
234 | {{ graph_bar_count|safe }} 235 |
236 |
237 |
238 | 239 |
240 |
241 |
242 | {{ graph1|safe }} 243 |
244 |
245 | 246 |
247 |
248 | {% endblock content %} 249 | 250 | {% block scripts %} 251 | 253 | 254 | 255 | 339 | 340 | 341 | 457 | 458 | 459 | 460 | 461 | 462 | {% endblock scripts %} --------------------------------------------------------------------------------