mirror of
https://github.com/Findus23/se-simulator.git
synced 2024-09-19 15:53:45 +02:00
some improvements and basic quiz
This commit is contained in:
parent
8d89773539
commit
49f2ec65ba
8 changed files with 177 additions and 64 deletions
46
server.py
46
server.py
|
@ -1,9 +1,10 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import time
|
||||||
import sass
|
import sass
|
||||||
from flask import render_template, send_from_directory, abort, session, jsonify, make_response
|
from flask import render_template, send_from_directory, abort, session, jsonify, make_response
|
||||||
from flask_limiter import Limiter
|
from flask_limiter import Limiter
|
||||||
from flask_limiter.util import get_remote_address
|
from flask_limiter.util import get_remote_address
|
||||||
|
from flask_session import Session
|
||||||
from playhouse.flask_utils import PaginatedQuery, get_object_or_404
|
from playhouse.flask_utils import PaginatedQuery, get_object_or_404
|
||||||
from playhouse.shortcuts import model_to_dict
|
from playhouse.shortcuts import model_to_dict
|
||||||
from sassutils.wsgi import SassMiddleware
|
from sassutils.wsgi import SassMiddleware
|
||||||
|
@ -15,7 +16,13 @@ from models import *
|
||||||
|
|
||||||
app.jinja_env.globals.update(prettydate=utils.prettydate)
|
app.jinja_env.globals.update(prettydate=utils.prettydate)
|
||||||
|
|
||||||
|
SESSION_TYPE = 'redis'
|
||||||
|
SESSION_COOKIE_SECURE = config.production
|
||||||
|
SESSION_USE_SIGNER = True
|
||||||
|
SESSION_KEY_PREFIX = "StackDataSessions:"
|
||||||
|
app.config.from_object(__name__)
|
||||||
app.secret_key = config.secret_key
|
app.secret_key = config.secret_key
|
||||||
|
Session(app)
|
||||||
|
|
||||||
limiter = Limiter(
|
limiter = Limiter(
|
||||||
app,
|
app,
|
||||||
|
@ -46,7 +53,8 @@ def index(site=None):
|
||||||
num_pages=paginated_query.get_page_count(),
|
num_pages=paginated_query.get_page_count(),
|
||||||
page=paginated_query.get_page(),
|
page=paginated_query.get_page(),
|
||||||
questions=paginated_query.get_object_list(),
|
questions=paginated_query.get_object_list(),
|
||||||
site=site_element
|
site=site_element,
|
||||||
|
voted=session["voted"] if "voted" in session and not config.make_cacheable else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,7 +71,23 @@ def question(slug):
|
||||||
return render_template(
|
return render_template(
|
||||||
"detail.html",
|
"detail.html",
|
||||||
question=question,
|
question=question,
|
||||||
answers=answers
|
answers=answers,
|
||||||
|
voted=session["voted"] if "voted" in session and not config.make_cacheable else None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/quiz')
|
||||||
|
def quiz():
|
||||||
|
time1 = time.time()
|
||||||
|
question = Question.select(Question, Title, User, Site) \
|
||||||
|
.join(Title).switch(Question) \
|
||||||
|
.join(User).switch(Question) \
|
||||||
|
.join(Site).where((Question.upvotes - Question.downvotes >= 0)).order_by(SQL("RAND()")).limit(1).get()
|
||||||
|
time2 = time.time()
|
||||||
|
print('{} ms'.format((time2 - time1) * 1000.0))
|
||||||
|
return render_template(
|
||||||
|
"quiz.html",
|
||||||
|
question=question
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,11 +102,6 @@ def sites():
|
||||||
|
|
||||||
@app.route('/test')
|
@app.route('/test')
|
||||||
def sdfdsfds():
|
def sdfdsfds():
|
||||||
user = User.select().get()
|
|
||||||
for question in Question.select():
|
|
||||||
question.upvotes = 1
|
|
||||||
question.downvotes = 1
|
|
||||||
question.save()
|
|
||||||
return jsonify(
|
return jsonify(
|
||||||
model_to_dict(Answer.select().where((Answer.question.is_null())).get()))
|
model_to_dict(Answer.select().where((Answer.question.is_null())).get()))
|
||||||
|
|
||||||
|
@ -91,11 +110,9 @@ def sdfdsfds():
|
||||||
@limiter.limit("10 per minute")
|
@limiter.limit("10 per minute")
|
||||||
def vote(type, id, vote):
|
def vote(type, id, vote):
|
||||||
if "voted" not in session:
|
if "voted" not in session:
|
||||||
voted = []
|
session["voted"] = {}
|
||||||
else:
|
print(session["voted"])
|
||||||
voted = session["voted"]
|
if (type, id) in session["voted"]:
|
||||||
print(voted)
|
|
||||||
if (type, id) in voted:
|
|
||||||
abort(403)
|
abort(403)
|
||||||
if type == "question":
|
if type == "question":
|
||||||
if vote == "up":
|
if vote == "up":
|
||||||
|
@ -113,8 +130,7 @@ def vote(type, id, vote):
|
||||||
return abort(404)
|
return abort(404)
|
||||||
else:
|
else:
|
||||||
return abort(404)
|
return abort(404)
|
||||||
voted.append((type, id))
|
session["voted"][(type, id)] = vote == "up"
|
||||||
session["voted"] = voted
|
|
||||||
query.execute()
|
query.execute()
|
||||||
if type == "question":
|
if type == "question":
|
||||||
query = Question.select(Question.upvotes, Question.downvotes).where(Question.id == id).get()
|
query = Question.select(Question.upvotes, Question.downvotes).where(Question.id == id).get()
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{{ siteheader(question.site) }}
|
{{ siteheader(question.site) }}
|
||||||
<h1>{{ question.title.text }}</h1>
|
<h1>{{ question.title.text }}</h1>
|
||||||
|
{% set vote=voted[("question", question.id)] %}
|
||||||
<div class="content question">
|
<div class="content question">
|
||||||
<div class="vote" data-id="{{ question.id }}" data-type="question">
|
<div class="vote" data-id="{{ question.id }}" data-type="question">
|
||||||
<a class="up"></a>
|
<a class="up {{ "active" if vote == True }}"></a>
|
||||||
<div>{{ question.upvotes - question.downvotes }}</div>
|
<div>{{ question.upvotes - question.downvotes }}</div>
|
||||||
<a class="down"></a>
|
<a class="down {{ "active" if vote == False }}"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="contentbox">
|
<div class="contentbox">
|
||||||
|
|
||||||
|
@ -26,11 +26,12 @@
|
||||||
</div>
|
</div>
|
||||||
<h2 class="answerheader">{{ answers|length }} Answers</h2>
|
<h2 class="answerheader">{{ answers|length }} Answers</h2>
|
||||||
{% for answer in answers %}
|
{% for answer in answers %}
|
||||||
|
{% set vote=voted[("answer", answer.id)] %}
|
||||||
<div class="content answer">
|
<div class="content answer">
|
||||||
<div class="vote" data-id="{{ answer.id }}" data-type="answer" data-ranking="{{ answer.ci_lower_bound }}">
|
<div class="vote" data-id="{{ answer.id }}" data-type="answer" data-ranking="{{ answer.ci_lower_bound }}">
|
||||||
<a class="up"></a>
|
<a class="up {{ "active" if vote == True }}"></a>
|
||||||
<div>{{ answer.upvotes - answer.downvotes }}</div>
|
<div>{{ answer.upvotes - answer.downvotes }}</div>
|
||||||
<a class="down"></a>
|
<a class="down {{ "active" if vote == False }}"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="contentbox">
|
<div class="contentbox">
|
||||||
{% for paragraph in answer.text.split("\n") %}
|
{% for paragraph in answer.text.split("\n") %}
|
||||||
|
|
|
@ -3,20 +3,21 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{{ siteheader(site) }}
|
{{ siteheader(site) }}
|
||||||
|
|
||||||
<label for="siteselector">Seite</label>
|
<label class="label-inline" for="siteselector">Seite</label>
|
||||||
<input id="siteselector" class="awesomplete" value="{{ site.url if not site.fallback }}"/>
|
<input id="siteselector" class="awesomplete" value="{{ site.url if not site.fallback }}" data-mode="filter"/>
|
||||||
{% if not site.fallback %}
|
{% if not site.fallback %}
|
||||||
|
|
||||||
<a href="{{ url_for("index") }}">Clear filter</a>
|
<a href="{{ url_for("index") }}">Clear filter</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ pagination(pagearray, num_pages, page, True) }}
|
{{ pagination(pagearray, num_pages, page, True) }}
|
||||||
{% for question in questions %}
|
{% for question in questions %}
|
||||||
|
{% set vote=voted[("question", question.id)] %}
|
||||||
<div class="content question"
|
<div class="content question"
|
||||||
style="border-right-color:{{ question.site.tag_foreground_color }};background-color:{{ question.site.tag_background_color }}">
|
style="border-right-color:{{ question.site.tag_foreground_color }};background-color:{{ question.site.tag_background_color }}">
|
||||||
<div class="vote" data-id="{{ question.id }}" data-type="question">
|
<div class="vote" data-id="{{ question.id }}" data-type="question">
|
||||||
<a class="up"></a>
|
<a class="up {{ "active" if vote == True }}"></a>
|
||||||
<div>{{ question.upvotes - question.downvotes }}</div>
|
<div>{{ question.upvotes - question.downvotes }}</div>
|
||||||
<a class="down"></a>
|
<a class="down {{ "active" if vote == False }}"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="contentbox">
|
<div class="contentbox">
|
||||||
<a href="https://{{ question.site.url }}" class="sitename" target="_blank" rel="noopener">
|
<a href="https://{{ question.site.url }}" class="sitename" target="_blank" rel="noopener">
|
||||||
|
|
29
templates/quiz.html
Normal file
29
templates/quiz.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<input id="siteselector" class="awesomplete" data-mode="quiz"/>
|
||||||
|
<button id="check">Check</button>
|
||||||
|
|
||||||
|
<h1>{{ question.title.text }}</h1>
|
||||||
|
<div class="content question">
|
||||||
|
<div class="vote">
|
||||||
|
<a class="up"></a>
|
||||||
|
<div>{{ question.upvotes - question.downvotes }}</div>
|
||||||
|
<a class="down"></a>
|
||||||
|
</div>
|
||||||
|
<div class="contentbox">
|
||||||
|
|
||||||
|
{% for paragraph in question.text.split("\n") %}
|
||||||
|
<p>{{ paragraph }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
<div class="contentfooter">
|
||||||
|
<div class="authorbox">
|
||||||
|
asked {{ prettydate(question.datetime) }}
|
||||||
|
<br>
|
||||||
|
{{ question.user.username }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -1,21 +1,17 @@
|
||||||
document.addEventListener("DOMContentLoaded", function (event) {
|
document.addEventListener("DOMContentLoaded", function (event) {
|
||||||
var vote = document.getElementsByClassName("vote");
|
var vote = document.getElementsByClassName("vote");
|
||||||
console.warn(vote);
|
|
||||||
Array.prototype.forEach.call(vote, function (elvote) {
|
Array.prototype.forEach.call(vote, function (elvote) {
|
||||||
var id = elvote.dataset.id;
|
var id = elvote.dataset.id;
|
||||||
var type = elvote.dataset.type;
|
var type = elvote.dataset.type;
|
||||||
Array.prototype.forEach.call(elvote.querySelectorAll("a"), function (el) {
|
Array.prototype.forEach.call(elvote.querySelectorAll("a"), function (el) {
|
||||||
el.addEventListener("click", function (event) {
|
el.addEventListener("click", function (event) {
|
||||||
console.info(elvote);
|
|
||||||
var vote = el.classList[0];
|
var vote = el.classList[0];
|
||||||
console.info(type, id, vote);
|
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
request.open("POST", "/api/vote/" + type + "/" + id + "/" + vote, true);
|
request.open("POST", "/api/vote/" + type + "/" + id + "/" + vote, true);
|
||||||
|
|
||||||
request.onload = function () {
|
request.onload = function () {
|
||||||
if (this.status >= 200 && this.status < 400) {
|
if (this.status >= 200 && this.status < 400) {
|
||||||
var resp = JSON.parse(this.response);
|
var resp = JSON.parse(this.response);
|
||||||
console.info(resp);
|
|
||||||
el.classList.add("active");
|
el.classList.add("active");
|
||||||
elvote.querySelector("div").textContent = resp.upvotes - resp.downvotes
|
elvote.querySelector("div").textContent = resp.upvotes - resp.downvotes
|
||||||
} else {
|
} else {
|
||||||
|
@ -31,6 +27,8 @@ document.addEventListener("DOMContentLoaded", function (event) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
var input = document.getElementById("siteselector");
|
var input = document.getElementById("siteselector");
|
||||||
|
if (input) {
|
||||||
|
var mode = input.dataset.mode;
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
request.open("GET", "/api/sites", true);
|
request.open("GET", "/api/sites", true);
|
||||||
|
|
||||||
|
@ -50,14 +48,21 @@ document.addEventListener("DOMContentLoaded", function (event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new Awesomplete(input, {
|
new Awesomplete(input, {
|
||||||
list: list
|
list: list,
|
||||||
|
minChars: 0,
|
||||||
|
autoFirst: true
|
||||||
});
|
});
|
||||||
input.addEventListener("awesomplete-select", function (event) {
|
input.addEventListener("awesomplete-select", function (event) {
|
||||||
if (!(event.text.value in resp)) { // shouldn't happen
|
if (!(event.text.value in resp)) { // shouldn't happen
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
var selectedSite = resp[event.text.value];
|
var selectedSite = resp[event.text.value];
|
||||||
|
|
||||||
|
if (mode === "filter") {
|
||||||
window.location.href = "/s/" + selectedSite.url
|
window.location.href = "/s/" + selectedSite.url
|
||||||
|
} else if (mode === "quiz") {
|
||||||
|
console.log(selectedSite);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,4 +75,6 @@ document.addEventListener("DOMContentLoaded", function (event) {
|
||||||
};
|
};
|
||||||
request.send();
|
request.send();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Based on https://github.com/LeaVerou/awesomplete/blob/gh-pages/awesomplete.base.css
|
||||||
|
// Copyright (c) 2015 Lea Verou
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
|
||||||
.awesomplete [hidden] {
|
.awesomplete [hidden] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
55
web/static/sass/_awesomplete.custom.scss
Normal file
55
web/static/sass/_awesomplete.custom.scss
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// Based on https://github.com/LeaVerou/awesomplete/blob/gh-pages/awesomplete.theme.css
|
||||||
|
// Copyright (c) 2015 Lea Verou
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
@import "awesomplete.base";
|
||||||
|
|
||||||
|
.awesomplete {
|
||||||
|
> ul {
|
||||||
|
padding: 1rem 1rem 0;
|
||||||
|
margin: .2em 0 0;
|
||||||
|
|
||||||
|
border: 1px solid rgba(0, 0, 0, .3);
|
||||||
|
box-shadow: .05em .2em .6em rgba(0, 0, 0, .2);
|
||||||
|
text-shadow: none;
|
||||||
|
> li {
|
||||||
|
position: relative;
|
||||||
|
padding: .2em .5em;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: hsl(200, 40%, 80%);
|
||||||
|
color: black;
|
||||||
|
mark {
|
||||||
|
background: hsl(68, 100%, 41%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[aria-selected="true"] {
|
||||||
|
background: hsl(205, 40%, 40%);
|
||||||
|
color: white;
|
||||||
|
mark {
|
||||||
|
background: hsl(86, 100%, 21%);
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:before { // pointer
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -.43em;
|
||||||
|
left: 1em;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
padding: .4em;
|
||||||
|
background: white;
|
||||||
|
border: inherit;
|
||||||
|
border-right: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mark {
|
||||||
|
background: hsl(65, 100%, 50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,11 +2,10 @@ $link-color: #07C;
|
||||||
$link-hover-color: #3af;
|
$link-hover-color: #3af;
|
||||||
|
|
||||||
@import "../../milligram/src/milligram";
|
@import "../../milligram/src/milligram";
|
||||||
@import "awesomplete.base";
|
@import "awesomplete.custom";
|
||||||
@import "pagination";
|
@import "pagination";
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: #111;
|
color: #111;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue