From 73091938e99dbb0c5ba7c6401f4992bff082c50e Mon Sep 17 00:00:00 2001 From: Kostanjevec Date: Sun, 7 Aug 2022 16:35:28 +0200 Subject: [PATCH 1/5] added a Base for ratings --- create_db.py | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/create_db.py b/create_db.py index e052e1b..e56028a 100644 --- a/create_db.py +++ b/create_db.py @@ -1,18 +1,8 @@ #!/usr/bin/python3 -from csv import unregister_dialect -#import email -from sqlite3 import Date -from xmlrpc.client import Boolean -from click import password_option -from pymysql import Time, Timestamp -#from ssl import _PasswordType -#from xmlrpc.client import DateTime -#from matplotlib.pyplot import title -from sqlalchemy import Column, Integer, Float, String, Text, DateTime, ForeignKey, BOOLEAN +from sqlalchemy import Column, Integer, Float, String, Text, Date, ForeignKey, BOOLEAN from sqlalchemy.ext.declarative import declarative_base -#from sqlalchemy.orm import relationship -from sqlalchemy import func, create_engine,join +from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from config import CONFIG @@ -46,15 +36,13 @@ class Deck(Base): current_card = Column(Integer) completed = Column(BOOLEAN) -""" -class Review(Base): - __tablename__ = 'review' +class Rating(Base): + __tablename__ = 'rating' id = Column(Integer, primary_key=True, autoincrement=True) - reviewer_id = Column(Integer, ForeignKey("user.id"), nullable=False) - card_id = Column(Integer, ForeignKey("user.id"), nullable=False) - review = Column(Text) - time = Column(DateTime) -""" + user_id = Column(Integer, ForeignKey("user.id"), nullable=False) + card_id = Column(Integer, ForeignKey("card.id"), nullable=False) + rating_value = Column(Text) #Yes, Maybe, No + rating_time = Column(Date) engine = create_engine(CONFIG['DB_CONNECTION']) From 6afecb846c0c941f40bf37f54741ff6e8e1c9494 Mon Sep 17 00:00:00 2001 From: Kostanjevec Date: Sun, 7 Aug 2022 16:46:03 +0200 Subject: [PATCH 2/5] added a todo --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cec5dd..cff83d8 100644 --- a/README.md +++ b/README.md @@ -36,4 +36,25 @@ If you have any questions, feedback, ideas or if you would like contribute, plea ## Build instructions -Comming soon... \ No newline at end of file +Comming soon... + +## TODO +### Design issues +#### Sorting and matching +The overlap of this features creates some issues. Interest in terms of sorting is likely lost when an item is read, but it may not be lost in terms of matching. + +### Features +#### Links +* cards can be links +* allow importing of links +* db representation of link-cards +#### Algorithm +* SR-algorithm for decks and appropriate db representation + * algorithm calculates due cards only with the help of rating history and time of rating. + * formula for next interval + * algorithm resets on every 'yes' vote. + +### UI +* Flash messages should have an approriate color (error is red...) +* Visual cue to distinguish shared vs private cards +* PDF action to view more than 10 pages. From 513a28ef515c08f82134f7a31fec1d200541e033 Mon Sep 17 00:00:00 2001 From: Kostanjevec Date: Sun, 7 Aug 2022 17:46:04 +0200 Subject: [PATCH 3/5] moved deck session function to special file --- app.py | 9 +++-- prob_session.py | 96 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 prob_session.py diff --git a/app.py b/app.py index 0599d1d..eee5d41 100644 --- a/app.py +++ b/app.py @@ -1,14 +1,13 @@ -from flask import Flask, redirect, render_template, session, request, url_for, flash +from flask import Flask, redirect, render_template, session, request, url_for from auth import login_required, bp as auth_bp from deck import bp as deck_bp from menu import bp as menu_bp from upload import bp as upload_bp -from share import share from matches import bp as matches_bp from settings import bp as settings_bp +from prob_session import prob_session -from create_db import Card, Deck, get_session from config import CONFIG def create_app(test_config=None): @@ -31,6 +30,9 @@ def create_app(test_config=None): @app.route('/deck/index', methods=["GET", "POST"]) def deck(): + return prob_session() + """ + this is commented out because it was moved to pro_session.py. dbsession = get_session() if not 'user_id' in session: return index() @@ -118,6 +120,7 @@ def create_app(test_config=None): # Prikaži obrazec return render_template("deck/index.html", username=username, card=show_card) + """ @app.route("/share_button", methods=["GET", "POST"]) diff --git a/prob_session.py b/prob_session.py new file mode 100644 index 0000000..f83b859 --- /dev/null +++ b/prob_session.py @@ -0,0 +1,96 @@ +from flask import session, redirect, url_for,request, flash, render_template +from create_db import get_session, Deck, Card +from share import share + +""" +Refactoring the 'deck' function from app.py +""" + + +def prob_session(): + dbsession = get_session() + if not 'user_id' in session: + redirect(url_for('login')) + user_id = session['user_id'] + username = session['username'] + + #pokliče na bazo, da dobi str card idjev, ga spremeni v list in srevira karte po idjih + deck_query = dbsession.query(Deck).filter(Deck.owner_id == user_id) + deck_object = deck_query.filter(Deck.completed == False).first() + # @TODO: ce deck, ne obstaja, kaj naj zaj jas? + if len(deck_object.cards_by_id.split(",")) == 0: + deck_query.filter(Deck) + redirect(url_for('login')) + + # Smo oddali obrazec? + if request.method == 'POST': + card_id = request.form.get('card_id', None) + if not card_id: + raise Exception("card_id je nujen!") + + + submit_card = dbsession.query(Card).get(card_id) + # @TODO preveri, ali je card del trenutnega decka! + + # Ali damo share? Potem nastavi na share in ponovi obrazec + share_request = request.form.get("share", None) + if share_request: + # @TODO logika za share! + share(submit_card, user_id) + + # Če ne, gre za rate! + else: + rate = request.form.get('rate', None) #je to nevarno?? + print(rate) + + if not rate: + raise Exception("manjka rate info!") + + if rate == "Yes": + submit_card.interest_rate = 1 + + elif rate == "Maybe": + k = 0.5 + print(submit_card) + submit_card.interest_rate= abs(submit_card.interest_rate*k) + + elif rate == "No": + k = 0.1 + submit_card.interest_rate = abs(submit_card.interest_rate*k) + + elif rate == "Delete": + submit_card.interest_rate = 0 + #@TODO to bi lahko zbrisalo tudi file v določenih primerih + + # zaporedno število trenutnega carda v decku + next_card = deck_object.current_card + 1 + + # Ali je deck končan? + if next_card >= deck_object.number_of_cards: + deck_object.completed = True + dbsession.commit() + dbsession.close() + flash("Deck rating finished!") + return redirect(url_for("menu.index")) + + deck_object.current_card = next_card + dbsession.commit() + + # Loudamo naslednjo karto v decku + show_card_index = deck_object.current_card + show_card_id = deck_object.cards_by_id.split(",")[show_card_index] + print("GET CARD PLS", show_card_id) + show_card = dbsession.query(Card).get(show_card_id) + dbsession.close() + + if not show_card: + # @TODO how to handle missing card? + #deck_object.completed = 1 + #dbsession.commit() + #dbsession.close() + print("show_card missing") + return render_template("error/no_cards_in_collection.html") + #raise Exception("Ne najdem naslednje karte") + + # Prikaži obrazec + return render_template("deck/index.html", username=username, card=show_card) \ No newline at end of file From 997fc4eea0e908396ce9840f1355d4f37799d5e3 Mon Sep 17 00:00:00 2001 From: Kostanjevec Date: Sun, 7 Aug 2022 17:46:52 +0200 Subject: [PATCH 4/5] created blueprint for spaced repetition like deck session --- sr_session.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 sr_session.py diff --git a/sr_session.py b/sr_session.py new file mode 100644 index 0000000..baf58fd --- /dev/null +++ b/sr_session.py @@ -0,0 +1,75 @@ +from flask import session, redirect, url_for, request, render_template + +from share import share +from create_db import Card, Rating, get_session + + +def sr_session(): + dbsession = get_session() + + #check if user in + if not 'user_id' in session: + redirect(url_for('login')) + user_id = session['user_id'] + username = session['username'] + + + #koliko je new cards? + #koliko je due cards? + #ali obstaja naslednji card? + #kaj je naslednji card? + show_card = "" + + + #Form sent + # Share + # Rate + + + + # Smo oddali obrazec? + if request.method == 'POST': + card_id = request.form.get('card_id', None) + if not card_id: + raise Exception("card_id je nujen!") + + + submit_card = dbsession.query(Card).get(card_id) + # @TODO preveri, ali je card del trenutnega decka! + + # Ali damo share? Potem nastavi na share in ponovi obrazec + share_request = request.form.get("share", None) + if share_request: + # @TODO logika za share! + share(submit_card, user_id) + + # Če ne, gre za rate! + else: + rate = request.form.get('rate', None) #je to nevarno?? + print(rate) + + if not rate: + raise Exception("manjka rate info!") + + if rate == "Yes": + submit_card.interest_rate = 1 + + elif rate == "Maybe": + k = 0.5 + print(submit_card) + submit_card.interest_rate= abs(submit_card.interest_rate*k) + + elif rate == "No": + k = 0.1 + submit_card.interest_rate = abs(submit_card.interest_rate*k) + + elif rate == "Delete": + submit_card.interest_rate = 0 + #@TODO to bi lahko zbrisalo tudi file v določenih primerih + + # Any more cards? + + # Loudamo naslednjo karto + + # Prikaži obrazec + return render_template("deck/index.html", username=username, card=show_card) \ No newline at end of file From f45c744d4d003181b8ccdedc41c0c6f9738ce44a Mon Sep 17 00:00:00 2001 From: Kostanjevec Date: Wed, 10 Aug 2022 14:22:09 +0200 Subject: [PATCH 5/5] added a bunch of functions needed for sr_session --- sr_session.py | 149 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 114 insertions(+), 35 deletions(-) diff --git a/sr_session.py b/sr_session.py index baf58fd..b0e2933 100644 --- a/sr_session.py +++ b/sr_session.py @@ -1,8 +1,101 @@ +from random import choice +from datetime import date, timedelta from flask import session, redirect, url_for, request, render_template - +from sqlalchemy import desc from share import share from create_db import Card, Rating, get_session +""" +testing +""" +def autofill_ratings(): + dbsession = get_session() + for i in range(100): + card = Rating(user_id="1", card_id="10", rating_value=choice(["Yes", "Maybe", "No"]), rating_time=date.today()-timedelta(choice([i for i in range(100)]))) + dbsession.add(card) + dbsession.commit() + dbsession.close() + return None + + +""" +Main fuctions +""" +def calculate_interval(sorted_rates): + maybe_factor = 1.2 + no_factor = 2.1 + + print(sorted_rates[0].rating_value) + if sorted_rates == []: + #this is a new card, but interval can't be zero cuz multiplication... may have to handeled differently + return 1 + elif sorted_rates[0].rating_value == "Yes": + return 1 + elif sorted_rates[0].rating_value == "Maybe": + sorted_rates.pop(0) + return calculate_interval(sorted_rates) * maybe_factor + elif sorted_rates[0].rating_value == "No": + sorted_rates.pop(0) + return calculate_interval(sorted_rates) * no_factor + elif sorted_rates[0].rating_value == "Delete": + return "Deleted" + + +def get_interval(card_id): + """ + takes card_id looks at the history of rates and gives the current interval + """ + dbsession = get_session() + sorted_rates_by_card = dbsession.query(Rating).filter(Rating.card_id == card_id).order_by(desc(Rating.rating_time)).all() + if sorted_rates_by_card == None: + print("ni kart... kaj zdaj") #@TODO + return None + else: + interval = calculate_interval(sorted_rates_by_card) + return round(interval) + +#print(get_interval(card_id="10")) + +def is_due(card_id): + dbsession = get_session() + interval = get_interval(card_id) + + last_rating_date = dbsession.query(Rating).filter(Rating.card_id == card_id).order_by(desc(Rating.rating_time)).first() + due_date = last_rating_date.rating_time + timedelta(interval) + + return date.today() >= due_date + + +def list_of_due_cardby_ids(user_id): + dbsession = get_session() + cards = dbsession.query(Card).filter(Card.owner_id == user_id).all() + l = [] + for card in cards: + if is_due(card.id): + l.append(card.id) + return l + + +def list_of_new_cards(user_id): + dbsession = get_session() + cards = dbsession.query(Card).filter(Card.owner_id == user_id).all() + l = [] + for card in cards: + rating = dbsession.query(Rating).filter(Rating.card_id == card.id).first() + if rating == None: + l.append(card.id) + return l + + +def next_card(user_id): + """ + find a due card in user's collection. A due card is last_review + interval <= current_date. There could be no new card. + """ + dbsession = get_session() + #verjetno ne bo tako easy + next_card = dbsession.query(Rating).filter().first() + + return None def sr_session(): dbsession = get_session() @@ -13,63 +106,49 @@ def sr_session(): user_id = session['user_id'] username = session['username'] - - #koliko je new cards? - #koliko je due cards? - #ali obstaja naslednji card? - #kaj je naslednji card? - show_card = "" - - - #Form sent - # Share - # Rate - - + #what is the next card, None means no next card, aka deck done... + show_card = next_card(user_id) # Smo oddali obrazec? if request.method == 'POST': + #get card_if from the card, rendered from the template, data actually from the template... card_id = request.form.get('card_id', None) if not card_id: raise Exception("card_id je nujen!") - + #this is the rendered card aka the card we are rating and submiting the data on submit_card = dbsession.query(Card).get(card_id) - # @TODO preveri, ali je card del trenutnega decka! + #@TODO check if this card is part of the user's deck to prevent possibile hack? - # Ali damo share? Potem nastavi na share in ponovi obrazec + # Share + #Share button pressed? then reset the form - hence the if-else share_request = request.form.get("share", None) if share_request: # @TODO logika za share! share(submit_card, user_id) - # Če ne, gre za rate! + # Rate + #If it's not a share it's a rate! else: - rate = request.form.get('rate', None) #je to nevarno?? - print(rate) + rate = request.form.get('rate', None) #is this get somehow dangerous? if not rate: - raise Exception("manjka rate info!") + raise Exception("Need rate info!") if rate == "Yes": - submit_card.interest_rate = 1 + pass elif rate == "Maybe": - k = 0.5 - print(submit_card) - submit_card.interest_rate= abs(submit_card.interest_rate*k) - + pass + elif rate == "No": - k = 0.1 - submit_card.interest_rate = abs(submit_card.interest_rate*k) - + pass + elif rate == "Delete": - submit_card.interest_rate = 0 - #@TODO to bi lahko zbrisalo tudi file v določenih primerih + #@TODO this should delete the file in some cases. + pass # Any more cards? - - # Loudamo naslednjo karto - - # Prikaži obrazec + + #Display from return render_template("deck/index.html", username=username, card=show_card) \ No newline at end of file