diff --git a/README.md b/README.md index 10e21b0..4583e59 100644 --- a/README.md +++ b/README.md @@ -55,3 +55,24 @@ If you have any questions, feedback, ideas or if you would like contribute, plea ## Build instructions 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. diff --git a/app.py b/app.py index aa61967..fd78281 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,94 +30,7 @@ def create_app(test_config=None): @app.route('/deck', methods=["GET", "POST"]) def deck(): - dbsession = get_session() - if not 'user_id' in session: - return index() - - 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) - return index() - - # 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.html", username=username, card=show_card) - + return prob_session() @app.route("/share_button", methods=["GET", "POST"]) def share_button(): 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']) 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 diff --git a/sr_session.py b/sr_session.py new file mode 100644 index 0000000..b0e2933 --- /dev/null +++ b/sr_session.py @@ -0,0 +1,154 @@ +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() + + #check if user in + if not 'user_id' in session: + redirect(url_for('login')) + user_id = session['user_id'] + username = session['username'] + + #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 check if this card is part of the user's deck to prevent possibile hack? + + # 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) + + # Rate + #If it's not a share it's a rate! + else: + rate = request.form.get('rate', None) #is this get somehow dangerous? + + if not rate: + raise Exception("Need rate info!") + + if rate == "Yes": + pass + + elif rate == "Maybe": + pass + + elif rate == "No": + pass + + elif rate == "Delete": + #@TODO this should delete the file in some cases. + pass + + # Any more cards? + + #Display from + return render_template("deck/index.html", username=username, card=show_card) \ No newline at end of file