Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
fef70ba
feature: add favorite quotes
subscorp Feb 15, 2021
da92003
fix: fixed conflicts
subscorp Feb 15, 2021
4d4ac98
fix: fixed flake8 error
subscorp Feb 15, 2021
9a103e6
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
subscorp Feb 15, 2021
8392bb2
fix: fixed pytest issues
subscorp Feb 15, 2021
c728f0c
a small fix
subscorp Feb 15, 2021
f62f8d3
fix: fixed display issue in favorite_quotes.html
subscorp Feb 15, 2021
82d79fd
fix: moved favorite quotes from navigation bar to profile
subscorp Feb 15, 2021
26f6a91
fix: fixed issues according to comments
subscorp Feb 16, 2021
076ab07
fix: fixed conflicts
subscorp Feb 17, 2021
701228e
fix: fixed issues according to comments
subscorp Feb 18, 2021
a9fe632
fix: fixed conflicts
subscorp Feb 18, 2021
fbe286c
fix: fixed flake8 issues
subscorp Feb 18, 2021
3026dd7
fix: fixed issue in pytest
subscorp Feb 18, 2021
2238a8d
fix: a tiny improvement
subscorp Feb 18, 2021
d8ce7b5
fix: small fix
subscorp Feb 18, 2021
47b0b5f
fix: another tiny improvement
subscorp Feb 18, 2021
1308754
fix: fixed issues according to comments
subscorp Feb 20, 2021
fef81a7
fix: fixed conflicts
subscorp Feb 20, 2021
e53fab9
fix: fixed pytest issues
subscorp Feb 20, 2021
fedc394
fix: a small fix
subscorp Feb 20, 2021
ee7ec43
fix: another small fix
subscorp Feb 20, 2021
c012d64
Update: now using the new login system
subscorp Feb 20, 2021
ac4a818
Update: now using the new login system
subscorp Feb 20, 2021
1800655
fix: fixed low coverage in pytest
subscorp Feb 20, 2021
a975a1c
fix: moved styles to css
subscorp Feb 21, 2021
bdba51f
fix: fixed conflicts
subscorp Feb 21, 2021
34d1ee6
Update: moved Favorite Quotes to profile
subscorp Feb 21, 2021
3098862
fix: fixed issues according to comments
subscorp Feb 22, 2021
1791fa6
fix: fixed conflicts
subscorp Feb 22, 2021
b722eea
fix: fixed pytest issue
subscorp Feb 22, 2021
5e5f6c3
fix: fixed issues in pytest
subscorp Feb 22, 2021
5d243e3
fix: small fix
subscorp Feb 22, 2021
56b0988
fix: fixed conflicts
subscorp Feb 23, 2021
097271a
fix: fixed issues according to comments
subscorp Feb 23, 2021
667b4a3
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
subscorp Feb 23, 2021
8a0464e
fix: a small fix
subscorp Feb 23, 2021
1fa587a
fix: fixed issues in pytest
subscorp Feb 23, 2021
de61e04
fix: flake8 error fix
subscorp Feb 24, 2021
f6355dc
fix: flake8 error fix
subscorp Feb 24, 2021
9c3a795
fix: flake8 error fix
subscorp Feb 24, 2021
f2c7d7a
fix: fixed conflicts
subscorp Feb 24, 2021
0e0f704
fix: revert some changes due to a bug
subscorp Feb 24, 2021
6bb30e1
Update: moved functions out of main
subscorp Feb 24, 2021
0a846f7
fix: a small fix
subscorp Feb 24, 2021
a059d81
Updates: A. heart now appears only when logged in. B. landing page no…
subscorp Feb 24, 2021
9a76d3f
fix: some small fixes and mainly added tests to both the audio featur…
subscorp Feb 25, 2021
37ec594
fix: fixed conflicts
subscorp Feb 25, 2021
0ea5847
fix: fixed issues according to comments
subscorp Feb 25, 2021
9f55f15
fix: fixed issues according to comments
subscorp Feb 25, 2021
99f5e33
fix: a small, final fix
subscorp Feb 25, 2021
c390fea
fix: fixed issues according to comments
subscorp Feb 25, 2021
3e33aa2
fix: fixed merge issue :)
subscorp Feb 26, 2021
79a8e6d
fix: fixed conflicts
subscorp Feb 26, 2021
19aae78
fix: trying to fix a bug in pytest
subscorp Feb 26, 2021
c671d5f
fix: trying to fix the bug
subscorp Feb 26, 2021
1e4c089
fix: trying to fix the bug
subscorp Feb 26, 2021
dccf688
fix: trying to fix the bug
subscorp Feb 26, 2021
095fcea
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
subscorp Feb 26, 2021
60af145
trying to fix the bug
subscorp Feb 26, 2021
07f6cc6
fix: trying to fix the bug (it's only in github)
subscorp Feb 26, 2021
970f296
fix: trying to fix the bug (it's only in github)
subscorp Feb 26, 2021
e5bcdc5
fix: trying to fix the bug
subscorp Feb 26, 2021
0504e22
fix: trying to fix the bug
subscorp Feb 26, 2021
12481b4
fix: trying to fix the bug
subscorp Feb 26, 2021
0d4042f
fix: trying to fix the bug
subscorp Feb 26, 2021
de05d1a
fix: trying to fix the bug
subscorp Feb 26, 2021
4abe14d
fix: trying to fix the bug
subscorp Feb 26, 2021
404e195
fix: trying to fix the bug
subscorp Feb 26, 2021
23becd6
fix: trying to fix the bug
subscorp Feb 26, 2021
943162a
fix: trying to fix the bug
subscorp Feb 26, 2021
9f5244e
fix: trying to fix the bug
subscorp Feb 26, 2021
cac2432
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
subscorp Feb 26, 2021
5fd6929
fix: fixed issues according to comments
subscorp Feb 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
from sqlalchemy.ext.declarative.api import declarative_base
from sqlalchemy.orm import relationship, Session
from sqlalchemy.sql.expression import null
from sqlalchemy.sql.schema import CheckConstraint

from app.config import PSQL_ENVIRONMENT
Expand Down Expand Up @@ -275,6 +276,7 @@ class Quote(Base):
id = Column(Integer, primary_key=True, index=True)
text = Column(String, nullable=False)
author = Column(String)
is_favorite = Column(Boolean)


class Comment(Base):
Expand Down Expand Up @@ -323,3 +325,11 @@ def insert_data(target, session: Session, **kw):


event.listen(Language.__table__, 'after_create', insert_data)


class UserQuotes(Base):
__tablename__ = "user_quotes"

id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
quote_id = Column(Integer, ForeignKey("quotes.id"), nullable=False)
44 changes: 41 additions & 3 deletions app/internal/daily_quotes.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from datetime import date
from typing import Dict, Optional

from app.database.models import Quote, UserQuotes
from sqlalchemy.orm import Session
from sqlalchemy.sql.expression import func

from app.database.models import Quote

TOTAL_DAYS = 366


Expand All @@ -14,7 +13,8 @@ def create_quote_object(quotes_fields: Dict[str, Optional[str]]) -> Quote:
It is used for adding the data from the json into the db"""
return Quote(
text=quotes_fields['text'],
author=quotes_fields['author']
author=quotes_fields['author'],
is_favorite=False
)


Expand All @@ -28,3 +28,41 @@ def quote_per_day(
quote = session.query(Quote).filter(
Quote.id % TOTAL_DAYS == day_num).order_by(func.random()).first()
return quote


def get_quotes(session, user_id):
"""Retrieves the users' favorite quotes from the database."""
quotes = []
user_quotes = session.query(UserQuotes).filter_by(user_id=user_id).all()
for user_quote in user_quotes:
quote_id = user_quote.quote_id
quote = session.query(Quote).filter_by(id=quote_id).first()
quote.is_favorite = True
quotes.append(quote)
return quotes


def get_quote_id(session, quote):
"""Retrieve quote id given the text of the quote."""
return session.query(Quote).filter_by(text=quote).first().id


def save_quote(session, user_id, quote):
"""Saves a quote in the database."""
quote_id = get_quote_id(session, quote)
record = session.query(UserQuotes).filter(
UserQuotes.user_id == user_id, UserQuotes.quote_id == quote_id).first()
if not record:
user_quote = UserQuotes(user_id=user_id, quote_id=quote_id)
session.add(user_quote)
session.commit()


def remove_quote(session, user_id, quote):
"""Removes a quote from the database."""
quote_id = get_quote_id(session, quote)
record = session.query(UserQuotes).filter(
UserQuotes.user_id == user_id, UserQuotes.quote_id == quote_id).first()
if record:
session.delete(record)
session.commit()
37 changes: 33 additions & 4 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from app import config
from app.database import engine, models
from app.dependencies import get_db, logger, MEDIA_PATH, STATIC_PATH, templates
from app.dependencies import MEDIA_PATH, STATIC_PATH, get_db, logger, templates
from app.internal import daily_quotes, json_data_loader

from app.internal.languages import set_ui_language
from app.internal.security.ouath2 import auth_exception_handler
from app.utils.extending_openapi import custom_openapi
from app.routers.salary import routes as salary
from fastapi import Depends, FastAPI, Request
from fastapi import Depends, FastAPI, Form, Request
from fastapi.openapi.docs import (
get_swagger_ui_html,
get_swagger_ui_oauth2_redirect_html,
Expand Down Expand Up @@ -94,16 +94,45 @@ async def swagger_ui_redirect():
app.include_router(router)


@app.get("/")
# TODO: I add the quote day to the home page
# until the relevant calendar view will be developed.
@app.get("/", include_in_schema=False)
@logger.catch()
async def home(request: Request, db: Session = Depends(get_db)):
"""Home page for the website."""
quote = daily_quotes.quote_per_day(db)
return templates.TemplateResponse("index.html", {
user_quotes = daily_quotes.get_quotes(db, 1)
for user_quote in user_quotes:
if user_quote.id == quote.id:
quote.is_favorite = True
return templates.TemplateResponse("home.html", {
"request": request,
"quote": quote,
})


custom_openapi(app)
@app.post("/")
async def save_or_delete_quote(
user_id: int = Form(...),
quote: str = Form(...),
to_save: bool = Form(...),
db: Session = Depends(get_db)):
"""Saves or deletes a quote from the database."""
if to_save:
daily_quotes.save_quote(db, user_id, quote)
else:
daily_quotes.remove_quote(db, user_id, quote)


@app.get("/favorite_quotes")
async def favorite_quotes(
request: Request,
db: Session = Depends(get_db),
user_id: int = 1):
"""html page for displaying the users' favorite quotes."""
quotes = daily_quotes.get_quotes(db, user_id)
return templates.TemplateResponse("favorite_quotes.html", {
"request": request,
"quotes": quotes,
})
Binary file added app/media/empty_heart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/media/full_heart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions app/static/favorite_quotes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
let hearts = Array.from(document.getElementsByClassName("heart"));
hearts.forEach((heart) => {
if (heart) {
heart.addEventListener("click", on_heart_click);
}
});

function on_heart_click() {
const quote = this.parentNode.previousElementSibling.innerHTML.replaceAll(
'"',
""
);
const author_element = this.parentNode;
const author = author_element.innerHTML.split("\\ ")[1].split("\n")[0];
if (this.src.split("/").pop() == "empty_heart.png") {
this.src = "../media/full_heart.png";
save_or_remove_quote(1, quote, author, true);
} else {
this.src = "../media/empty_heart.png";
save_or_remove_quote(1, quote, author, false);
if (this.classList.contains("favorites")) {
this.parentNode.parentNode.innerHTML = null;
}
}
}

function save_or_remove_quote(user_id, quote, author, to_save) {
let xhr = new XMLHttpRequest();
xhr.open("post", "/");
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(
`user_id=${user_id}&quote=${quote}&author=${author}&to_save=${to_save}`
);
}
8 changes: 8 additions & 0 deletions app/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ p {
background-color: whitesmoke;
}

.subtitle {
font-size: 20px;
}

.heart {
width: 1.5em;
height: 1.5em;
}
.error-message {
line-height: 0;
color: red;
Expand Down
22 changes: 22 additions & 0 deletions app/templates/favorite_quotes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% extends "partials/index/index_base.html" %}

{% block content %}
<div class="container mt-4">
<h1>Favorite Quotes</h1>
<br>
{% for quote in quotes %}
{% if not quote.author %}
<p><i>"{{ quote.text }}"</i></p>
{% else %}
<p><i id="quote">"{{ quote.text }}"</i> <span id="author" style="font-size: small;">&nbsp; \ {{ quote.author }}
{% if quote.is_favorite %}
<img src="../media/full_heart.png" class="heart favorites">
{% else %}
<img src="../media/empty_heart.png" class="heart favorites">
{% endif %}
</span>
</p>
{% endif %}
{% endfor %}
</div>
{% endblock %}
16 changes: 9 additions & 7 deletions app/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@

{% block content %}


<div class="container mt-4">
</div>


<!-- I chose to add the quote day to the home page until the relavent calendar view will be developed. -->
<div>
<div class="container mt-4">
{% if quote %}
{% if not quote.author %}
<p><i>"{{ quote.text }}"</i></p>
{% else %}
<p><i>"{{ quote.text }}"</i> <span style="font-size: small;">&nbsp; \ {{ quote.author }}</span></p>
<p><i id="quote">"{{ quote.text }}"</i> <span id="author" style="font-size: small;">&nbsp; \ {{ quote.author }}
{% if quote.is_favorite %}
<img src="../media/full_heart.png" class="heart">
{% else %}
<img src="../media/empty_heart.png" class="heart">
{% endif %}
</span>
</p>
{% endif %}
{% endif %}
</div>
Expand Down
1 change: 1 addition & 0 deletions app/templates/partials/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
integrity="sha512-d9xgZrVZpmmQlfonhQUvTR7lMPtO7NkZMkA0ABN3PHCbKA5nqylQ/yWlFAyY6hYgdF1Qh6nYiuADWwKB4C2WSw=="
crossorigin="anonymous"></script>
<script defer type="text/javascript" src="{{ url_for('static', path='/popover.js') }}"></script>
<script defer type="text/javascript" src="{{ url_for('static', path='/favorite_quotes.js') }}"></script>
{% endblock head %}
{% block title %}
<title>Pylendar{% if self.page_name() %} - {% endif %}{% block page_name %}{% endblock %}</title>
Expand Down
3 changes: 3 additions & 0 deletions app/templates/partials/index/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
<li class="nav-item">
<a class="nav-link" href="/search">Search</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('favorite_quotes') }}">favorite quotes</a>
</li>
</ul>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions tests/client_fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ def profile_test_client() -> Iterator[TestClient]:


@pytest.fixture(scope="session")
def settings_test_client() -> Iterator[TestClient]:
yield from create_test_client(profile.get_db)


def security_test_client():
yield from create_test_client(event.get_db)

Expand Down
11 changes: 7 additions & 4 deletions tests/quotes_fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@


def add_quote(
session: Session, id_quote: int, text: str, author: str) -> Quote:
session: Session, id_quote: int, text: str, author: str, is_favorite: bool) -> Quote:
quote = create_model(
session, Quote,
id=id_quote,
text=text,
author=author
author=author,
is_favorite=is_favorite
)
yield quote
delete_instance(session, quote)
Expand All @@ -23,7 +24,8 @@ def quote1(session: Session) -> Quote:
session=session,
id_quote=1,
text='You have to believe in yourself.',
author='Sun Tzu'
author='Sun Tzu',
is_favorite=False
)


Expand All @@ -33,5 +35,6 @@ def quote2(session: Session) -> Quote:
session=session,
id_quote=2,
text='Wisdom begins in wonder.',
author='Socrates'
author='Socrates',
is_favorite=False
)
46 changes: 46 additions & 0 deletions tests/test_quotes.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from datetime import date

from app.internal import daily_quotes
from app.main import app

DATE = date(2021, 1, 1)
DATE2 = date(2021, 1, 2)
HOME_URL = app.url_path_for("home")
FAVORITE_QUOTES_URL = app.url_path_for("favorite_quotes")


def test_create_quote_object():
Expand All @@ -27,3 +30,46 @@ def test_quote_per_day_get_first_quote(session, quote1, quote2):
def test_quote_per_day_get_second_quote(session, quote1, quote2):
assert daily_quotes.quote_per_day(
session, DATE2).text == quote2.text


# Tests for saving, removing and displaying favorite quotes
def test_save_quote(session, settings_test_client, quote1):
data = {
"user_id": 1,
"quote": quote1.text,
"author": quote1.author,
"to_save": True,
}
quotes = daily_quotes.get_quotes(session, 1)
response = settings_test_client.post(
url=HOME_URL, data=data)
assert response.ok
assert len(daily_quotes.get_quotes(session, 1)) == len(quotes) + 1


def test_home(session, settings_test_client, quote1):
response = settings_test_client.get(
url=HOME_URL)
assert response.ok
assert b"Search" in response.content


def test_delete_quote(session, settings_test_client, quote1):
test_save_quote(session, settings_test_client, quote1)
data = {
"user_id": 1,
"quote": quote1.text,
"author": quote1.author,
"to_save": False,
}
response = settings_test_client.post(
url=HOME_URL, data=data)
assert response.ok
assert len(daily_quotes.get_quotes(session, 1)) == 0


def test_get_favorite_quotes(session, settings_test_client, quote1):
test_save_quote(session, settings_test_client, quote1)
response = settings_test_client.get(url=FAVORITE_QUOTES_URL)
assert response.ok
assert b"Favorite Quotes" in response.content