Commit 989a47e8 authored by Antoine Angot's avatar Antoine Angot
Browse files

added: courses part1

parent dcf6999e
# How to contribute
## Requirements
This project requires python 3.5 at least.
## Install
```
pip install -r requirements.txt
./manage.py migrate
......
......@@ -42,12 +42,14 @@ urlpatterns = [
path("accounts/", include("registration.backends.default.urls")),
path("dashboard/", dashboard_view, name="dashboard"),
path("exercises/", ExerciseListView.as_view(), name="exercises"),
path("favicon.ico", favicon_view),
path("exercises/<slug:slug>", ExerciseView.as_view(), name="exercise"),
path("favicon.ico", favicon_view),
path("profile/<int:pk>", ProfileView.as_view(), name="profile"),
path("stats/", StatsListView.as_view(), name="stats"),
path("stats/<int:pk>", StatsDetailView.as_view(), name="stats"),
path("chat/", chat, name="chat"),
path("lessons/", LessonListView.as_view(), name="lesson"),
path("lessons/<slug:slug>", LessonView.as_view(), name="lesson"),
]
if settings.DEBUG:
......
# Generated by Django 2.1 on 2019-01-25 18:46
from django.db import migrations, models
import django_extensions.db.fields
class Migration(migrations.Migration):
dependencies = [
('website', '0008_auto_20190118_2151'),
]
operations = [
migrations.CreateModel(
name='Lesson',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('slug', django_extensions.db.fields.AutoSlugField(blank=True, editable=False, populate_from=['title'])),
('content', models.TextField()),
('is_published', models.BooleanField(default=False)),
('position', models.FloatField(default=0)),
],
options={
'ordering': ['position'],
},
),
]
......@@ -58,6 +58,21 @@ class Answer(models.Model):
def get_absolute_url(self):
return reverse("exercise", args=[self.exercise.slug])
class Lesson(models.Model):
title = models.CharField(max_length=255)
slug = AutoSlugField(populate_from=["title"], editable=True)
content = models.TextField()
is_published = models.BooleanField(default=False)
position = models.FloatField(default=0)
class Meta:
ordering = ["position"]
def get_absolute_url(self):
return reverse("lesson", args=[self.slug])
def __str__(self):
return self.title
def cb_new_answer(sender, instance, created, **kwargs):
group = "answers.{}.{}".format(instance.user.id, instance.exercise.id)
......
{% extends "hkis/layout.html" %}
{% load hkis_extra %}
{% load i18n static bootstrap4 %}
{% block title %}{{ object.title }}{% endblock %}
{% block stylesheets %}
<link href="{% static "css/codehilite.css" %}" rel="stylesheet" type="text/css" />
{% endblock %}
{% block container %}
{{ answer_form.media }}
{% if next %}
<div class="float-right">
<a class="btn btn-primary" href="{% url "lesson" next %}">
Next →
</a>
</div>
{% endif %}
<div style="margin-bottom: 25px;">
{{ object.wording|markdown|md_to_bootstrap|i18n_doc_links:LANGUAGE_CODE }}
</div>
<div class="row">
<div class="col-md-8">
<h2>{{ lesson.title }}</h2>
<form id="answer_form" action="/api/answers/" method="post" onsubmit="document.getElementById('submit_answer').setAttribute('disabled', true); AJAXSubmit(this); return false;">{% csrf_token %}
{% for field in answer_form %}
<div class="fieldWrapper">
{{ field }}
</div>
{% endfor %}
<button id="submit_snippet" class="btn btn-primary" onclick="AJAXSubmit(document.getElementById('answer_form'), '/api/snippets/'); this.setAttribute('disabled', true); return false;">Run your code</button>
<button id="submit_answer" class="btn btn-primary" type="submit">Save and submit your code</button>
</form>
</div>
<div class="col-md-4">
<h2>Results</h2>
<div id="answers-result" class="result">
<table id="answer-table" class="table">
{% for answer in answers %}
<tr><td id="td-{{ answer.id }}">
{% if not answer.is_corrected %}
<img src="{% static "img/loading_anim.gif" %}" height=30> Waiting for correction
{% else %}
<pre>{{ answer.correction_message }}</pre>
{% endif %}
</td></tr>
{% endfor %}
</table>
</div>
</div>
</div>
{% autoescape off %}
<script>
(function() {
function append_to_answer_table(html) {
if ($("#answer-table tr:first").length)
$("#answer-table tr:first").before(html);
else
$("#answer-table").append(html);
}
function fill_answer(answer_id, is_corrected, message) {
var result = document.getElementById("answers-result");
if (!result)
return;
var elem = document.getElementById("td-".concat(answer_id));
if (!elem) {
var answer_table = document.getElementById("answer-table");
append_to_answer_table('<tr><td id="td-'.concat(answer_id).concat('">…</td> </tr>'));
elem = document.getElementById("td-".concat(answer_id));
}
if (is_corrected) {
var pre = document.createElement("pre");
var content = document.createTextNode(message);
pre.appendChild(content);
while (elem.firstChild) elem.removeChild(elem.firstChild);
elem.appendChild(pre);
document.getElementById('submit_answer').removeAttribute('disabled');
} else {
elem.innerHTML = '<img src="{% static "img/loading_anim.gif" %}" height=30> Waiting for correction';
}
}
function fill_snippet(id, executed_at, output) {
var result = document.getElementById("answers-result");
if (!result)
return;
var elem = document.getElementById("td-snippet-".concat(id));
if (!elem) {
var answer_table = document.getElementById("answer-table");
append_to_answer_table('<tr><td id="td-snippet-'.concat(id).concat('">…</td> </tr>'));
elem = document.getElementById("td-snippet-".concat(id));
}
if (executed_at !== undefined) {
var pre = document.createElement("pre");
var content = document.createTextNode(output);
pre.appendChild(content);
while (elem.firstChild) elem.removeChild(elem.firstChild);
elem.appendChild(pre);
document.getElementById("submit_snippet").removeAttribute('disabled');
} else {
elem.innerHTML = '<img src="{% static "img/loading_anim.gif" %}" height=30> Waiting for test run';
}
}
function websocket_connect(ws_location) {
var ws = new WebSocket(ws_location);
ws.onmessage = function(e) {
var data = JSON.parse(e.data);
console.log("WebSocket recv:", data);
if (data.type == "snippet")
fill_snippet(data.id, data.executed_at, data.output)
if (data.type == "correction")
fill_answer(data.answer, data.is_corrected, data.correction_message);
};
ws.onclose = function() {
ws = null;
// Try to reconnect in 5 seconds
setTimeout(function(){websocket_connect(ws_location)}, 5000);
};
}
var ws_protocol = window.location.protocol == "http:" ? "ws:" : "wss:";
websocket_connect(ws_protocol + "//" + window.location.host +
"/ws/answers/" + {{ user.id }} + "/" + {{object.id}} + "/");
})();
</script>
{% endautoescape %}
{% endblock %}
{% extends "hkis/layout.html" %}
{% load i18n %}
{% load static %}
{% load bootstrap4 %}
{% block title %}Python course{% endblock %}
{% block container %}
<h1>Python lessons</h1>
<table class="table table-sm table-hover records_list">
<tbody>
{% for lesson in lessons %}
<tr onclick="if (!event.ctrlKey){location.href='{% url "lesson" lesson.slug %}';}">
<td>
<a href="{% url "lesson" lesson.slug %}">
<h5>{{ lesson.number }}. {{ lesson.title }}</h5>
</a>
{{ lesson.lead }}
</td>
<td scope="row">
{% if lesson.tried %}
{% if lesson.done %}
<span class="good h3"></span>
{% else %}
<span class="bad h3"></span>
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
......@@ -14,7 +14,7 @@ from django.contrib.auth.models import User
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.list import ListView
from website.models import Exercise, Answer
from website.models import Exercise, Answer, Lesson
from website.forms import AnswerForm
......@@ -46,7 +46,6 @@ class ProfileView(LoginRequiredMixin, UpdateView):
messages.info(self.request, "Profile updated")
return reverse("profile", kwargs={"pk": self.request.user.id})
class ExerciseListView(LoginRequiredMixin, ListView):
model = Exercise
template_name = "hkis/exercises.html"
......@@ -112,6 +111,43 @@ class ExerciseView(LoginRequiredMixin, DetailView):
context["next"] = None
return context
class LessonListView(LoginRequiredMixin, ListView):
model = Lesson
template_name = "hkis/lessons.html"
def get_queryset(self):
self.queryset = Lesson.objects.filter(is_published=True)
return super().get_queryset()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["lessons"] = [
{
**vars(lesson),
"number": i + 1,
}
for i, lesson in enumerate(self.object_list)
]
return context
class LessonView(LoginRequiredMixin, DetailView):
model = Lesson
template_name = "hkis/lesson.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
context["next"] = (
Lesson.objects.filter(position__gt=self.object.position)
.order_by("position")[0]
.slug
)
except IndexError:
context["next"] = None
return context
class StatsListView(UserPassesTestMixin, ListView):
template_name = "hkis/stats_list.html"
......@@ -143,7 +179,7 @@ class StatsDetailView(UserPassesTestMixin, DetailView):
"is_valid": exercice.nb_valid_anwser > 0,
"slug": exercice.slug,
}
for exercice in Exercise.objects.annotate(
for exercice in Lesson.objects.annotate(
nb_anwser=Count(
"answers", filter=Q(answers__user_id=user.id)
),
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment