urosm 2022-08-13 15:35:02 +02:00
commit ca4e65d504
5 changed files with 282 additions and 111 deletions

View File

@ -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.

94
app.py
View File

@ -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():

View File

@ -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'])

96
prob_session.py 100644
View File

@ -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)

154
sr_session.py 100644
View File

@ -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)